summaryrefslogtreecommitdiffstats
path: root/ceee/ie/plugin
diff options
context:
space:
mode:
Diffstat (limited to 'ceee/ie/plugin')
-rw-r--r--ceee/ie/plugin/bho/bho.gyp87
-rw-r--r--ceee/ie/plugin/bho/browser_helper_object.cc1372
-rw-r--r--ceee/ie/plugin/bho/browser_helper_object.h345
-rw-r--r--ceee/ie/plugin/bho/browser_helper_object.rgs28
-rw-r--r--ceee/ie/plugin/bho/browser_helper_object_unittest.cc743
-rw-r--r--ceee/ie/plugin/bho/cookie_accountant.cc226
-rw-r--r--ceee/ie/plugin/bho/cookie_accountant.h113
-rw-r--r--ceee/ie/plugin/bho/cookie_accountant_unittest.cc141
-rw-r--r--ceee/ie/plugin/bho/cookie_events_funnel.cc28
-rw-r--r--ceee/ie/plugin/bho/cookie_events_funnel.h28
-rw-r--r--ceee/ie/plugin/bho/cookie_events_funnel_unittest.cc59
-rw-r--r--ceee/ie/plugin/bho/dom_utils.cc126
-rw-r--r--ceee/ie/plugin/bho/dom_utils.h53
-rw-r--r--ceee/ie/plugin/bho/dom_utils_unittest.cc197
-rw-r--r--ceee/ie/plugin/bho/events_funnel.cc42
-rw-r--r--ceee/ie/plugin/bho/events_funnel.h38
-rw-r--r--ceee/ie/plugin/bho/events_funnel_unittest.cc62
-rw-r--r--ceee/ie/plugin/bho/executor.cc771
-rw-r--r--ceee/ie/plugin/bho/executor.h166
-rw-r--r--ceee/ie/plugin/bho/executor.rgs9
-rw-r--r--ceee/ie/plugin/bho/executor_creator.rgs9
-rw-r--r--ceee/ie/plugin/bho/executor_unittest.cc1080
-rw-r--r--ceee/ie/plugin/bho/extension_port_manager.cc193
-rw-r--r--ceee/ie/plugin/bho/extension_port_manager.h86
-rw-r--r--ceee/ie/plugin/bho/frame_event_handler.cc518
-rw-r--r--ceee/ie/plugin/bho/frame_event_handler.h309
-rw-r--r--ceee/ie/plugin/bho/frame_event_handler_unittest.cc740
-rw-r--r--ceee/ie/plugin/bho/http_negotiate.cc197
-rw-r--r--ceee/ie/plugin/bho/http_negotiate.h69
-rw-r--r--ceee/ie/plugin/bho/infobar_browser_window.cc205
-rw-r--r--ceee/ie/plugin/bho/infobar_browser_window.h156
-rw-r--r--ceee/ie/plugin/bho/infobar_events_funnel.cc21
-rw-r--r--ceee/ie/plugin/bho/infobar_events_funnel.h24
-rw-r--r--ceee/ie/plugin/bho/infobar_events_funnel_unittest.cc38
-rw-r--r--ceee/ie/plugin/bho/infobar_manager.cc270
-rw-r--r--ceee/ie/plugin/bho/infobar_manager.h67
-rw-r--r--ceee/ie/plugin/bho/infobar_window.cc314
-rw-r--r--ceee/ie/plugin/bho/infobar_window.h143
-rw-r--r--ceee/ie/plugin/bho/mediumtest_browser_event.cc495
-rw-r--r--ceee/ie/plugin/bho/mediumtest_browser_helper_object.cc531
-rw-r--r--ceee/ie/plugin/bho/tab_events_funnel.cc85
-rw-r--r--ceee/ie/plugin/bho/tab_events_funnel.h61
-rw-r--r--ceee/ie/plugin/bho/tab_events_funnel_unittest.cc141
-rw-r--r--ceee/ie/plugin/bho/tab_window_manager.cc244
-rw-r--r--ceee/ie/plugin/bho/tab_window_manager.h39
-rw-r--r--ceee/ie/plugin/bho/tool_band_visibility.cc179
-rw-r--r--ceee/ie/plugin/bho/tool_band_visibility.h98
-rw-r--r--ceee/ie/plugin/bho/tool_band_visibility_unittest.cc179
-rw-r--r--ceee/ie/plugin/bho/web_browser_events_source.h34
-rw-r--r--ceee/ie/plugin/bho/web_progress_notifier.cc653
-rw-r--r--ceee/ie/plugin/bho/web_progress_notifier.h366
-rw-r--r--ceee/ie/plugin/bho/web_progress_notifier_unittest.cc593
-rw-r--r--ceee/ie/plugin/bho/webnavigation_events_funnel.cc113
-rw-r--r--ceee/ie/plugin/bho/webnavigation_events_funnel.h108
-rw-r--r--ceee/ie/plugin/bho/webnavigation_events_funnel_unittest.cc180
-rw-r--r--ceee/ie/plugin/bho/webrequest_events_funnel.cc106
-rw-r--r--ceee/ie/plugin/bho/webrequest_events_funnel.h98
-rw-r--r--ceee/ie/plugin/bho/webrequest_events_funnel_unittest.cc171
-rw-r--r--ceee/ie/plugin/bho/webrequest_notifier.cc811
-rw-r--r--ceee/ie/plugin/bho/webrequest_notifier.h453
-rw-r--r--ceee/ie/plugin/bho/webrequest_notifier_unittest.cc340
-rw-r--r--ceee/ie/plugin/bho/window_message_source.cc216
-rw-r--r--ceee/ie/plugin/bho/window_message_source.h103
-rw-r--r--ceee/ie/plugin/scripting/base.js1291
-rw-r--r--ceee/ie/plugin/scripting/ceee_bootstrap.js145
-rw-r--r--ceee/ie/plugin/scripting/content_script_manager.cc384
-rw-r--r--ceee/ie/plugin/scripting/content_script_manager.h112
-rw-r--r--ceee/ie/plugin/scripting/content_script_manager.rc19
-rw-r--r--ceee/ie/plugin/scripting/content_script_manager_unittest.cc489
-rw-r--r--ceee/ie/plugin/scripting/content_script_native_api.cc276
-rw-r--r--ceee/ie/plugin/scripting/content_script_native_api.h183
-rw-r--r--ceee/ie/plugin/scripting/content_script_native_api_unittest.cc209
-rw-r--r--ceee/ie/plugin/scripting/json.js318
-rw-r--r--ceee/ie/plugin/scripting/renderer_extension_bindings_unittest.cc479
-rw-r--r--ceee/ie/plugin/scripting/renderer_extension_bindings_unittest.rc12
-rw-r--r--ceee/ie/plugin/scripting/script_host.cc886
-rw-r--r--ceee/ie/plugin/scripting/script_host.h317
-rw-r--r--ceee/ie/plugin/scripting/script_host_unittest.cc519
-rw-r--r--ceee/ie/plugin/scripting/scripting.gyp94
-rw-r--r--ceee/ie/plugin/scripting/transform_native_js.py53
-rw-r--r--ceee/ie/plugin/scripting/userscripts_docs.h66
-rw-r--r--ceee/ie/plugin/scripting/userscripts_librarian.cc119
-rw-r--r--ceee/ie/plugin/scripting/userscripts_librarian.h73
-rw-r--r--ceee/ie/plugin/scripting/userscripts_librarian_unittest.cc332
-rw-r--r--ceee/ie/plugin/toolband/resource.h35
-rw-r--r--ceee/ie/plugin/toolband/tool_band.cc628
-rw-r--r--ceee/ie/plugin/toolband/tool_band.h247
-rw-r--r--ceee/ie/plugin/toolband/tool_band.rgs20
-rw-r--r--ceee/ie/plugin/toolband/tool_band_unittest.cc363
-rw-r--r--ceee/ie/plugin/toolband/toolband.def13
-rw-r--r--ceee/ie/plugin/toolband/toolband.gyp140
-rw-r--r--ceee/ie/plugin/toolband/toolband.idl370
-rw-r--r--ceee/ie/plugin/toolband/toolband.rc116
-rw-r--r--ceee/ie/plugin/toolband/toolband_module.cc408
-rw-r--r--ceee/ie/plugin/toolband/toolband_module_reporting.cc50
-rw-r--r--ceee/ie/plugin/toolband/toolband_module_reporting.h23
-rw-r--r--ceee/ie/plugin/toolband/toolband_module_reporting_unittest.cc60
97 files changed, 24319 insertions, 0 deletions
diff --git a/ceee/ie/plugin/bho/bho.gyp b/ceee/ie/plugin/bho/bho.gyp
new file mode 100644
index 0000000..fe773d39
--- /dev/null
+++ b/ceee/ie/plugin/bho/bho.gyp
@@ -0,0 +1,87 @@
+# 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.
+
+{
+ 'variables': {
+ 'chromium_code': 1,
+ },
+ 'includes': [
+ '../../../../build/common.gypi',
+ ],
+ 'targets': [
+ {
+ 'target_name': 'bho',
+ 'type': 'static_library',
+ 'dependencies': [
+ '../toolband/toolband.gyp:toolband_idl',
+ '../../broker/broker.gyp:broker',
+ '../../common/common.gyp:ie_common',
+ '../../common/common.gyp:ie_common_settings',
+ '../../../common/common.gyp:ceee_common',
+ '../../../common/common.gyp:initializing_coclass',
+ '../../../../base/base.gyp:base',
+ # For the vtable patching stuff...
+ '../../../../chrome_frame/chrome_frame.gyp:chrome_frame_ie',
+ ],
+ 'sources': [
+ 'browser_helper_object.cc',
+ 'browser_helper_object.h',
+ 'browser_helper_object.rgs',
+ 'cookie_accountant.cc',
+ 'cookie_accountant.h',
+ 'cookie_events_funnel.cc',
+ 'cookie_events_funnel.h',
+ 'dom_utils.cc',
+ 'dom_utils.h',
+ 'events_funnel.cc',
+ 'events_funnel.h',
+ 'executor.cc',
+ 'executor.h',
+ 'extension_port_manager.cc',
+ 'extension_port_manager.h',
+ 'frame_event_handler.cc',
+ 'frame_event_handler.h',
+ 'http_negotiate.cc',
+ 'http_negotiate.h',
+ 'infobar_browser_window.cc',
+ 'infobar_browser_window.h',
+ 'infobar_events_funnel.cc',
+ 'infobar_events_funnel.h',
+ 'infobar_manager.cc',
+ 'infobar_manager.h',
+ 'infobar_window.cc',
+ 'infobar_window.h',
+ '../../common/precompile.cc',
+ '../../common/precompile.h',
+ 'tab_events_funnel.cc',
+ 'tab_events_funnel.h',
+ 'tab_window_manager.cc',
+ 'tab_window_manager.h',
+ 'tool_band_visibility.cc',
+ 'tool_band_visibility.h',
+ 'web_browser_events_source.h',
+ 'webnavigation_events_funnel.cc',
+ 'webnavigation_events_funnel.h',
+ 'webrequest_events_funnel.cc',
+ 'webrequest_events_funnel.h',
+ 'webrequest_notifier.cc',
+ 'webrequest_notifier.h',
+ 'web_progress_notifier.cc',
+ 'web_progress_notifier.h',
+ 'window_message_source.cc',
+ 'window_message_source.h',
+
+ '../../../../chrome_frame/renderer_glue.cc', # needed for cf_ie.lib
+ '../../../../chrome/common/extensions/extension_resource.cc',
+ '../../../../chrome/common/extensions/extension_resource.h',
+ ],
+ 'configurations': {
+ 'Debug': {
+ 'msvs_precompiled_source': '../../common/precompile.cc',
+ 'msvs_precompiled_header': '../../common/precompile.h',
+ },
+ },
+ },
+ ]
+}
diff --git a/ceee/ie/plugin/bho/browser_helper_object.cc b/ceee/ie/plugin/bho/browser_helper_object.cc
new file mode 100644
index 0000000..7a9f4d7
--- /dev/null
+++ b/ceee/ie/plugin/bho/browser_helper_object.cc
@@ -0,0 +1,1372 @@
+// 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.
+//
+// IE browser helper object implementation.
+#include "ceee/ie/plugin/bho/browser_helper_object.h"
+
+#include <atlsafe.h>
+#include <shlguid.h>
+
+#include <algorithm>
+
+#include "base/debug/trace_event.h"
+#include "base/json/json_reader.h"
+#include "base/logging.h"
+#include "base/string_util.h"
+#include "base/tuple.h"
+#include "base/utf_string_conversions.h"
+#include "ceee/common/com_utils.h"
+#include "ceee/common/window_utils.h"
+#include "ceee/common/windows_constants.h"
+#include "ceee/ie/broker/tab_api_module.h"
+#include "ceee/ie/common/extension_manifest.h"
+#include "ceee/ie/common/ie_util.h"
+#include "ceee/ie/common/ceee_module_util.h"
+#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 "chrome/browser/automation/extension_automation_constants.h"
+#include "chrome/browser/extensions/extension_tabs_module_constants.h"
+#include "chrome/common/url_constants.h"
+#include "chrome/test/automation/automation_constants.h"
+#include "googleurl/src/gurl.h"
+
+#include "broker_lib.h" // NOLINT
+
+namespace keys = extension_tabs_module_constants;
+namespace ext = extension_automation_constants;
+
+
+_ATL_FUNC_INFO
+ BrowserHelperObject::handler_type_idispatch_5variantptr_boolptr_ = {
+ CC_STDCALL, VT_EMPTY, 7, {
+ VT_DISPATCH,
+ VT_VARIANT | VT_BYREF,
+ VT_VARIANT | VT_BYREF,
+ VT_VARIANT | VT_BYREF,
+ VT_VARIANT | VT_BYREF,
+ VT_VARIANT | VT_BYREF,
+ VT_BOOL | VT_BYREF
+ }
+};
+
+_ATL_FUNC_INFO BrowserHelperObject::handler_type_idispatch_variantptr_ = {
+ CC_STDCALL, VT_EMPTY, 2, {
+ VT_DISPATCH,
+ VT_VARIANT | VT_BYREF
+ }
+};
+
+_ATL_FUNC_INFO
+ BrowserHelperObject::handler_type_idispatch_3variantptr_boolptr_ = {
+ CC_STDCALL, VT_EMPTY, 5, {
+ VT_DISPATCH,
+ VT_VARIANT | VT_BYREF,
+ VT_VARIANT | VT_BYREF,
+ VT_VARIANT | VT_BYREF,
+ VT_BOOL | VT_BYREF
+ }
+};
+
+_ATL_FUNC_INFO BrowserHelperObject::handler_type_idispatchptr_boolptr_ = {
+ CC_STDCALL, VT_EMPTY, 2, {
+ VT_DISPATCH | VT_BYREF,
+ VT_BOOL | VT_BYREF
+ }
+};
+
+_ATL_FUNC_INFO
+ BrowserHelperObject::handler_type_idispatchptr_boolptr_dword_2bstr_ = {
+ CC_STDCALL, VT_EMPTY, 5, {
+ VT_DISPATCH | VT_BYREF,
+ VT_BOOL | VT_BYREF,
+ VT_UI4,
+ VT_BSTR,
+ VT_BSTR
+ }
+};
+
+// Remove the need to AddRef/Release for Tasks that target this class.
+DISABLE_RUNNABLE_METHOD_REFCOUNT(BrowserHelperObject);
+
+BrowserHelperObject::BrowserHelperObject()
+ : already_tried_installing_(false),
+ tab_window_(NULL),
+ tab_id_(kInvalidChromeSessionId),
+ fired_on_created_event_(false),
+ lower_bound_ready_state_(READYSTATE_UNINITIALIZED),
+ ie7_or_later_(false),
+ thread_id_(::GetCurrentThreadId()),
+ full_tab_chrome_frame_(false) {
+ TRACE_EVENT_BEGIN("ceee.bho", this, "");
+ // Only the first call to this function really does anything.
+ CookieAccountant::GetInstance()->PatchWininetFunctions();
+}
+
+BrowserHelperObject::~BrowserHelperObject() {
+ TRACE_EVENT_END("ceee.bho", this, "");
+}
+
+HRESULT BrowserHelperObject::FinalConstruct() {
+ return S_OK;
+}
+
+void BrowserHelperObject::FinalRelease() {
+ web_browser_.Release();
+}
+
+STDMETHODIMP BrowserHelperObject::SetSite(IUnknown* site) {
+ typedef IObjectWithSiteImpl<BrowserHelperObject> SuperSite;
+
+ // From experience, we know the site may be set multiple times.
+ // Let's ignore second and subsequent set or unset.
+ if (NULL != site && NULL != m_spUnkSite.p ||
+ NULL == site && NULL == m_spUnkSite.p) {
+ LOG(WARNING) << "Duplicate call to SetSite, previous site "
+ << m_spUnkSite.p << " new site " << site;
+ return S_OK;
+ }
+
+ if (NULL == site) {
+ // We're being torn down.
+ TearDown();
+
+ FireOnRemovedEvent();
+ // This call should be the last thing we send to the broker.
+ FireOnUnmappedEvent();
+ }
+
+ HRESULT hr = SuperSite::SetSite(site);
+ if (FAILED(hr))
+ return hr;
+
+ if (NULL != site) {
+ // We're being initialized.
+ hr = Initialize(site);
+
+ // Release the site in case of failure.
+ if (FAILED(hr))
+ SuperSite::SetSite(NULL);
+ }
+
+ return hr;
+}
+
+HRESULT BrowserHelperObject::GetParentBrowser(IWebBrowser2* browser,
+ IWebBrowser2** parent_browser) {
+ DCHECK(browser != NULL);
+ DCHECK(parent_browser != NULL && *parent_browser == NULL);
+
+ // Get the parent object.
+ CComPtr<IDispatch> parent_disp;
+ HRESULT hr = browser->get_Parent(&parent_disp);
+ if (FAILED(hr)) {
+ // NO DCHECK, or even log here, the caller will handle and log the error.
+ return hr;
+ }
+
+ // Then get the associated browser through the appropriate contortions.
+ CComQIPtr<IServiceProvider> parent_sp(parent_disp);
+ if (parent_sp == NULL)
+ return E_NOINTERFACE;
+
+ CComPtr<IWebBrowser2> parent;
+ hr = parent_sp->QueryService(SID_SWebBrowserApp,
+ IID_IWebBrowser2,
+ reinterpret_cast<void**>(&parent));
+ if (FAILED(hr))
+ return hr;
+
+ DCHECK(parent != NULL);
+ // IE seems to define the top-level browser as its own parent,
+ // hence this check and error return.
+ if (parent == browser)
+ return E_FAIL;
+
+ LOG(INFO) << "Child: " << std::hex << browser << " -> Parent: " <<
+ std::hex << parent.p;
+
+ *parent_browser = parent.Detach();
+ return S_OK;
+}
+
+HRESULT BrowserHelperObject::GetBrokerRegistrar(
+ ICeeeBrokerRegistrar** broker) {
+ // This is a singleton and will not create a new one.
+ return ::CoCreateInstance(CLSID_CeeeBroker, NULL, CLSCTX_ALL,
+ IID_ICeeeBrokerRegistrar,
+ reinterpret_cast<void**>(broker));
+}
+
+HRESULT BrowserHelperObject::CreateExecutor(IUnknown** executor) {
+ HRESULT hr = ::CoCreateInstance(
+ CLSID_CeeeExecutor, NULL, CLSCTX_INPROC_SERVER,
+ IID_IUnknown, reinterpret_cast<void**>(executor));
+ if (SUCCEEDED(hr)) {
+ CComQIPtr<IObjectWithSite> executor_with_site(*executor);
+ DCHECK(executor_with_site != NULL)
+ << "Executor must implement IObjectWithSite.";
+ if (executor_with_site != NULL) {
+ CComPtr<IUnknown> bho_identity;
+ hr = QueryInterface(IID_IUnknown,
+ reinterpret_cast<void**>(&bho_identity));
+ DCHECK(SUCCEEDED(hr)) << "QueryInterface for IUnknown failed!!!" <<
+ com::LogHr(hr);
+ if (SUCCEEDED(hr))
+ executor_with_site->SetSite(bho_identity);
+ }
+ }
+
+ return hr;
+}
+
+WebProgressNotifier* BrowserHelperObject::CreateWebProgressNotifier() {
+ scoped_ptr<WebProgressNotifier> web_progress_notifier(
+ new WebProgressNotifier());
+ HRESULT hr = web_progress_notifier->Initialize(this, tab_window_,
+ web_browser_);
+
+ return SUCCEEDED(hr) ? web_progress_notifier.release() : NULL;
+}
+
+HRESULT BrowserHelperObject::Initialize(IUnknown* site) {
+ TRACE_EVENT_INSTANT("ceee.bho.initialize", this, "");
+
+ ie7_or_later_ = ie_util::GetIeVersion() > ie_util::IEVERSION_IE6;
+
+ HRESULT hr = InitializeChromeFrameHost();
+ 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_);
+
+ // Preemptively feed the broker with an executor in our thread.
+ hr = GetBrokerRegistrar(&broker_registrar_);
+ LOG_IF(ERROR, FAILED(hr)) << "Failed to create broker, hr=" <<
+ com::LogHr(hr);
+ DCHECK(SUCCEEDED(hr)) << "CoCreating Broker. " << com::LogHr(hr);
+ if (SUCCEEDED(hr)) {
+ DCHECK(executor_ == NULL);
+ hr = CreateExecutor(&executor_);
+ LOG_IF(ERROR, FAILED(hr)) << "Failed to create Executor, hr=" <<
+ com::LogHr(hr);
+ DCHECK(SUCCEEDED(hr)) << "CoCreating Executor. " << com::LogHr(hr);
+ if (SUCCEEDED(hr)) {
+ // TODO(mad@chromium.org): Implement the proper manual/secure
+ // registration.
+ hr = broker_registrar_->RegisterTabExecutor(::GetCurrentThreadId(),
+ executor_);
+ DCHECK(SUCCEEDED(hr)) << "Registering Executor. " << com::LogHr(hr);
+ }
+ }
+
+ // We need the service provider for both the sink connections and
+ // to get a handle to the tab window.
+ 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;
+ }
+
+ // Patch IHttpNegotiate for user-agent and cookie functionality.
+ HttpNegotiatePatch::Initialize();
+
+ CheckToolBandVisibility(web_browser_);
+
+ web_progress_notifier_.reset(CreateWebProgressNotifier());
+ DCHECK(web_progress_notifier_ != NULL)
+ << "Failed to initialize WebProgressNotifier";
+ if (web_progress_notifier_ == NULL) {
+ TearDown();
+ return E_FAIL;
+ }
+
+ return S_OK;
+}
+
+HRESULT BrowserHelperObject::TearDown() {
+ TRACE_EVENT_INSTANT("ceee.bho.teardown", this, "");
+
+ if (web_progress_notifier_ != NULL) {
+ web_progress_notifier_->TearDown();
+ web_progress_notifier_.reset(NULL);
+ }
+
+ ToolBandVisibility::TearDown();
+
+ if (executor_ != NULL) {
+ CComQIPtr<IObjectWithSite> executor_with_site(executor_);
+ DCHECK(executor_with_site != NULL)
+ << "Executor must implement IObjectWithSite.";
+ if (executor_with_site != NULL) {
+ executor_with_site->SetSite(NULL);
+ }
+ }
+
+ // Unregister our executor so that the broker don't have to rely
+ // on the thread dying to release it and confuse COM in trying to release it.
+ if (broker_registrar_ != NULL) {
+ DCHECK(executor_ != NULL);
+ // Manually disconnect executor_,
+ // so it doesn't get called while we unregister it below.
+ HRESULT hr = ::CoDisconnectObject(executor_, 0);
+ DCHECK(SUCCEEDED(hr)) << "Couldn't disconnect Executor. " << com::LogHr(hr);
+
+ // TODO(mad@chromium.org): Implement the proper manual/secure
+ // unregistration.
+ hr = broker_registrar_->UnregisterExecutor(::GetCurrentThreadId());
+ DCHECK(SUCCEEDED(hr)) << "Unregistering Executor. " << com::LogHr(hr);
+ } else {
+ DCHECK(executor_ == NULL);
+ }
+ executor_.Release();
+
+ if (web_browser_)
+ DispEventUnadvise(web_browser_, &DIID_DWebBrowserEvents2);
+ web_browser_.Release();
+
+ HttpNegotiatePatch::Uninitialize();
+
+ if (chrome_frame_host_) {
+ chrome_frame_host_->SetEventSink(NULL);
+ chrome_frame_host_->TearDown();
+ }
+ chrome_frame_host_.Release();
+
+ return S_OK;
+}
+
+HRESULT BrowserHelperObject::InitializeChromeFrameHost() {
+ HRESULT hr = CreateChromeFrameHost();
+ DCHECK(SUCCEEDED(hr) && chrome_frame_host_ != NULL);
+ if (FAILED(hr) || chrome_frame_host_ == NULL) {
+ LOG(ERROR) << "Failed to get chrome frame host " << com::LogHr(hr);
+ return com::AlwaysError(hr);
+ }
+
+ chrome_frame_host_->SetChromeProfileName(
+ ceee_module_util::GetBrokerProfileNameForIe());
+ chrome_frame_host_->SetEventSink(this);
+ hr = chrome_frame_host_->StartChromeFrame();
+ DCHECK(SUCCEEDED(hr)) << "Failed to start Chrome Frame." << com::LogHr(hr);
+ if (FAILED(hr)) {
+ chrome_frame_host_->SetEventSink(NULL);
+
+ LOG(ERROR) << "Failed to start chrome frame " << com::LogHr(hr);
+ return hr;
+ }
+
+ return hr;
+}
+
+HRESULT BrowserHelperObject::OnCfReadyStateChanged(LONG state) {
+ // If EnsureTabId() returns false, the session_id isn't available.
+ // This means that the ExternalTab hasn't been created yet, which is certainly
+ // a bug. Calling this here will also ensure we call all the deferred calls if
+ // they haven't been called yet.
+ bool id_available = EnsureTabId();
+ if (!id_available) {
+ NOTREACHED();
+ return E_UNEXPECTED;
+ }
+
+ if (state == READYSTATE_COMPLETE) {
+ extension_path_ = ceee_module_util::GetExtensionPath();
+
+ if (ceee_module_util::IsCrxOrEmpty(extension_path_) &&
+ ceee_module_util::NeedToInstallExtension()) {
+ LOG(INFO) << "Installing extension: \"" << extension_path_ << "\"";
+ chrome_frame_host_->InstallExtension(CComBSTR(extension_path_.c_str()));
+ } else {
+ // In the case where we don't have a CRX (or we don't need to install it),
+ // we must ask for the currently enabled extension before we can decide
+ // what we need to do.
+ chrome_frame_host_->GetEnabledExtensions();
+ }
+ }
+
+ return S_OK;
+}
+
+HRESULT BrowserHelperObject::OnCfExtensionReady(BSTR path, int response) {
+ TRACE_EVENT_INSTANT("ceee.bho.oncfextensionready", this, "");
+
+ if (ceee_module_util::IsCrxOrEmpty(extension_path_)) {
+ // If we get here, it's because we just did the first-time
+ // install, so save the installation path+time for future comparison.
+ ceee_module_util::SetInstalledExtensionPath(
+ FilePath(extension_path_));
+ }
+
+ // Now list enabled extensions so that we can properly start it whether
+ // it's a CRX file or an exploded folder.
+ //
+ // Note that we do this even if Chrome says installation failed,
+ // as that is the error code it uses when we try to install an
+ // older version of the extension than it already has, which happens
+ // on overinstall when Chrome has already auto-updated.
+ //
+ // If it turns out no extension is installed, we will handle that
+ // error in the OnCfGetEnabledExtensionsComplete callback.
+ chrome_frame_host_->GetEnabledExtensions();
+ return S_OK;
+}
+
+void BrowserHelperObject::StartExtension(const wchar_t* base_dir) {
+ chrome_frame_host_->SetUrl(CComBSTR(chrome::kAboutBlankURL));
+
+ LoadManifestFile(base_dir);
+
+ // There is a race between launching Chrome to get the directory of
+ // the extension, and the first page this BHO is attached to being loaded.
+ // If we hadn't loaded the manifest file when injection of scripts and
+ // CSS should have been done for that page, then do it now as it is the
+ // earliest opportunity.
+ BrowserHandlerMap::const_iterator it = browsers_.begin();
+ for (; it != browsers_.end(); ++it) {
+ DCHECK(it->second.m_T != NULL);
+ it->second.m_T->RedoDoneInjections();
+ }
+
+ // Now we should know the extension id and can pass it to the executor.
+ if (extension_id_.empty()) {
+ LOG(ERROR) << "Have no extension id after loading the extension.";
+ } else if (executor_ != NULL) {
+ CComPtr<ICeeeInfobarExecutor> infobar_executor;
+ HRESULT hr = executor_->QueryInterface(IID_ICeeeInfobarExecutor,
+ reinterpret_cast<void**>(&infobar_executor));
+ DCHECK(SUCCEEDED(hr)) << "Failed to get ICeeeInfobarExecutor interface " <<
+ com::LogHr(hr);
+ if (SUCCEEDED(hr)) {
+ infobar_executor->SetExtensionId(CComBSTR(extension_id_.c_str()));
+ }
+ }
+}
+
+HRESULT BrowserHelperObject::OnCfGetEnabledExtensionsComplete(
+ SAFEARRAY* base_dirs) {
+ CComSafeArray<BSTR> directories;
+ directories.Attach(base_dirs); // MUST DETACH BEFORE RETURNING
+
+ // TODO(joi@chromium.org) Deal with multiple extensions.
+ if (directories.GetCount() > 0) {
+ // If our extension_path is not a CRX, it MUST be the same as the installed
+ // extension path which would be an exploded extension.
+ // If you get this DCHECK, you may have changed your registry settings to
+ // debug with an exploded extension, but you didn't uninstall the previous
+ // extension, either via the Chrome UI or by simply wiping out your
+ // profile folder.
+ DCHECK(ceee_module_util::IsCrxOrEmpty(extension_path_) ||
+ extension_path_ == std::wstring(directories.GetAt(0)));
+ StartExtension(directories.GetAt(0));
+ } else if (!ceee_module_util::IsCrxOrEmpty(extension_path_)) {
+ // We have an extension path that isn't a CRX and we don't have any
+ // enabled extension, so we must load the exploded extension from this
+ // given path. WE MUST DO THIS BEFORE THE NEXT ELSE IF because it assumes
+ // a CRX file.
+ chrome_frame_host_->LoadExtension(CComBSTR(extension_path_.c_str()));
+ } else if (!already_tried_installing_ && !extension_path_.empty()) {
+ // We attempt to install the .crx file from the CEEE folder; in the
+ // default case this will happen only once after installation.
+ already_tried_installing_ = true;
+ chrome_frame_host_->InstallExtension(CComBSTR(extension_path_.c_str()));
+ }
+
+ // If no extension is installed, we do nothing. The toolband handles
+ // this error and will show an explanatory message to the user.
+ directories.Detach();
+ return S_OK;
+}
+
+HRESULT BrowserHelperObject::OnCfGetExtensionApisToAutomate(
+ BSTR* functions_enabled) {
+ *functions_enabled = NULL;
+ return S_FALSE;
+}
+
+HRESULT BrowserHelperObject::OnCfChannelError() {
+ return S_FALSE;
+}
+
+bool BrowserHelperObject::EnsureTabId() {
+ if (tab_id_ != kInvalidChromeSessionId) {
+ return true;
+ }
+
+ HRESULT hr = chrome_frame_host_->GetSessionId(&tab_id_);
+ DCHECK(SUCCEEDED(hr));
+ if (hr == S_FALSE) {
+ // The server returned false, the session_id isn't available yet
+ return false;
+ }
+
+ // At this point if tab_id_ is still invalid we have a problem.
+ if (tab_id_ == kInvalidChromeSessionId) {
+ // TODO(hansl@google.com): uncomment the following code when the CF change
+ // has landed.
+ //// Something really bad happened.
+ //NOTREACHED();
+ //return false;
+ tab_id_ = reinterpret_cast<int>(tab_window_);
+ }
+
+ CeeeWindowHandle handle = reinterpret_cast<CeeeWindowHandle>(tab_window_);
+ hr = broker_registrar_->SetTabIdForHandle(tab_id_, handle);
+ if (FAILED(hr)) {
+ DCHECK(SUCCEEDED(hr)) << "An error occured when setting the tab_id: " <<
+ com::LogHr(hr);
+ tab_id_ = kInvalidChromeSessionId;
+ return false;
+ }
+ VLOG(2) << "TabId(" << tab_id_ << ") set for Handle(" << handle << ")";
+
+ // Call back all the methods we deferred. In order, please.
+ while (!deferred_tab_id_call_.empty()) {
+ // We pop here so that if an error happens in the call we don't recall the
+ // faulty method. This has the side-effect of losing events.
+ DeferredCallListType::value_type call = deferred_tab_id_call_.front();
+ deferred_tab_id_call_.pop_front();
+ call->Run();
+ delete call;
+ }
+
+ return true;
+}
+
+// Fetch and remembers the tab window we are attached to.
+HRESULT BrowserHelperObject::GetTabWindow(IServiceProvider* service_provider) {
+ CComQIPtr<IOleWindow> ole_window;
+ HRESULT hr = service_provider->QueryService(
+ SID_SShellBrowser, IID_IOleWindow, reinterpret_cast<void**>(&ole_window));
+ DCHECK(SUCCEEDED(hr)) << "Failed to get ole window " << com::LogHr(hr);
+ if (FAILED(hr)) {
+ return hr;
+ }
+
+ hr = ole_window->GetWindow(&tab_window_);
+ DCHECK(SUCCEEDED(hr)) << "Failed to get window from ole window " <<
+ com::LogHr(hr);
+ if (FAILED(hr)) {
+ return hr;
+ }
+ DCHECK(tab_window_ != NULL);
+
+ // Initialize our executor to the right HWND
+ if (executor_ == NULL)
+ return E_POINTER;
+ CComPtr<ICeeeTabExecutor> tab_executor;
+ hr = executor_->QueryInterface(IID_ICeeeTabExecutor,
+ reinterpret_cast<void**>(&tab_executor));
+ if (SUCCEEDED(hr)) {
+ CeeeWindowHandle handle = reinterpret_cast<CeeeWindowHandle>(tab_window_);
+ hr = tab_executor->Initialize(handle);
+ }
+ return hr;
+}
+
+// Connect for notifications.
+HRESULT BrowserHelperObject::ConnectSinks(IServiceProvider* service_provider) {
+ HRESULT hr = service_provider->QueryService(
+ SID_SWebBrowserApp, IID_IWebBrowser2,
+ reinterpret_cast<void**>(&web_browser_));
+ DCHECK(SUCCEEDED(hr)) << "Failed to get web browser " << com::LogHr(hr);
+ if (FAILED(hr)) {
+ return hr;
+ }
+
+ // Start sinking events from the web browser object.
+ hr = DispEventAdvise(web_browser_, &DIID_DWebBrowserEvents2);
+ if (FAILED(hr)) {
+ LOG(ERROR) << "Failed to set event sink " << com::LogHr(hr);
+ return hr;
+ }
+ return hr;
+}
+
+HRESULT BrowserHelperObject::CreateChromeFrameHost() {
+ return ChromeFrameHost::CreateInitializedIID(IID_IChromeFrameHost,
+ &chrome_frame_host_);
+}
+
+HRESULT BrowserHelperObject::FireOnCreatedEvent(BSTR url) {
+ DCHECK(url != NULL);
+ DCHECK(tab_window_ != NULL);
+ DCHECK(!fired_on_created_event_);
+ HRESULT hr = tab_events_funnel().OnCreated(tab_window_, url,
+ lower_bound_ready_state_ == READYSTATE_COMPLETE);
+ fired_on_created_event_ = SUCCEEDED(hr);
+ DCHECK(SUCCEEDED(hr)) << "Failed to fire the tab.onCreated event " <<
+ com::LogHr(hr);
+ return hr;
+}
+
+HRESULT BrowserHelperObject::FireOnRemovedEvent() {
+ DCHECK(tab_window_ != NULL);
+ HRESULT hr =
+ tab_events_funnel().OnRemoved(tab_window_);
+ DCHECK(SUCCEEDED(hr)) << "Failed to fire the tab.onRemoved event " <<
+ com::LogHr(hr);
+ return hr;
+}
+
+HRESULT BrowserHelperObject::FireOnUnmappedEvent() {
+ DCHECK(tab_window_ != NULL);
+ DCHECK(tab_id_ != kInvalidChromeSessionId);
+ HRESULT hr = tab_events_funnel().OnTabUnmapped(tab_window_, tab_id_);
+ DCHECK(SUCCEEDED(hr)) << "Failed to fire the ceee.onTabUnmapped event " <<
+ com::LogHr(hr);
+ return hr;
+}
+
+void BrowserHelperObject::CloseAll(IContentScriptNativeApi* instance) {
+ extension_port_manager_.CloseAll(instance);
+}
+
+HRESULT BrowserHelperObject::OpenChannelToExtension(
+ IContentScriptNativeApi* instance, const std::string& extension,
+ const std::string& channel_name, int cookie) {
+ // TODO(hansl@google.com): separate this method into an event and an Impl.
+ if (EnsureTabId() == false) {
+ deferred_tab_id_call_.push_back(NewRunnableMethod(
+ this, &BrowserHelperObject::OpenChannelToExtension,
+ instance, extension, channel_name, cookie));
+ return S_OK;
+ }
+
+ scoped_ptr<DictionaryValue> tab_info(new DictionaryValue());
+
+ DCHECK(tab_id_ != kInvalidChromeSessionId);
+ tab_info->SetInteger(keys::kIdKey, tab_id_);
+
+ return extension_port_manager_.OpenChannelToExtension(instance,
+ extension,
+ channel_name,
+ tab_info.release(),
+ cookie);
+}
+
+HRESULT BrowserHelperObject::PostMessage(int port_id,
+ const std::string& message) {
+ return extension_port_manager_.PostMessage(port_id, message);
+}
+
+HRESULT BrowserHelperObject::OnCfPrivateMessage(BSTR msg,
+ BSTR origin,
+ BSTR target) {
+ // OnPortMessage uses tab_id_, so we need to check here.
+ // TODO(hansl@google.com): separate this method into an event and an Impl.
+ if (EnsureTabId() == false) {
+ deferred_tab_id_call_.push_back(NewRunnableMethod(
+ this, &BrowserHelperObject::OnCfPrivateMessage,
+ CComBSTR(msg), origin, target));
+ return S_OK;
+ }
+
+ const wchar_t* start = com::ToString(target);
+ const wchar_t* end = start + ::SysStringLen(target);
+ if (LowerCaseEqualsASCII(start, end, ext::kAutomationPortRequestTarget) ||
+ LowerCaseEqualsASCII(start, end, ext::kAutomationPortResponseTarget)) {
+ extension_port_manager_.OnPortMessage(msg);
+ return S_OK;
+ }
+
+ // TODO(siggi@chromium.org): What to do here?
+ LOG(ERROR) << "Unexpected message: '" << msg << "' to invalid target: "
+ << target;
+ return E_UNEXPECTED;
+}
+
+
+STDMETHODIMP_(void) BrowserHelperObject::OnBeforeNavigate2(
+ IDispatch* webbrowser_disp, VARIANT* url, VARIANT* /*flags*/,
+ VARIANT* /*target_frame_name*/, VARIANT* /*post_data*/,
+ VARIANT* /*headers*/, VARIANT_BOOL* /*cancel*/) {
+ if (webbrowser_disp == NULL || url == NULL) {
+ LOG(ERROR) << "OnBeforeNavigate2 got invalid parameter(s)";
+ return;
+ }
+
+ // TODO(hansl@google.com): separate this method into an event and an Impl.
+ if (EnsureTabId() == false) {
+ VARIANT* null_param = NULL;
+ deferred_tab_id_call_.push_back(NewRunnableMethod(
+ this, &BrowserHelperObject::OnBeforeNavigate2,
+ webbrowser_disp, url, null_param, null_param, null_param, null_param,
+ reinterpret_cast<VARIANT_BOOL*>(NULL)));
+ return;
+ }
+
+ CComPtr<IWebBrowser2> webbrowser;
+ HRESULT hr = webbrowser_disp->QueryInterface(&webbrowser);
+ if (FAILED(hr)) {
+ LOG(ERROR) << "OnBeforeNavigate2 failed to QI " << com::LogHr(hr);
+ return;
+ }
+
+ if (V_VT(url) != VT_BSTR) {
+ LOG(ERROR) << "OnBeforeNavigate2 url VT=" << V_VT(url);
+ return;
+ }
+
+ for (std::vector<Sink*>::iterator iter = sinks_.begin();
+ iter != sinks_.end(); ++iter) {
+ (*iter)->OnBeforeNavigate(webbrowser, url->bstrVal);
+ }
+
+ // Notify the infobar executor on the event but only for the main browser.
+ // TODO(vadimb@google.com): Refactor this so that the executor can just
+ // register self as WebBrowserEventsSource::Sink. Right now this is
+ // problematic because the executor is COM object.
+ CComPtr<IWebBrowser2> parent_browser;
+ if (executor_ != NULL && web_browser_ != NULL &&
+ web_browser_.IsEqualObject(webbrowser_disp)) {
+ CComPtr<ICeeeInfobarExecutor> infobar_executor;
+ HRESULT hr = executor_->QueryInterface(IID_ICeeeInfobarExecutor,
+ reinterpret_cast<void**>(&infobar_executor));
+ DCHECK(SUCCEEDED(hr)) << "Failed to get ICeeeInfobarExecutor interface " <<
+ com::LogHr(hr);
+ if (SUCCEEDED(hr)) {
+ infobar_executor->OnTopFrameBeforeNavigate(CComBSTR(url->bstrVal));
+ }
+ }
+}
+
+STDMETHODIMP_(void) BrowserHelperObject::OnDocumentComplete(
+ IDispatch* webbrowser_disp, VARIANT* url) {
+ if (webbrowser_disp == NULL || url == NULL) {
+ LOG(ERROR) << "OnDocumentComplete got invalid parameter(s)";
+ return;
+ }
+
+ // TODO(hansl@google.com): separate this method into an event and an Impl.
+ if (EnsureTabId() == false) {
+ deferred_tab_id_call_.push_back(NewRunnableMethod(
+ this, &BrowserHelperObject::OnDocumentComplete, webbrowser_disp, url));
+ return;
+ }
+
+ CComPtr<IWebBrowser2> webbrowser;
+ HRESULT hr = webbrowser_disp->QueryInterface(&webbrowser);
+ if (FAILED(hr)) {
+ LOG(ERROR) << "OnDocumentComplete failed to QI " << com::LogHr(hr);
+ return;
+ }
+
+ if (V_VT(url) != VT_BSTR) {
+ LOG(ERROR) << "OnDocumentComplete url VT=" << V_VT(url);
+ return;
+ }
+
+ for (std::vector<Sink*>::iterator iter = sinks_.begin();
+ iter != sinks_.end(); ++iter) {
+ (*iter)->OnDocumentComplete(webbrowser, url->bstrVal);
+ }
+}
+
+STDMETHODIMP_(void) BrowserHelperObject::OnNavigateComplete2(
+ IDispatch* webbrowser_disp, VARIANT* url) {
+ if (webbrowser_disp == NULL || url == NULL) {
+ LOG(ERROR) << "OnNavigateComplete2 got invalid parameter(s)";
+ return;
+ }
+
+ // TODO(hansl@google.com): separate this method into an event and an Impl.
+ if (EnsureTabId() == false) {
+ deferred_tab_id_call_.push_back(NewRunnableMethod(
+ this, &BrowserHelperObject::OnNavigateComplete2, webbrowser_disp, url));
+ return;
+ }
+
+ CComPtr<IWebBrowser2> webbrowser;
+ HRESULT hr = webbrowser_disp->QueryInterface(&webbrowser);
+ if (FAILED(hr)) {
+ LOG(ERROR) << "OnNavigateComplete2 failed to QI " << com::LogHr(hr);
+ return;
+ }
+
+ if (V_VT(url) != VT_BSTR) {
+ LOG(ERROR) << "OnNavigateComplete2 url VT=" << V_VT(url);
+ return;
+ }
+
+ HandleNavigateComplete(webbrowser, url->bstrVal);
+
+ for (std::vector<Sink*>::iterator iter = sinks_.begin();
+ iter != sinks_.end(); ++iter) {
+ (*iter)->OnNavigateComplete(webbrowser, url->bstrVal);
+ }
+}
+
+STDMETHODIMP_(void) BrowserHelperObject::OnNavigateError(
+ IDispatch* webbrowser_disp, VARIANT* url, VARIANT* /*target_frame_name*/,
+ VARIANT* status_code, VARIANT_BOOL* /*cancel*/) {
+ if (webbrowser_disp == NULL || url == NULL || status_code == NULL) {
+ LOG(ERROR) << "OnNavigateError got invalid parameter(s)";
+ return;
+ }
+
+ // TODO(hansl@google.com): separate this method into an event and an Impl.
+ if (EnsureTabId() == false) {
+ VARIANT* null_param = NULL;
+ deferred_tab_id_call_.push_back(NewRunnableMethod(
+ this, &BrowserHelperObject::OnNavigateError,
+ webbrowser_disp, url, null_param, status_code,
+ reinterpret_cast<VARIANT_BOOL*>(NULL)));
+ return;
+ }
+
+ CComPtr<IWebBrowser2> webbrowser;
+ HRESULT hr = webbrowser_disp->QueryInterface(&webbrowser);
+ if (FAILED(hr)) {
+ LOG(ERROR) << "OnNavigateError failed to QI " << com::LogHr(hr);
+ return;
+ }
+
+ if (V_VT(url) != VT_BSTR) {
+ LOG(ERROR) << "OnNavigateError url VT=" << V_VT(url);
+ return;
+ }
+
+ if (V_VT(status_code) != VT_I4) {
+ LOG(ERROR) << "OnNavigateError status_code VT=" << V_VT(status_code);
+ return;
+ }
+
+ for (std::vector<Sink*>::iterator iter = sinks_.begin();
+ iter != sinks_.end(); ++iter) {
+ (*iter)->OnNavigateError(webbrowser, url->bstrVal, status_code->lVal);
+ }
+}
+
+STDMETHODIMP_(void) BrowserHelperObject::OnNewWindow2(
+ IDispatch** /*webbrowser_disp*/, VARIANT_BOOL* /*cancel*/) {
+ // When a new window/tab is created, IE7 or later version of IE will also
+ // fire NewWindow3 event, which extends NewWindow2 with additional
+ // information. As a result, we ignore NewWindow2 event in that case.
+ if (ie7_or_later_)
+ return;
+
+ CComBSTR url_context(L"");
+ CComBSTR url(L"");
+ for (std::vector<Sink*>::iterator iter = sinks_.begin();
+ iter != sinks_.end(); ++iter) {
+ (*iter)->OnNewWindow(url_context, url);
+ }
+}
+
+STDMETHODIMP_(void) BrowserHelperObject::OnNewWindow3(
+ IDispatch** /*webbrowser_disp*/, VARIANT_BOOL* /*cancel*/, DWORD /*flags*/,
+ BSTR url_context, BSTR url) {
+ // IE6 uses NewWindow2 event.
+ if (!ie7_or_later_)
+ return;
+
+ for (std::vector<Sink*>::iterator iter = sinks_.begin();
+ iter != sinks_.end(); ++iter) {
+ (*iter)->OnNewWindow(url_context, url);
+ }
+}
+
+bool BrowserHelperObject::BrowserContainsChromeFrame(IWebBrowser2* browser) {
+ CComPtr<IDispatch> document_disp;
+ HRESULT hr = browser->get_Document(&document_disp);
+ if (FAILED(hr)) {
+ // This should never happen.
+ NOTREACHED() << "IWebBrowser2::get_Document failed " << com::LogHr(hr);
+ return false;
+ }
+
+ CComQIPtr<IPersist> document_persist(document_disp);
+ if (document_persist != NULL) {
+ CLSID clsid = {};
+ hr = document_persist->GetClassID(&clsid);
+ if (SUCCEEDED(hr) && clsid == CLSID_ChromeFrame) {
+ return true;
+ }
+ }
+ return false;
+}
+
+HRESULT BrowserHelperObject::AttachBrowserHandler(IWebBrowser2* webbrowser,
+ IFrameEventHandler** handler) {
+ // We're not attached yet, figure out whether we're attaching
+ // to the top-level browser or a frame, and looukup the parentage
+ // in the latter case.
+ CComPtr<IWebBrowser2> parent_browser;
+ HRESULT hr = S_OK;
+ bool is_top_frame = web_browser_.IsEqualObject(webbrowser);
+ if (!is_top_frame) {
+ // Not the top-level browser, so find the parent. If this fails,
+ // we assume webbrowser is orphaned, and will not attach to it.
+ // This can happen when a FRAME/IFRAME is removed from the DOM tree.
+ hr = GetParentBrowser(webbrowser, &parent_browser);
+ if (FAILED(hr))
+ LOG(WARNING) << "Failed to get parent browser " << com::LogHr(hr);
+ }
+
+ // Attempt to attach to the web browser.
+ if (SUCCEEDED(hr)) {
+ hr = CreateFrameEventHandler(webbrowser, parent_browser, handler);
+ bool document_is_mshtml = SUCCEEDED(hr);
+ DCHECK(SUCCEEDED(hr) || hr == E_DOCUMENT_NOT_MSHTML) <<
+ "Unexpected error creating a frame handler " << com::LogHr(hr);
+
+ if (is_top_frame) {
+ // Check if it is a chrome frame.
+ bool is_chrome_frame = BrowserContainsChromeFrame(webbrowser);
+
+ if (is_chrome_frame) {
+ fired_on_created_event_ = true;
+ full_tab_chrome_frame_ = true;
+ // Send a tabs.onRemoved event to make the extension believe the tab is
+ // dead.
+ hr = FireOnRemovedEvent();
+ DCHECK(SUCCEEDED(hr));
+ } else if (document_is_mshtml) {
+ // We know it's MSHTML. We check if the last page was chrome frame.
+ if (full_tab_chrome_frame_) {
+ // This will send a tabs.onCreated event later to balance the
+ // onRemoved event above.
+ fired_on_created_event_ = false;
+ full_tab_chrome_frame_ = false;
+ }
+ }
+ }
+ }
+
+ return hr;
+}
+
+void BrowserHelperObject::HandleNavigateComplete(IWebBrowser2* webbrowser,
+ BSTR url) {
+ // If the top-level document or a sub-frame is navigated, we'll already
+ // be attached to the browser in question, so don't reattach.
+ HRESULT hr = S_OK;
+ CComPtr<IFrameEventHandler> handler;
+ if (FAILED(GetBrowserHandler(webbrowser, &handler))) {
+ hr = AttachBrowserHandler(webbrowser, &handler);
+
+ DCHECK(SUCCEEDED(hr)) << "Failed to attach ourselves to the web browser " <<
+ com::LogHr(hr);
+ }
+
+ bool is_hash_change = false;
+ if (handler) {
+ // Find out if this is a hash change.
+ CComBSTR prev_url;
+ handler->GetUrl(&prev_url);
+ is_hash_change = IsHashChange(url, prev_url);
+
+ // Notify the handler of the current URL.
+ hr = handler->SetUrl(url);
+ DCHECK(SUCCEEDED(hr)) << "Failed setting the handler URL " <<
+ com::LogHr(hr);
+ }
+
+ // SetUrl returns S_FALSE if the URL didn't change.
+ if (SUCCEEDED(hr) && hr != S_FALSE) {
+ // And we should only fire events for URL changes on the top frame.
+ if (web_browser_.IsEqualObject(webbrowser)) {
+ // We can assume that we are NOT in a complete state when we get
+ // a navigation complete.
+ lower_bound_ready_state_ = READYSTATE_UNINITIALIZED;
+
+ // At this point, we should have all the tab windows created,
+ // including the proper lower bound ready state set just before,
+ // so setup the tab info if it has not been set yet.
+ if (!fired_on_created_event_) {
+ hr = FireOnCreatedEvent(url);
+ DCHECK(SUCCEEDED(hr)) << "Failed to fire tab created event " <<
+ com::LogHr(hr);
+ }
+
+ // The onUpdate event usually gets fired after the onCreated,
+ // which is fired from FireOnCreatedEvent above.
+ DCHECK(tab_window_ != NULL);
+ hr = tab_events_funnel().OnUpdated(tab_window_, url,
+ lower_bound_ready_state_);
+ DCHECK(SUCCEEDED(hr)) << "Failed to fire tab updated event " <<
+ com::LogHr(hr);
+
+ // If this is a hash change, we manually fire the OnUpdated for the
+ // complete ready state as we don't receive ready state notifications
+ // for hash changes.
+ if (is_hash_change) {
+ hr = tab_events_funnel().OnUpdated(tab_window_, url,
+ READYSTATE_COMPLETE);
+ DCHECK(SUCCEEDED(hr)) << "Failed to fire tab updated event " <<
+ com::LogHr(hr);
+ }
+ }
+ }
+}
+
+HRESULT BrowserHelperObject::CreateFrameEventHandler(
+ IWebBrowser2* browser, IWebBrowser2* parent_browser,
+ IFrameEventHandler** handler) {
+ return FrameEventHandler::CreateInitializedIID(
+ browser, parent_browser, this, IID_IFrameEventHandler, handler);
+}
+
+HRESULT BrowserHelperObject::AttachBrowser(IWebBrowser2* browser,
+ IWebBrowser2* parent_browser,
+ IFrameEventHandler* handler) {
+ DCHECK(browser);
+ DCHECK(handler);
+ // Get the identity unknown of the browser.
+ CComPtr<IUnknown> browser_identity;
+ HRESULT hr = browser->QueryInterface(&browser_identity);
+ DCHECK(SUCCEEDED(hr)) << "QueryInterface for IUnknown failed!!!" <<
+ com::LogHr(hr);
+ if (FAILED(hr))
+ return hr;
+
+ std::pair<BrowserHandlerMap::iterator, bool> inserted =
+ browsers_.insert(std::make_pair(browser_identity, handler));
+ // We shouldn't have a previous entry for any inserted browser.
+ // map::insert().second is true iff an item was inserted.
+ DCHECK(inserted.second);
+
+ // Now try and find a parent event handler for this browser.
+ // If we have a parent browser, locate its handler
+ // and notify it of the association.
+ if (parent_browser) {
+ CComPtr<IFrameEventHandler> parent_handler;
+ hr = GetBrowserHandler(parent_browser, &parent_handler);
+
+ // Notify the parent of its new underling.
+ if (parent_handler != NULL) {
+ hr = parent_handler->AddSubHandler(handler);
+ DCHECK(SUCCEEDED(hr)) << "AddSubHandler()" << com::LogHr(hr);
+ } else {
+ LOG(INFO) << "Received an orphan handler: " << std::hex << handler <<
+ com::LogHr(hr);
+ // Lets see if we can find an ancestor up the chain of parent browsers
+ // that we could connect to our existing hierarchy of handlers.
+ CComQIPtr<IWebBrowser2> grand_parent_browser;
+ hr = GetParentBrowser(parent_browser, &grand_parent_browser);
+ if (FAILED(hr))
+ LOG(WARNING) << "Failed to get parent browser " << com::LogHr(hr);
+ bool valid_grand_parent = (grand_parent_browser != NULL &&
+ !grand_parent_browser.IsEqualObject(parent_browser));
+ DCHECK(valid_grand_parent) <<
+ "Orphan handler without a valid grand parent!";
+ LOG_IF(ERROR, !valid_grand_parent) << "Orphan handler: " << std::hex <<
+ handler << ", with parent browser: " << std::hex << parent_browser;
+ if (grand_parent_browser != NULL &&
+ !grand_parent_browser.IsEqualObject(parent_browser)) {
+ DCHECK(!web_browser_.IsEqualObject(parent_browser));
+ // We have a grand parent IWebBrowser2, so create a handler for the
+ // parent we were given that doesn't have a handler already.
+ CComBSTR parent_browser_url;
+ parent_browser->get_LocationURL(&parent_browser_url);
+ DLOG(INFO) << "Creating handler for parent browser: " << std::hex <<
+ parent_browser << ", at URL: " << parent_browser_url;
+ hr = CreateFrameEventHandler(parent_browser, grand_parent_browser,
+ &parent_handler);
+ // If we have a handler for the child, we must be able to create one for
+ // the parent... And CreateFrameEventHandler should have attached it
+ // to the parent by calling us again via IFrameEventHandler::Init...
+ DCHECK(SUCCEEDED(hr) && parent_handler != NULL) << com::LogHr(hr);
+ if (FAILED(hr) || parent_handler == NULL)
+ return hr;
+
+ // When we create a handler, we must set its URL.
+ hr = parent_handler->SetUrl(parent_browser_url);
+ DCHECK(SUCCEEDED(hr)) << "Handler::SetUrl()" << com::LogHr(hr);
+ if (FAILED(hr))
+ return hr;
+
+ // And now that we have a fully created parent handler, we can add
+ // the handler that looked orphan, to its newly created parent.
+ hr = parent_handler->AddSubHandler(handler);
+ DCHECK(SUCCEEDED(hr)) << "AddSubHandler()" << com::LogHr(hr);
+ } else {
+ // No grand parent for the orphan handler?
+ return E_UNEXPECTED;
+ }
+ }
+ }
+
+ if (inserted.second)
+ return S_OK;
+ else
+ return E_FAIL;
+}
+
+HRESULT BrowserHelperObject::DetachBrowser(IWebBrowser2* browser,
+ IWebBrowser2* parent_browser,
+ IFrameEventHandler* handler) {
+ // Get the identity unknown of the browser.
+ CComPtr<IUnknown> browser_identity;
+ HRESULT hr = browser->QueryInterface(&browser_identity);
+ DCHECK(SUCCEEDED(hr)) << "QueryInterface for IUnknown failed!!!" <<
+ com::LogHr(hr);
+ if (FAILED(hr))
+ return hr;
+
+ // If we have a parent browser, locate its handler
+ // and notify it of the disassociation.
+ if (parent_browser) {
+ CComPtr<IFrameEventHandler> parent_handler;
+
+ hr = GetBrowserHandler(parent_browser, &parent_handler);
+ LOG_IF(WARNING, FAILED(hr) || parent_handler == NULL) << "No Parent " <<
+ "Handler" << com::LogHr(hr);
+
+ // Notify the parent of its underling removal.
+ if (parent_handler != NULL) {
+ hr = parent_handler->RemoveSubHandler(handler);
+ DCHECK(SUCCEEDED(hr)) << "RemoveSubHandler" << com::LogHr(hr);
+ }
+ }
+
+ BrowserHandlerMap::iterator it = browsers_.find(browser_identity);
+ DCHECK(it != browsers_.end());
+ if (it == browsers_.end())
+ return E_FAIL;
+
+ browsers_.erase(it);
+ return S_OK;
+}
+
+HRESULT BrowserHelperObject::GetTopLevelBrowser(IWebBrowser2** browser) {
+ DCHECK(browser != NULL);
+ return web_browser_.CopyTo(browser);
+}
+
+HRESULT BrowserHelperObject::OnReadyStateChanged(READYSTATE ready_state) {
+ // We make sure to always keep the lowest ready state of all the handlers
+ // and only fire an event when we get from not completed to completed or
+ // vice versa.
+ READYSTATE new_lowest_ready_state = READYSTATE_COMPLETE;
+ BrowserHandlerMap::const_iterator it = browsers_.begin();
+ for (; it != browsers_.end(); ++it) {
+ DCHECK(it->second.m_T != NULL);
+ READYSTATE this_ready_state = it->second.m_T->GetReadyState();
+ if (this_ready_state < new_lowest_ready_state) {
+ new_lowest_ready_state = this_ready_state;
+ }
+ }
+
+ return HandleReadyStateChanged(lower_bound_ready_state_,
+ new_lowest_ready_state);
+}
+
+HRESULT BrowserHelperObject::GetReadyState(READYSTATE* ready_state) {
+ DCHECK(ready_state != NULL);
+ if (ready_state != NULL) {
+ *ready_state = lower_bound_ready_state_;
+ return S_OK;
+ } else {
+ return E_POINTER;
+ }
+}
+
+HRESULT BrowserHelperObject::GetExtensionId(std::wstring* extension_id) {
+ *extension_id = extension_id_;
+ return S_OK;
+}
+
+HRESULT BrowserHelperObject::GetExtensionPath(std::wstring* extension_path) {
+ *extension_path = extension_base_dir_;
+ return S_OK;
+}
+
+HRESULT BrowserHelperObject::GetExtensionPortMessagingProvider(
+ IExtensionPortMessagingProvider** messaging_provider) {
+ GetUnknown()->AddRef();
+ *messaging_provider = this;
+ return S_OK;
+}
+
+HRESULT BrowserHelperObject::InsertCode(BSTR code, BSTR file, BOOL all_frames,
+ CeeeTabCodeType type) {
+ // TODO(hansl@google.com): separate this method into an event and an Impl.
+ if (EnsureTabId() == false) {
+ deferred_tab_id_call_.push_back(NewRunnableMethod(
+ this, &BrowserHelperObject::InsertCode,
+ code, file, all_frames, type));
+ return S_OK;
+ }
+
+ // If all_frames is false, we execute only in the top level frame. Otherwise
+ // we do the top level frame as well as all the inner frames.
+ if (all_frames) {
+ // Make a copy of the browser handler map since it could potentially be
+ // modified in the loop.
+ BrowserHandlerMap browsers_copy(browsers_.begin(), browsers_.end());
+ BrowserHandlerMap::const_iterator it = browsers_copy.begin();
+ for (; it != browsers_copy.end(); ++it) {
+ DCHECK(it->second.m_T != NULL);
+ if (it->second.m_T != NULL) {
+ HRESULT hr = it->second.m_T->InsertCode(code, file, type);
+ DCHECK(SUCCEEDED(hr)) << "IFrameEventHandler::InsertCode()" <<
+ com::LogHr(hr);
+ }
+ }
+ } else if (web_browser_ != NULL) {
+ CComPtr<IFrameEventHandler> handler;
+ HRESULT hr = GetBrowserHandler(web_browser_, &handler);
+ DCHECK(SUCCEEDED(hr) && handler != NULL) << com::LogHr(hr);
+
+ if (handler != NULL) {
+ hr = handler->InsertCode(code, file, type);
+ // TODO(joi@chromium.org) We don't DCHECK for now, because Chrome may have
+ // multiple extensions loaded whereas CEEE only knows about a single
+ // extension. Clean this up once we support multiple extensions.
+ }
+ }
+
+ return S_OK;
+}
+
+HRESULT BrowserHelperObject::HandleReadyStateChanged(READYSTATE old_state,
+ READYSTATE new_state) {
+ if (old_state == new_state)
+ return S_OK;
+
+ // Remember the new lowest ready state as our current one.
+ lower_bound_ready_state_ = new_state;
+
+ if (old_state == READYSTATE_COMPLETE || new_state == READYSTATE_COMPLETE) {
+ // The new ready state got us to or away from complete, so fire the event.
+ DCHECK(tab_window_ != NULL);
+ return tab_events_funnel().OnUpdated(tab_window_, NULL, new_state);
+ }
+ return S_OK;
+}
+
+HRESULT BrowserHelperObject::GetMatchingUserScriptsCssContent(
+ const GURL& url, bool require_all_frames, std::string* css_content) {
+ return librarian_.GetMatchingUserScriptsCssContent(url, require_all_frames,
+ css_content);
+}
+
+HRESULT BrowserHelperObject::GetMatchingUserScriptsJsContent(
+ const GURL& url, UserScript::RunLocation location, bool require_all_frames,
+ UserScriptsLibrarian::JsFileList* js_file_list) {
+ return librarian_.GetMatchingUserScriptsJsContent(url, location,
+ require_all_frames,
+ js_file_list);
+}
+
+HRESULT BrowserHelperObject::GetBrowserHandler(IWebBrowser2* webbrowser,
+ IFrameEventHandler** handler) {
+ DCHECK(webbrowser != NULL);
+ DCHECK(handler != NULL && *handler == NULL);
+ CComPtr<IUnknown> browser_identity;
+ HRESULT hr = webbrowser->QueryInterface(&browser_identity);
+ DCHECK(SUCCEEDED(hr)) << com::LogHr(hr);
+
+ BrowserHandlerMap::iterator found(browsers_.find(browser_identity));
+ if (found != browsers_.end()) {
+ found->second.m_T.CopyTo(handler);
+ return S_OK;
+ }
+
+ return E_FAIL;
+}
+
+void BrowserHelperObject::LoadManifestFile(const std::wstring& base_dir) {
+ // TODO(siggi@chromium.org): Generalize this to the possibility of
+ // multiple extensions.
+ FilePath extension_path(base_dir);
+ if (extension_path.empty()) {
+ // expected case if no extensions registered/found
+ return;
+ }
+
+ extension_base_dir_ = base_dir;
+
+ ExtensionManifest manifest;
+ if (SUCCEEDED(manifest.ReadManifestFile(extension_path, true))) {
+ extension_id_ = UTF8ToWide(manifest.extension_id());
+ librarian_.AddUserScripts(manifest.content_scripts());
+ }
+}
+
+void BrowserHelperObject::OnFinalMessage(HWND window) {
+ GetUnknown()->Release();
+}
+
+LRESULT BrowserHelperObject::OnCreate(LPCREATESTRUCT lpCreateStruct) {
+ // Grab a self-reference.
+ GetUnknown()->AddRef();
+
+ return 0;
+}
+
+bool BrowserHelperObject::IsHashChange(BSTR url1, BSTR url2) {
+ if (::SysStringLen(url1) == 0 || ::SysStringLen(url2) == 0) {
+ return false;
+ }
+
+ GURL gurl1(url1);
+ GURL gurl2(url2);
+
+ // The entire URL should be the same except for the hash.
+ // We could compare gurl1.ref() to gurl2.ref() on the last step, but this
+ // doesn't differentiate between URLs like http://a/ and http://a/#.
+ return gurl1.scheme() == gurl2.scheme() &&
+ gurl1.username() == gurl2.username() &&
+ gurl1.password() == gurl2.password() &&
+ gurl1.host() == gurl2.host() &&
+ gurl1.port() == gurl2.port() &&
+ gurl1.path() == gurl2.path() &&
+ gurl1.query() == gurl2.query() &&
+ gurl1 != gurl2;
+}
+
+void BrowserHelperObject::RegisterSink(Sink* sink) {
+ DCHECK(thread_id_ == ::GetCurrentThreadId());
+
+ if (sink == NULL)
+ return;
+
+ // Although the registration logic guarantees that the same sink won't be
+ // registered twice, we prefer to use std::vector rather than std::set.
+ // Using std::vector, we could keep "first-registered-first-notified".
+ //
+ // With std::set, however, the notifying order can only be decided at
+ // run-time. Moreover, in different runs, the notifying order may be
+ // different, since the value of the pointer to each sink is changing. The may
+ // cause unstable behavior and hard-to-debug issues.
+ std::vector<Sink*>::iterator iter = std::find(sinks_.begin(), sinks_.end(),
+ sink);
+ if (iter == sinks_.end())
+ sinks_.push_back(sink);
+}
+
+void BrowserHelperObject::UnregisterSink(Sink* sink) {
+ DCHECK(thread_id_ == ::GetCurrentThreadId());
+
+ if (sink == NULL)
+ return;
+
+ std::vector<Sink*>::iterator iter = std::find(sinks_.begin(), sinks_.end(),
+ sink);
+ if (iter != sinks_.end())
+ sinks_.erase(iter);
+}
diff --git a/ceee/ie/plugin/bho/browser_helper_object.h b/ceee/ie/plugin/bho/browser_helper_object.h
new file mode 100644
index 0000000..bf4afb9
--- /dev/null
+++ b/ceee/ie/plugin/bho/browser_helper_object.h
@@ -0,0 +1,345 @@
+// 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.
+//
+// IE browser helper object implementation.
+#ifndef CEEE_IE_PLUGIN_BHO_BROWSER_HELPER_OBJECT_H_
+#define CEEE_IE_PLUGIN_BHO_BROWSER_HELPER_OBJECT_H_
+
+#include <atlbase.h>
+#include <atlcom.h>
+#include <mshtml.h> // Needed for exdisp.h
+#include <exdisp.h>
+#include <exdispid.h>
+#include <map>
+#include <set>
+#include <string>
+#include <vector>
+
+#include "base/scoped_ptr.h"
+#include "base/task.h"
+#include "ceee/ie/plugin/bho/tab_events_funnel.h"
+#include "ceee/ie/common/chrome_frame_host.h"
+#include "ceee/ie/plugin/bho/frame_event_handler.h"
+#include "ceee/ie/plugin/bho/extension_port_manager.h"
+#include "ceee/ie/plugin/bho/tool_band_visibility.h"
+#include "ceee/ie/plugin/bho/web_browser_events_source.h"
+#include "ceee/ie/plugin/bho/web_progress_notifier.h"
+#include "ceee/ie/plugin/scripting/userscripts_librarian.h"
+#include "ceee/ie/plugin/scripting/content_script_native_api.h"
+#include "ceee/ie/plugin/toolband/resource.h"
+#include "broker_lib.h" // NOLINT
+#include "toolband.h" // NOLINT
+
+// Implementation of an IE browser helper object.
+class ATL_NO_VTABLE BrowserHelperObject
+ : public CComObjectRootEx<CComSingleThreadModel>,
+ public CComCoClass<BrowserHelperObject, &CLSID_BrowserHelperObject>,
+ public IObjectWithSiteImpl<BrowserHelperObject>,
+ public IDispEventSimpleImpl<0,
+ BrowserHelperObject,
+ &DIID_DWebBrowserEvents2>,
+ public IFrameEventHandlerHost,
+ public IExtensionPortMessagingProvider,
+ public IChromeFrameHostEvents,
+ public ToolBandVisibility,
+ public WebBrowserEventsSource {
+ public:
+ DECLARE_REGISTRY_RESOURCEID(IDR_BROWSERHELPEROBJECT)
+ DECLARE_NOT_AGGREGATABLE(BrowserHelperObject)
+
+ BEGIN_COM_MAP(BrowserHelperObject)
+ COM_INTERFACE_ENTRY(IObjectWithSite)
+ COM_INTERFACE_ENTRY_IID(IID_IFrameEventHandlerHost, IFrameEventHandlerHost)
+ END_COM_MAP()
+
+ DECLARE_PROTECT_FINAL_CONSTRUCT()
+
+ BEGIN_SINK_MAP(BrowserHelperObject)
+ SINK_ENTRY_INFO(0, DIID_DWebBrowserEvents2, DISPID_BEFORENAVIGATE2,
+ OnBeforeNavigate2,
+ &handler_type_idispatch_5variantptr_boolptr_)
+ SINK_ENTRY_INFO(0, DIID_DWebBrowserEvents2, DISPID_DOCUMENTCOMPLETE,
+ OnDocumentComplete, &handler_type_idispatch_variantptr_)
+ SINK_ENTRY_INFO(0, DIID_DWebBrowserEvents2, DISPID_NAVIGATECOMPLETE2,
+ OnNavigateComplete2, &handler_type_idispatch_variantptr_)
+ SINK_ENTRY_INFO(0, DIID_DWebBrowserEvents2, DISPID_NAVIGATEERROR,
+ OnNavigateError,
+ &handler_type_idispatch_3variantptr_boolptr_)
+ SINK_ENTRY_INFO(0, DIID_DWebBrowserEvents2, DISPID_NEWWINDOW2,
+ OnNewWindow2, &handler_type_idispatchptr_boolptr_)
+ SINK_ENTRY_INFO(0, DIID_DWebBrowserEvents2, DISPID_NEWWINDOW3,
+ OnNewWindow3,
+ &handler_type_idispatchptr_boolptr_dword_2bstr_)
+ END_SINK_MAP()
+
+ BrowserHelperObject();
+ ~BrowserHelperObject();
+
+ HRESULT FinalConstruct();
+ void FinalRelease();
+
+ // @name IObjectWithSite override.
+ STDMETHOD(SetSite)(IUnknown* site);
+
+ // @name IExtensionPortMessagingProvider implementation
+ // @{
+ virtual void CloseAll(IContentScriptNativeApi* instance);
+ virtual HRESULT OpenChannelToExtension(IContentScriptNativeApi* instance,
+ const std::string& extension,
+ const std::string& channel_name,
+ int cookie);
+ virtual HRESULT PostMessage(int port_id, const std::string& message);
+ // @}
+
+ // @name IChromeFrameHostEvents implementation
+ virtual HRESULT OnCfReadyStateChanged(LONG state);
+ virtual HRESULT OnCfPrivateMessage(BSTR msg, BSTR origin, BSTR target);
+ virtual HRESULT OnCfExtensionReady(BSTR path, int response);
+ virtual HRESULT OnCfGetEnabledExtensionsComplete(
+ SAFEARRAY* tab_delimited_paths);
+ virtual HRESULT OnCfGetExtensionApisToAutomate(BSTR* functions_enabled);
+ virtual HRESULT OnCfChannelError();
+
+ // @name WebBrowser event handlers
+ // @{
+ STDMETHOD_(void, OnBeforeNavigate2)(IDispatch* webbrowser_disp, VARIANT* url,
+ VARIANT* flags,
+ VARIANT* target_frame_name,
+ VARIANT* post_data, VARIANT* headers,
+ VARIANT_BOOL* cancel);
+ STDMETHOD_(void, OnDocumentComplete)(IDispatch* webbrowser_disp,
+ VARIANT* url);
+ STDMETHOD_(void, OnNavigateComplete2)(IDispatch* webbrowser_disp,
+ VARIANT* url);
+ STDMETHOD_(void, OnNavigateError)(IDispatch* webbrowser_disp, VARIANT* url,
+ VARIANT* target_frame_name,
+ VARIANT* status_code, VARIANT_BOOL* cancel);
+ STDMETHOD_(void, OnNewWindow2)(IDispatch** webbrowser_disp,
+ VARIANT_BOOL* cancel);
+ STDMETHOD_(void, OnNewWindow3)(IDispatch** webbrowser_disp,
+ VARIANT_BOOL* cancel, DWORD flags,
+ BSTR url_context, BSTR url);
+ // @}
+
+ // @name IFrameEventHandlerHost
+ // @{
+ virtual HRESULT AttachBrowser(IWebBrowser2* browser,
+ IWebBrowser2* parent_browser,
+ IFrameEventHandler* handler);
+ virtual HRESULT DetachBrowser(IWebBrowser2* browser,
+ IWebBrowser2* parent_browser,
+ IFrameEventHandler* handler);
+ virtual HRESULT GetTopLevelBrowser(IWebBrowser2** browser);
+ virtual HRESULT GetMatchingUserScriptsCssContent(
+ const GURL& url, bool require_all_frames, std::string* css_content);
+ virtual HRESULT GetMatchingUserScriptsJsContent(
+ const GURL& url, UserScript::RunLocation location,
+ bool require_all_frames,
+ UserScriptsLibrarian::JsFileList* js_file_list);
+ virtual HRESULT OnReadyStateChanged(READYSTATE ready_state);
+ virtual HRESULT GetReadyState(READYSTATE* ready_state);
+ virtual HRESULT GetExtensionId(std::wstring* extension_id);
+ virtual HRESULT GetExtensionPath(std::wstring* extension_path);
+ virtual HRESULT GetExtensionPortMessagingProvider(
+ IExtensionPortMessagingProvider** messaging_provider);
+ virtual HRESULT InsertCode(BSTR code, BSTR file, BOOL all_frames,
+ CeeeTabCodeType type);
+ // @}
+
+ // @name WebBrowserEventsSource
+ // @{
+ // Both RegisterSink and UnregisterSink are supposed to be called from the
+ // main browser thread of the tab to which this BHO is attached. Sinks will
+ // receive notifications on the same thread.
+ virtual void RegisterSink(Sink* sink);
+ virtual void UnregisterSink(Sink* sink);
+ // @}
+
+ protected:
+ // Finds the handler attached to webbrowser.
+ // @returns S_OK if handler is found.
+ HRESULT GetBrowserHandler(IWebBrowser2* webbrowser,
+ IFrameEventHandler** handler);
+
+ virtual void HandleNavigateComplete(IWebBrowser2* webbrowser, BSTR url);
+ virtual HRESULT HandleReadyStateChanged(READYSTATE old_state,
+ READYSTATE new_state);
+
+ // Unit testing seems to create the frame event handler.
+ virtual HRESULT CreateFrameEventHandler(IWebBrowser2* browser,
+ IWebBrowser2* parent_browser,
+ IFrameEventHandler** handler);
+
+ // Unit testing seems to get the parent of a browser.
+ virtual HRESULT GetParentBrowser(IWebBrowser2* browser,
+ IWebBrowser2** parent_browser);
+
+ // Unit testing seems to create the broker registrar.
+ virtual HRESULT GetBrokerRegistrar(ICeeeBrokerRegistrar** broker);
+
+ // Unit testing seems to create an executor.
+ virtual HRESULT CreateExecutor(IUnknown** executor);
+
+ // Unit testing seems to create a WebProgressNotifier instance.
+ virtual WebProgressNotifier* CreateWebProgressNotifier();
+
+ // Initializes the BHO to the given site.
+ // Called from SetSite.
+ HRESULT Initialize(IUnknown* site);
+
+ // Tears down an initialized bho.
+ // Called from SetSite.
+ HRESULT TearDown();
+
+ // Creates and initializes the chrome frame host.
+ HRESULT InitializeChromeFrameHost();
+
+ // Fetch and remembers the tab window we are attached to.
+ // Virtual for testing purposes.
+ virtual HRESULT GetTabWindow(IServiceProvider* service_provider);
+
+ // Connect for notifications.
+ HRESULT ConnectSinks(IServiceProvider* service_provider);
+
+ // Isolate the creation of the host so we can overload it to mock
+ // the Chrome Frame Host in our tests.
+ virtual HRESULT CreateChromeFrameHost();
+
+ // Accessor so that we can mock it in unit tests.
+ virtual TabEventsFunnel& tab_events_funnel() { return tab_events_funnel_; }
+
+ // Fires the tab.onCreated event via the tab event funnel.
+ virtual HRESULT FireOnCreatedEvent(BSTR url);
+
+ // Fires the tab.onRemoved event via the tab event funnel.
+ virtual HRESULT FireOnRemovedEvent();
+
+ // Fires the private message to unmap a tab to its BHO.
+ virtual HRESULT FireOnUnmappedEvent();
+
+ // Loads our manifest and initialize our librarian.
+ virtual void LoadManifestFile(const std::wstring& base_dir);
+
+ // Called when we know the base directory of our extension.
+ void StartExtension(const wchar_t* base_dir);
+
+ // Our ToolBandVisibility window maintains a refcount on us for the duration
+ // of its lifetime. The self-reference is managed with these two methods.
+ virtual void OnFinalMessage(HWND window);
+ virtual LRESULT OnCreate(LPCREATESTRUCT lpCreateStruct);
+
+ // Compares two URLs and returns whether they represent a hash change.
+ virtual bool IsHashChange(BSTR url1, BSTR url2);
+
+ // Ensure that the tab ID is correct. On the first time it's set, it will
+ // call all deferred methods added to deferred_tab_id_call_.
+ // This method should be called by every method that send a message or use
+ // the tab event funnel, as they need the tab_id to be mapped.
+ // If this method returns false, the caller should defer itself using the
+ // deferred_tab_id_call_ list.
+ virtual bool EnsureTabId();
+
+ // Returns true if the browser interface passed in contains a full tab
+ // chrome frame.
+ virtual bool BrowserContainsChromeFrame(IWebBrowser2* browser);
+
+ // Attach ourselves and the event handler to the browser, and launches the
+ // right events when going to and from a Full Tab Chrome Frame.
+ virtual HRESULT AttachBrowserHandler(IWebBrowser2* webbrowser,
+ IFrameEventHandler** handler);
+
+ // Function info objects describing our message handlers.
+ // Effectively const but can't make const because of silly ATL macro problem.
+ static _ATL_FUNC_INFO handler_type_idispatch_5variantptr_boolptr_;
+ static _ATL_FUNC_INFO handler_type_idispatch_variantptr_;
+ static _ATL_FUNC_INFO handler_type_idispatch_3variantptr_boolptr_;
+ static _ATL_FUNC_INFO handler_type_idispatchptr_boolptr_;
+ static _ATL_FUNC_INFO handler_type_idispatchptr_boolptr_dword_2bstr_;
+
+ // The top-level web browser (window) we're attached to. NULL before SetSite.
+ CComPtr<IWebBrowser2> web_browser_;
+
+ // The Chrome Frame host handling a Chrome Frame instance for us.
+ CComPtr<IChromeFrameHost> chrome_frame_host_;
+
+ // The Broker Registrar we use to un/register executors for our thread.
+ CComPtr<ICeeeBrokerRegistrar> broker_registrar_;
+
+ // We keep a reference to the executor we registered so that we can
+ // manually disconnect it, so it doesn't get called while we unregister it.
+ CComPtr<IUnknown> executor_;
+
+ // Maintains a map from browser (top-level and sub-browsers) to the
+ // attached FrameEventHandlers.
+ typedef std::map<CAdapt<CComPtr<IUnknown> >,
+ CAdapt<CComPtr<IFrameEventHandler> > > BrowserHandlerMap;
+ BrowserHandlerMap browsers_;
+
+ // Initialized by LoadManifestFile() at
+ // OnCfGetEnabledExtensionsComplete-time. Valid from that point forward.
+ UserScriptsLibrarian librarian_;
+
+ // Filesystem path to the .crx we will install (or have installed), or the
+ // empty string, or (if not ending in .crx) the path to an exploded extension
+ // directory to load (or which we have loaded).
+ std::wstring extension_path_;
+
+ // The extension we're associated with. Set at
+ // OnCfGetEnabledExtensionsComplete-time.
+ // TODO(siggi@chromium.org): Generalize this to multiple extensions.
+ std::wstring extension_id_;
+
+ // The base directory of the extension we're associated with.
+ // Set at OnCfGetEnabledExtensionsComplete time.
+ std::wstring extension_base_dir_;
+
+ // Extension port messaging and management is delegated to this.
+ ExtensionPortManager extension_port_manager_;
+
+ // Used to dispatch tab events back to Chrome.
+ TabEventsFunnel tab_events_funnel_;
+
+ // Remember the tab window handle so that we can use it.
+ HWND tab_window_;
+
+ // Remember the tab id so we can pass it to the underlying Chrome.
+ int tab_id_;
+
+ // Makes sure we fire the onCreated event only once.
+ bool fired_on_created_event_;
+
+ // True if we found no enabled extensions and tried to install one.
+ bool already_tried_installing_;
+
+ // The last known ready state lower bound, so that we decide when to fire a
+ // tabs.onUpdated event, which only when we go from all frames completed to
+ // at least one of them not completed, and vice versa (from incomplete to
+ // fully completely completed :-)...
+ READYSTATE lower_bound_ready_state_;
+
+ // Consumers of WebBrowser events.
+ std::vector<Sink*> sinks_;
+
+ // Used to generate and fire Web progress notifications.
+ scoped_ptr<WebProgressNotifier> web_progress_notifier_;
+
+ // True if the user is running IE7 or later.
+ bool ie7_or_later_;
+
+ // The thread we are running into.
+ DWORD thread_id_;
+
+ // Indicates if the current shown page is a full-tab chrome frame.
+ bool full_tab_chrome_frame_;
+
+ private:
+ // Used during initialization to get the tab information from Chrome and
+ // register ourselves with the broker.
+ HRESULT RegisterTabInfo();
+
+ typedef std::deque<Task*> DeferredCallListType;
+ DeferredCallListType deferred_tab_id_call_;
+};
+
+#endif // CEEE_IE_PLUGIN_BHO_BROWSER_HELPER_OBJECT_H_
diff --git a/ceee/ie/plugin/bho/browser_helper_object.rgs b/ceee/ie/plugin/bho/browser_helper_object.rgs
new file mode 100644
index 0000000..f7331a6
--- /dev/null
+++ b/ceee/ie/plugin/bho/browser_helper_object.rgs
@@ -0,0 +1,28 @@
+HKCR {
+NoRemove CLSID {
+ ForceRemove {E49EBDB7-CEC9-4014-A5F5-8D3C8F5997DC} = s 'Google Chrome Extensions Execution Environment Helper' {
+ InprocServer32 = s '%MODULE%' {
+ val ThreadingModel = s 'Apartment'
+ }
+ 'TypeLib' = s '{7C09079D-F9CB-4E9E-9293-D224B071D8BA}'
+ }
+ }
+}
+
+HKLM {
+ NoRemove SOFTWARE {
+ NoRemove Microsoft {
+ NoRemove Windows {
+ NoRemove CurrentVersion {
+ NoRemove Explorer {
+ NoRemove 'Browser Helper Objects' {
+ ForceRemove '{E49EBDB7-CEC9-4014-A5F5-8D3C8F5997DC}' = s 'Google Chrome Extensions Execution Environment Helper' {
+ val 'NoExplorer' = d '1'
+ }
+ }
+ }
+ }
+ }
+ }
+ }
+}
diff --git a/ceee/ie/plugin/bho/browser_helper_object_unittest.cc b/ceee/ie/plugin/bho/browser_helper_object_unittest.cc
new file mode 100644
index 0000000..285a733
--- /dev/null
+++ b/ceee/ie/plugin/bho/browser_helper_object_unittest.cc
@@ -0,0 +1,743 @@
+// 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.
+//
+// IE browser helper object implementation.
+#include "ceee/ie/plugin/bho/browser_helper_object.h"
+
+#include <exdisp.h>
+#include <shlguid.h>
+
+#include "ceee/common/initializing_coclass.h"
+#include "ceee/ie/testing/mock_broker_and_friends.h"
+#include "ceee/ie/testing/mock_browser_and_friends.h"
+#include "ceee/ie/testing/mock_chrome_frame_host.h"
+#include "ceee/testing/utils/dispex_mocks.h"
+#include "ceee/testing/utils/instance_count_mixin.h"
+#include "ceee/testing/utils/mock_com.h"
+#include "ceee/testing/utils/test_utils.h"
+#include "gmock/gmock.h"
+#include "gtest/gtest.h"
+
+#include "broker_lib.h" // NOLINT
+
+namespace {
+
+using testing::_;
+using testing::CopyBSTRToArgument;
+using testing::CopyInterfaceToArgument;
+using testing::DoAll;
+using testing::GetConnectionCount;
+using testing::InstanceCountMixin;
+using testing::MockChromeFrameHost;
+using testing::MockDispatchEx;
+using testing::MockIOleWindow;
+using testing::NotNull;
+using testing::Return;
+using testing::SetArgumentPointee;
+using testing::StrEq;
+using testing::StrictMock;
+using testing::TestBrowser;
+using testing::TestBrowserSite;
+
+// Tab Ids passed to the API
+const int kGoodTabId = 1;
+const CeeeWindowHandle kGoodTabHandle = kGoodTabId + 1;
+const HWND kGoodTab = (HWND)kGoodTabHandle;
+
+class TestFrameEventHandler
+ : public CComObjectRootEx<CComSingleThreadModel>,
+ public InitializingCoClass<StrictMock<TestFrameEventHandler> >,
+ public InstanceCountMixin<TestFrameEventHandler>,
+ public IFrameEventHandler {
+ public:
+ BEGIN_COM_MAP(TestFrameEventHandler)
+ COM_INTERFACE_ENTRY_IID(IID_IFrameEventHandler, IFrameEventHandler)
+ END_COM_MAP()
+
+ HRESULT Initialize(TestFrameEventHandler** self) {
+ *self = this;
+ return S_OK;
+ }
+
+ MOCK_METHOD1(GetUrl, void(BSTR* url));
+ MOCK_METHOD1(SetUrl, HRESULT(BSTR url));
+ // no need to mock it yet and it is called from a DCHECK so...
+ virtual READYSTATE GetReadyState() { return READYSTATE_COMPLETE; }
+ MOCK_METHOD1(AddSubHandler, HRESULT(IFrameEventHandler* handler));
+ MOCK_METHOD1(RemoveSubHandler, HRESULT(IFrameEventHandler* handler));
+ MOCK_METHOD0(TearDown, void());
+ MOCK_METHOD3(InsertCode, HRESULT(BSTR code, BSTR file,
+ CeeeTabCodeType type));
+ MOCK_METHOD0(RedoDoneInjections, void());
+};
+
+class TestingBrowserHelperObject
+ : public BrowserHelperObject,
+ public InstanceCountMixin<TestingBrowserHelperObject>,
+ public InitializingCoClass<TestingBrowserHelperObject> {
+ public:
+ HRESULT Initialize(TestingBrowserHelperObject** self) {
+ // Make sure this is done early so we can mock it.
+ EXPECT_HRESULT_SUCCEEDED(MockChromeFrameHost::CreateInitializedIID(
+ &mock_chrome_frame_host_, IID_IChromeFrameHost,
+ &mock_chrome_frame_host_keeper_));
+ chrome_frame_host_ = mock_chrome_frame_host_;
+ *self = this;
+ return S_OK;
+ }
+
+ virtual TabEventsFunnel& tab_events_funnel() {
+ return mock_tab_events_funnel_;
+ }
+
+ virtual HRESULT GetBrokerRegistrar(ICeeeBrokerRegistrar** broker) {
+ broker_keeper_.CopyTo(broker);
+ return S_OK;
+ }
+
+ virtual HRESULT CreateExecutor(IUnknown** executor) {
+ executor_keeper_.CopyTo(executor);
+ return S_OK;
+ }
+
+ virtual HRESULT CreateChromeFrameHost() {
+ EXPECT_TRUE(chrome_frame_host_ != NULL);
+ return S_OK;
+ }
+
+ virtual HRESULT GetTabWindow(IServiceProvider* service_provider) {
+ tab_window_ = reinterpret_cast<HWND>(kGoodTab);
+ return S_OK;
+ }
+
+ virtual void SetTabId(int tab_id) {
+ tab_id_ = tab_id;
+ }
+
+ virtual WebProgressNotifier* CreateWebProgressNotifier() {
+ // Without calling Initialize(), the class won't do anything.
+ return new WebProgressNotifier();
+ }
+
+ MOCK_METHOD0(SetupNewTabInfo, bool());
+ MOCK_METHOD3(CreateFrameEventHandler, HRESULT(IWebBrowser2* browser,
+ IWebBrowser2* parent_browser,
+ IFrameEventHandler** handler));
+ MOCK_METHOD1(BrowserContainsChromeFrame, bool(IWebBrowser2* browser));
+
+ MOCK_METHOD2(IsHashChange, bool(BSTR, BSTR));
+ bool CallIsHashChange(BSTR url1, BSTR url2) {
+ return BrowserHelperObject::IsHashChange(url1, url2);
+ }
+
+ MOCK_METHOD2(GetParentBrowser, HRESULT(IWebBrowser2*, IWebBrowser2**));
+
+ // Pulicize
+ using BrowserHelperObject::HandleNavigateComplete;
+
+ StrictMock<testing::MockTabEventsFunnel> mock_tab_events_funnel_;
+ MockChromeFrameHost* mock_chrome_frame_host_;
+ CComPtr<IChromeFrameHost> mock_chrome_frame_host_keeper_;
+
+ testing::MockExecutorIUnknown* executor_;
+ CComPtr<IUnknown> executor_keeper_;
+
+ testing::MockBroker* broker_;
+ CComPtr<ICeeeBrokerRegistrar> broker_keeper_;
+};
+
+class BrowserHelperObjectTest: public testing::Test {
+ public:
+ BrowserHelperObjectTest() : bho_(NULL), site_(NULL), browser_(NULL) {
+ }
+ ~BrowserHelperObjectTest() {
+ }
+
+ virtual void SetUp() {
+ // Create the instance to test.
+ ASSERT_HRESULT_SUCCEEDED(
+ TestingBrowserHelperObject::CreateInitialized(&bho_, &bho_with_site_));
+ bho_with_site_ = bho_;
+
+ // TODO(mad@chromium.org): Test this method.
+ EXPECT_CALL(*bho_, SetupNewTabInfo()).WillRepeatedly(Return(true));
+
+ // We always go beyond Chrome Frame start and event funnel init.
+ // Create the broker registrar related objects
+ ASSERT_HRESULT_SUCCEEDED(testing::MockExecutorIUnknown::CreateInitialized(
+ &bho_->executor_, &bho_->executor_keeper_));
+ ASSERT_HRESULT_SUCCEEDED(testing::MockBroker::CreateInitialized(
+ &bho_->broker_, &bho_->broker_keeper_));
+
+ // We always go beyond Chrome Frame start, broker reg and event funnel init.
+ // TODO(mad@chromium.org): Also cover failure cases from those.
+ ExpectBrokerRegistration();
+ ExpectChromeFrameStart();
+
+ // Assert on successful TearDown.
+ ExpectBrokerUnregistration();
+ ExpectChromeFrameTearDown();
+ }
+
+ virtual void TearDown() {
+ bho_->executor_ = NULL;
+ bho_->executor_keeper_.Release();
+
+ bho_->broker_ = NULL;
+ bho_->broker_keeper_.Release();
+
+ bho_ = NULL;
+ bho_with_site_.Release();
+
+ site_ = NULL;
+ site_keeper_.Release();
+
+ browser_ = NULL;
+ browser_keeper_.Release();
+
+ handler_ = NULL;
+ handler_keeper_.Release();
+
+ // Everything should have been relinquished.
+ ASSERT_EQ(0, testing::InstanceCountMixinBase::all_instance_count());
+ }
+
+ void CreateSite() {
+ ASSERT_HRESULT_SUCCEEDED(
+ TestBrowserSite::CreateInitialized(&site_, &site_keeper_));
+ }
+
+ void CreateBrowser() {
+ ASSERT_HRESULT_SUCCEEDED(
+ TestBrowser::CreateInitialized(&browser_, &browser_keeper_));
+
+ // Fail get_Parent calls for the root.
+ EXPECT_CALL(*bho_, GetParentBrowser(browser_keeper_.p, NotNull())).
+ WillRepeatedly(Return(E_NOTIMPL));
+
+ if (site_)
+ site_->browser_ = browser_keeper_;
+ }
+
+ void CreateHandler() {
+ ASSERT_HRESULT_SUCCEEDED(
+ TestFrameEventHandler::CreateInitializedIID(
+ &handler_, IID_IFrameEventHandler, &handler_keeper_));
+ }
+
+ bool BhoHasSite() {
+ // Check whether BHO has a site set.
+ CComPtr<IUnknown> site;
+ if (SUCCEEDED(bho_with_site_->GetSite(
+ IID_IUnknown, reinterpret_cast<void**>(&site))))
+ return true;
+ if (site != NULL)
+ return true;
+
+ return false;
+ }
+
+ void ExpectChromeFrameStart() {
+ EXPECT_CALL(*(bho_->mock_chrome_frame_host_), SetEventSink(_)).
+ Times(1);
+ EXPECT_CALL(*(bho_->mock_chrome_frame_host_), SetChromeProfileName(_)).
+ Times(1);
+ EXPECT_CALL(*(bho_->mock_chrome_frame_host_), StartChromeFrame()).
+ WillOnce(Return(S_OK));
+ }
+
+ CComBSTR CreateTabInfo(int tab_id) {
+ std::ostringstream iss;
+ iss << L"{\"id\":" << tab_id << L"}";
+ return CComBSTR(iss.str().c_str());
+ }
+ void ExpectChromeFrameGetSessionId() {
+ EXPECT_CALL(*(bho_->mock_chrome_frame_host_), GetSessionId(NotNull())).
+ WillOnce(DoAll(SetArgumentPointee<0>(kGoodTabId), Return(S_OK)));
+ EXPECT_CALL(*(bho_->broker_), SetTabIdForHandle(kGoodTabId,
+ kGoodTabHandle)).WillOnce(Return(S_OK));
+ }
+
+ void ExpectChromeFrameTearDown() {
+ EXPECT_CALL(*(bho_->mock_chrome_frame_host_), SetEventSink(NULL)).
+ Times(1);
+ EXPECT_CALL(*(bho_->mock_chrome_frame_host_), TearDown()).
+ WillOnce(Return(S_OK));
+ }
+
+ void ExpectBrokerRegistration() {
+ EXPECT_CALL(*bho_->broker_, RegisterTabExecutor(::GetCurrentThreadId(),
+ bho_->executor_keeper_.p)).WillOnce(Return(S_OK));
+ }
+
+ void ExpectBrokerUnregistration() {
+ EXPECT_CALL(*bho_->broker_, UnregisterExecutor(::GetCurrentThreadId())).
+ WillOnce(Return(S_OK));
+ }
+
+ void ExpectHandleNavigation(TestFrameEventHandler* handler,
+ bool hash_change) {
+ EXPECT_CALL(*handler, GetUrl(_)).Times(1).
+ WillOnce(CopyBSTRToArgument<0>(kUrl2));
+ EXPECT_CALL(*bho_, IsHashChange(StrEq(kUrl1), StrEq(kUrl2))).
+ WillOnce(Return(hash_change));
+ // We should get the URL poked at the handler.
+ EXPECT_CALL(*handler, SetUrl(StrEq(kUrl1))).WillOnce(Return(S_OK));
+ }
+
+ void ExpectTopBrowserNavigation(bool hash_change, bool first_call) {
+ // We also get a tab update notification.
+ if (first_call) {
+ EXPECT_CALL(bho_->mock_tab_events_funnel_,
+ OnCreated(_, StrEq(kUrl1), false)).Times(1);
+ }
+ EXPECT_CALL(bho_->mock_tab_events_funnel_,
+ OnUpdated(_, StrEq(kUrl1), READYSTATE_UNINITIALIZED)).Times(1);
+ if (hash_change) {
+ EXPECT_CALL(bho_->mock_tab_events_funnel_,
+ OnUpdated(_, StrEq(kUrl1), READYSTATE_COMPLETE)).Times(1);
+ }
+ }
+
+ void ExpectFireOnRemovedEvent() {
+ EXPECT_CALL(bho_->mock_tab_events_funnel_, OnRemoved(_));
+ }
+
+ void ExpectFireOnUnmappedEvent() {
+ EXPECT_CALL(bho_->mock_tab_events_funnel_, OnTabUnmapped(_, _));
+ }
+
+ static const wchar_t* kUrl1;
+ static const wchar_t* kUrl2;
+
+ // Logging quenched for all tests.
+ testing::LogDisabler no_dchecks_;
+
+ TestingBrowserHelperObject* bho_;
+ CComPtr<IObjectWithSite> bho_with_site_;
+
+ testing::TestBrowserSite* site_;
+ CComPtr<IUnknown> site_keeper_;
+
+ TestBrowser* browser_;
+ CComPtr<IWebBrowser2> browser_keeper_;
+
+ TestFrameEventHandler* handler_;
+ CComPtr<IFrameEventHandler> handler_keeper_;
+};
+
+const wchar_t* BrowserHelperObjectTest::kUrl1 =
+L"http://www.google.com/search?q=Google+Buys+Iceland";
+const wchar_t* BrowserHelperObjectTest::kUrl2 = L"http://www.google.com";
+
+
+// Setting the BHO site with a non-service provider fails.
+TEST_F(BrowserHelperObjectTest, SetSiteWithNoServiceProviderFails) {
+ // Create an object that doesn't implement IServiceProvider.
+ MockDispatchEx* site = NULL;
+ CComPtr<IUnknown> site_keeper;
+ ASSERT_HRESULT_SUCCEEDED(
+ InitializingCoClass<MockDispatchEx>::CreateInitialized(&site,
+ &site_keeper));
+ // Setting a site that doesn't implement IServiceProvider fails.
+ ASSERT_HRESULT_FAILED(bho_with_site_->SetSite(site_keeper));
+ ASSERT_FALSE(BhoHasSite());
+}
+
+// Setting the BHO site with no browser fails.
+TEST_F(BrowserHelperObjectTest, SetSiteWithNullBrowserFails) {
+ CreateSite();
+
+ // Setting a site with no browser fails.
+ ASSERT_HRESULT_FAILED(bho_with_site_->SetSite(site_keeper_));
+ ASSERT_FALSE(BhoHasSite());
+}
+
+// Setting the BHO site with a non-browser fails.
+TEST_F(BrowserHelperObjectTest, SetSiteWithNonBrowserFails) {
+ CreateSite();
+
+ // Endow the site with a non-browser service.
+ MockDispatchEx* mock_non_browser = NULL;
+ ASSERT_HRESULT_SUCCEEDED(
+ InitializingCoClass<MockDispatchEx>::CreateInitialized(&mock_non_browser,
+ &site_->browser_));
+ // Setting a site with a non-browser fails.
+ ASSERT_HRESULT_FAILED(bho_with_site_->SetSite(site_keeper_));
+ ASSERT_FALSE(BhoHasSite());
+}
+
+// Setting the BHO site with a browser that doesn't implement the
+// DIID_DWebBrowserEvents2 connection point fails.
+TEST_F(BrowserHelperObjectTest, SetSiteWithNoEventsFails) {
+ CreateSite();
+ CreateBrowser();
+
+ // Disable the connection point.
+ browser_->no_events_ = true;
+
+ // No connection point site fails.
+ ASSERT_HRESULT_FAILED(bho_with_site_->SetSite(site_keeper_));
+ ASSERT_FALSE(BhoHasSite());
+}
+
+TEST_F(BrowserHelperObjectTest, SetSiteWithBrowserSucceeds) {
+ CreateSite();
+ CreateBrowser();
+
+ size_t num_connections = 0;
+ ASSERT_HRESULT_SUCCEEDED(GetConnectionCount(browser_keeper_,
+ DIID_DWebBrowserEvents2,
+ &num_connections));
+ ASSERT_EQ(0, num_connections);
+
+ ASSERT_HRESULT_SUCCEEDED(bho_with_site_->SetSite(site_keeper_));
+ // Check that the we set up a connection.
+ ASSERT_HRESULT_SUCCEEDED(GetConnectionCount(browser_keeper_,
+ DIID_DWebBrowserEvents2,
+ &num_connections));
+ ASSERT_EQ(1, num_connections);
+
+ // Check the site's retained.
+ CComPtr<IUnknown> set_site;
+ ASSERT_HRESULT_SUCCEEDED(bho_with_site_->GetSite(
+ IID_IUnknown, reinterpret_cast<void**>(&set_site)));
+ ASSERT_TRUE(set_site == site_keeper_);
+
+ ExpectFireOnRemovedEvent();
+ ExpectFireOnUnmappedEvent();
+ ASSERT_HRESULT_SUCCEEDED(bho_with_site_->SetSite(NULL));
+
+ // And check that the connection was severed.
+ ASSERT_HRESULT_SUCCEEDED(GetConnectionCount(browser_keeper_,
+ DIID_DWebBrowserEvents2,
+ &num_connections));
+ ASSERT_EQ(0, num_connections);
+}
+
+TEST_F(BrowserHelperObjectTest, OnNavigateCompleteHandled) {
+ CreateSite();
+ CreateBrowser();
+ CreateHandler();
+ ExpectChromeFrameGetSessionId();
+
+ // The site needs to return the top-level browser.
+ site_->browser_ = browser_;
+
+ ASSERT_HRESULT_SUCCEEDED(bho_with_site_->SetSite(site_keeper_));
+
+ EXPECT_CALL(*bho_, CreateFrameEventHandler(browser_, NULL, NotNull())).
+ WillOnce(DoAll(CopyInterfaceToArgument<2>(handler_keeper_),
+ Return(S_OK)));
+ EXPECT_CALL(*bho_, BrowserContainsChromeFrame(browser_)).
+ WillOnce(Return(false));
+ ExpectHandleNavigation(handler_, true);
+ ExpectTopBrowserNavigation(true, true);
+ browser_->FireOnNavigateComplete(browser_, &CComVariant(kUrl1));
+
+ ExpectFireOnRemovedEvent();
+ ExpectFireOnUnmappedEvent();
+ ASSERT_HRESULT_SUCCEEDED(bho_with_site_->SetSite(NULL));
+}
+
+TEST_F(BrowserHelperObjectTest, RenavigationNotifiesUrl) {
+ CreateSite();
+ CreateBrowser();
+ CreateHandler();
+ ExpectChromeFrameGetSessionId();
+
+ ASSERT_HRESULT_SUCCEEDED(bho_with_site_->SetSite(site_keeper_));
+
+ // Make as if a handler has been attached to the browser.
+ ASSERT_HRESULT_SUCCEEDED(
+ bho_->AttachBrowser(browser_, NULL, handler_keeper_));
+
+ EXPECT_CALL(*handler_, GetUrl(_)).Times(1).
+ WillOnce(CopyBSTRToArgument<0>(kUrl2));
+ EXPECT_CALL(*bho_, IsHashChange(StrEq(kUrl1), StrEq(kUrl2))).
+ WillOnce(Return(false));
+ // We should get the "new" URL poked at the handler.
+ EXPECT_CALL(*handler_, SetUrl(StrEq(kUrl1))).Times(1);
+
+ // We also get a tab update notification.
+ EXPECT_CALL(bho_->mock_tab_events_funnel_,
+ OnCreated(_, StrEq(kUrl1), false)).Times(1);
+ EXPECT_CALL(bho_->mock_tab_events_funnel_,
+ OnUpdated(_, StrEq(kUrl1), READYSTATE_UNINITIALIZED)).Times(1);
+ browser_->FireOnNavigateComplete(browser_, &CComVariant(kUrl1));
+
+ ExpectFireOnRemovedEvent();
+ ExpectFireOnUnmappedEvent();
+ ASSERT_HRESULT_SUCCEEDED(bho_with_site_->SetSite(NULL));
+}
+
+// Test that we filter OnNavigateComplete invocations with
+// non-IWebBrowser2 or non BSTR arguments.
+TEST_F(BrowserHelperObjectTest, OnNavigateCompleteUnhandled) {
+ CreateSite();
+ CreateBrowser();
+ ExpectChromeFrameGetSessionId();
+
+ // Create an object that doesn't implement IWebBrowser2.
+ MockDispatchEx* non_browser = NULL;
+ CComPtr<IDispatch> non_browser_keeper;
+ ASSERT_HRESULT_SUCCEEDED(
+ InitializingCoClass<MockDispatchEx>::CreateInitialized(
+ &non_browser, &non_browser_keeper));
+ ASSERT_HRESULT_SUCCEEDED(bho_with_site_->SetSite(site_keeper_));
+
+ // HandleNavigateComplete should not be called by the invocations below.
+ EXPECT_CALL(*bho_, CreateFrameEventHandler(_, _, _)).Times(0);
+
+ // Non-browser target.
+ browser_->FireOnNavigateComplete(non_browser, &CComVariant(kUrl1));
+
+ // Non-BSTR url parameter.
+ browser_->FireOnNavigateComplete(browser_, &CComVariant(non_browser));
+
+ ExpectFireOnRemovedEvent();
+ ExpectFireOnUnmappedEvent();
+ ASSERT_HRESULT_SUCCEEDED(bho_with_site_->SetSite(NULL));
+}
+
+TEST_F(BrowserHelperObjectTest, HandleNavigateComplete) {
+ CreateSite();
+ CreateBrowser();
+ CreateHandler();
+
+ // The site needs to return the top-level browser.
+ site_->browser_ = browser_;
+ ASSERT_HRESULT_SUCCEEDED(bho_with_site_->SetSite(site_keeper_));
+
+ EXPECT_CALL(*bho_, CreateFrameEventHandler(browser_, NULL, NotNull())).
+ WillOnce(DoAll(CopyInterfaceToArgument<2>(handler_keeper_),
+ Return(S_OK)));
+ EXPECT_CALL(*bho_, BrowserContainsChromeFrame(browser_)).
+ WillOnce(Return(false));
+ ExpectHandleNavigation(handler_, false);
+ ExpectTopBrowserNavigation(false, true);
+ bho_->HandleNavigateComplete(browser_, CComBSTR(kUrl1));
+
+ // Now handle the case without the creation of a handler.
+ EXPECT_HRESULT_SUCCEEDED(bho_->AttachBrowser(browser_, NULL, handler_));
+ ExpectHandleNavigation(handler_, false);
+ ExpectTopBrowserNavigation(false, false);
+ bho_->HandleNavigateComplete(browser_, CComBSTR(kUrl1));
+
+ // Now navigate a sub-frame.
+ TestBrowser* browser2;
+ CComPtr<IWebBrowser2> browser2_keeper;
+ ASSERT_HRESULT_SUCCEEDED(
+ TestBrowser::CreateInitialized(&browser2, &browser2_keeper));
+ EXPECT_CALL(*bho_, GetParentBrowser(browser2, NotNull())).
+ WillOnce(DoAll(CopyInterfaceToArgument<1>(browser_keeper_),
+ Return(S_OK)));
+ TestFrameEventHandler* handler2;
+ CComPtr<IFrameEventHandler> handler2_keeper;
+ ASSERT_HRESULT_SUCCEEDED(
+ TestFrameEventHandler::CreateInitializedIID(
+ &handler2, IID_IFrameEventHandler, &handler2_keeper));
+
+ EXPECT_CALL(*bho_,
+ CreateFrameEventHandler(browser2, browser_, NotNull())).
+ WillOnce(DoAll(CopyInterfaceToArgument<2>(handler2_keeper),
+ Return(S_OK)));
+
+ ExpectHandleNavigation(handler2, false);
+ bho_->HandleNavigateComplete(browser2, CComBSTR(kUrl1));
+ EXPECT_CALL(*handler_, AddSubHandler(handler2)).
+ WillOnce(Return(S_OK));
+ EXPECT_HRESULT_SUCCEEDED(bho_->AttachBrowser(browser2, browser_, handler2));
+
+ // Now, navigating the top browser again.
+ ExpectHandleNavigation(handler_, false);
+ ExpectTopBrowserNavigation(false, false);
+ bho_->HandleNavigateComplete(browser_, CComBSTR(kUrl1));
+
+ ExpectFireOnRemovedEvent();
+ ExpectFireOnUnmappedEvent();
+ ASSERT_HRESULT_SUCCEEDED(bho_with_site_->SetSite(NULL));
+}
+
+TEST_F(BrowserHelperObjectTest, AttachOrphanedBrowser) {
+ CreateSite();
+ CreateBrowser();
+ CreateHandler();
+
+ // The site needs to return the top-level browser.
+ site_->browser_ = browser_;
+ ASSERT_HRESULT_SUCCEEDED(bho_with_site_->SetSite(site_keeper_));
+
+ // Attach the root.
+ EXPECT_HRESULT_SUCCEEDED(bho_->AttachBrowser(browser_, NULL, handler_));
+
+ // Now attach an apparent orphan which is actually the grand child of an
+ // existing frame which parent wasn't seen yet.
+ TestBrowser* browser3;
+ CComPtr<IWebBrowser2> browser3_keeper;
+ ASSERT_HRESULT_SUCCEEDED(
+ TestBrowser::CreateInitialized(&browser3, &browser3_keeper));
+
+ TestFrameEventHandler* handler3;
+ CComPtr<IFrameEventHandler> handler_keeper_3;
+ ASSERT_HRESULT_SUCCEEDED(
+ TestFrameEventHandler::CreateInitializedIID(
+ &handler3, IID_IFrameEventHandler, &handler_keeper_3));
+
+ TestBrowser* browser3_parent;
+ CComPtr<IWebBrowser2> browser3_parent_keeper;
+ ASSERT_HRESULT_SUCCEEDED(
+ TestBrowser::CreateInitialized(&browser3_parent,
+ &browser3_parent_keeper));
+
+ TestFrameEventHandler* handler3_parent;
+ CComPtr<IFrameEventHandler> handler3_parent_keeper;
+ ASSERT_HRESULT_SUCCEEDED(
+ TestFrameEventHandler::CreateInitializedIID(
+ &handler3_parent, IID_IFrameEventHandler, &handler3_parent_keeper));
+
+ EXPECT_CALL(*bho_, GetParentBrowser(browser3_parent, NotNull())).
+ WillOnce(DoAll(CopyInterfaceToArgument<1>(browser_keeper_.p),
+ Return(S_OK)));
+ EXPECT_CALL(*browser3_parent, get_LocationURL(NotNull())).
+ WillOnce(DoAll(CopyBSTRToArgument<0>(kUrl1), Return(S_OK)));
+ EXPECT_CALL(*bho_,
+ CreateFrameEventHandler(browser3_parent, browser_keeper_.p, NotNull())).
+ WillOnce(DoAll(CopyInterfaceToArgument<2>(handler3_parent_keeper),
+ Return(S_OK)));
+ EXPECT_CALL(*handler3_parent, SetUrl(StrEq(kUrl1))).WillOnce(Return(S_OK));
+ EXPECT_CALL(*handler3_parent, AddSubHandler(handler3)).WillOnce(Return(S_OK));
+ EXPECT_HRESULT_SUCCEEDED(bho_->AttachBrowser(browser3, browser3_parent,
+ handler3));
+
+ ExpectFireOnRemovedEvent();
+ ExpectFireOnUnmappedEvent();
+ ASSERT_HRESULT_SUCCEEDED(bho_with_site_->SetSite(NULL));
+}
+
+TEST_F(BrowserHelperObjectTest, IFrameEventHandlerHost) {
+ CreateSite();
+ CreateBrowser();
+ CreateHandler();
+
+ ASSERT_HRESULT_SUCCEEDED(bho_with_site_->SetSite(site_keeper_));
+
+ // Detaching a non-attached browser should fail.
+ EXPECT_HRESULT_FAILED(bho_->DetachBrowser(browser_, NULL, handler_));
+
+ // First-time attach should succeed.
+ EXPECT_HRESULT_SUCCEEDED(bho_->AttachBrowser(browser_, NULL, handler_));
+ // Second attach should fail.
+ EXPECT_HRESULT_FAILED(bho_->AttachBrowser(browser_, NULL, handler_));
+
+ // Subsequent detach should succeed.
+ EXPECT_HRESULT_SUCCEEDED(bho_->DetachBrowser(browser_, NULL, handler_));
+ // But not twice.
+ EXPECT_HRESULT_FAILED(bho_->DetachBrowser(browser_, NULL, handler_));
+
+ ExpectFireOnRemovedEvent();
+ ExpectFireOnUnmappedEvent();
+ ASSERT_HRESULT_SUCCEEDED(bho_with_site_->SetSite(NULL));
+
+ // TODO(siggi@chromium.org): test hierarchial attach/detach/TearDown.
+}
+
+TEST_F(BrowserHelperObjectTest, InsertCode) {
+ CreateSite();
+ CreateBrowser();
+ CreateHandler();
+ ExpectChromeFrameGetSessionId();
+
+ ASSERT_HRESULT_SUCCEEDED(bho_with_site_->SetSite(site_keeper_));
+ ASSERT_TRUE(BhoHasSite());
+ ASSERT_HRESULT_SUCCEEDED(bho_->AttachBrowser(browser_, NULL, handler_));
+
+ CComBSTR code;
+ CComBSTR file;
+ EXPECT_CALL(*handler_, InsertCode(_, _, kCeeeTabCodeTypeCss))
+ .WillOnce(Return(S_OK));
+ ASSERT_HRESULT_SUCCEEDED(bho_->InsertCode(code, file, FALSE,
+ kCeeeTabCodeTypeCss));
+
+ ExpectFireOnRemovedEvent();
+ ExpectFireOnUnmappedEvent();
+ ASSERT_HRESULT_SUCCEEDED(bho_with_site_->SetSite(NULL));
+}
+
+TEST_F(BrowserHelperObjectTest, InsertCodeAllFrames) {
+ CreateSite();
+ CreateBrowser();
+ CreateHandler();
+ ExpectChromeFrameGetSessionId();
+
+ ASSERT_HRESULT_SUCCEEDED(bho_with_site_->SetSite(site_keeper_));
+ ASSERT_TRUE(BhoHasSite());
+ ASSERT_HRESULT_SUCCEEDED(bho_->AttachBrowser(browser_, NULL, handler_));
+
+ // Add a second browser to the BHO and make sure that both get called.
+ TestBrowser* browser2;
+ CComPtr<IWebBrowser2> browser_keeper_2;
+ ASSERT_HRESULT_SUCCEEDED(
+ TestBrowser::CreateInitialized(&browser2, &browser_keeper_2));
+
+ TestFrameEventHandler* handler2;
+ CComPtr<IFrameEventHandler> handler_keeper_2;
+ ASSERT_HRESULT_SUCCEEDED(
+ TestFrameEventHandler::CreateInitializedIID(
+ &handler2, IID_IFrameEventHandler, &handler_keeper_2));
+
+ ASSERT_HRESULT_SUCCEEDED(bho_->AttachBrowser(browser_keeper_2,
+ NULL,
+ handler_keeper_2));
+
+ CComBSTR code;
+ CComBSTR file;
+ EXPECT_CALL(*handler_, InsertCode(_, _, kCeeeTabCodeTypeJs))
+ .WillOnce(Return(S_OK));
+ EXPECT_CALL(*handler2, InsertCode(_, _, kCeeeTabCodeTypeJs))
+ .WillOnce(Return(S_OK));
+ ASSERT_HRESULT_SUCCEEDED(bho_->InsertCode(code, file, TRUE,
+ kCeeeTabCodeTypeJs));
+
+ ExpectFireOnRemovedEvent();
+ ExpectFireOnUnmappedEvent();
+ ASSERT_HRESULT_SUCCEEDED(bho_with_site_->SetSite(NULL));
+}
+
+TEST_F(BrowserHelperObjectTest, IsHashChange) {
+ CreateSite();
+ CreateBrowser();
+ CreateHandler();
+
+ ASSERT_HRESULT_SUCCEEDED(bho_with_site_->SetSite(site_keeper_));
+
+ CComBSTR url1("http://www.google.com/");
+ CComBSTR url2("http://www.google.com/#");
+ CComBSTR url3("http://www.google.com/#test");
+ CComBSTR url4("http://www.google.com/#bingo");
+ CComBSTR url5("http://www.bingo.com/");
+ CComBSTR url6("http://www.twitter.com/#test");
+ CComBSTR empty;
+
+ // Passing cases.
+ EXPECT_TRUE(bho_->CallIsHashChange(url1, url2));
+ EXPECT_TRUE(bho_->CallIsHashChange(url1, url3));
+ EXPECT_TRUE(bho_->CallIsHashChange(url2, url3));
+ EXPECT_TRUE(bho_->CallIsHashChange(url3, url4));
+
+ // Failing cases.
+ EXPECT_FALSE(bho_->CallIsHashChange(url1, empty));
+ EXPECT_FALSE(bho_->CallIsHashChange(empty, url1));
+ EXPECT_FALSE(bho_->CallIsHashChange(url1, url1));
+ EXPECT_FALSE(bho_->CallIsHashChange(url1, url5));
+ EXPECT_FALSE(bho_->CallIsHashChange(url1, url6));
+ EXPECT_FALSE(bho_->CallIsHashChange(url3, url6));
+ EXPECT_FALSE(bho_->CallIsHashChange(url5, url6));
+
+ ExpectFireOnRemovedEvent();
+ ExpectFireOnUnmappedEvent();
+ ASSERT_HRESULT_SUCCEEDED(bho_with_site_->SetSite(NULL));
+}
+
+} // namespace
diff --git a/ceee/ie/plugin/bho/cookie_accountant.cc b/ceee/ie/plugin/bho/cookie_accountant.cc
new file mode 100644
index 0000000..7453bb9
--- /dev/null
+++ b/ceee/ie/plugin/bho/cookie_accountant.cc
@@ -0,0 +1,226 @@
+// 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.
+//
+// CookieAccountant implementation.
+#include "ceee/ie/plugin/bho/cookie_accountant.h"
+
+#include <atlbase.h>
+#include <wininet.h>
+
+#include <set>
+
+#include "base/format_macros.h" // For PRIu64.
+#include "base/logging.h"
+#include "base/process_util.h"
+#include "base/string_tokenizer.h"
+#include "base/time.h"
+#include "base/utf_string_conversions.h"
+#include "ceee/ie/broker/cookie_api_module.h"
+#include "ceee/ie/common/ceee_module_util.h"
+
+namespace {
+
+static const char kSetCookieHeaderName[] = "Set-Cookie:";
+static const char kHttpResponseHeaderDelimiter[] = "\n";
+static const wchar_t kMsHtmlModuleName[] = L"mshtml.dll";
+static const char kWinInetModuleName[] = "wininet.dll";
+static const char kInternetSetCookieExAFunctionName[] = "InternetSetCookieExA";
+static const char kInternetSetCookieExWFunctionName[] = "InternetSetCookieExW";
+
+} // namespace
+
+CookieAccountant* CookieAccountant::singleton_instance_ = NULL;
+
+CookieAccountant::~CookieAccountant() {
+ if (internet_set_cookie_ex_a_patch_.is_patched())
+ internet_set_cookie_ex_a_patch_.Unpatch();
+ if (internet_set_cookie_ex_w_patch_.is_patched())
+ internet_set_cookie_ex_w_patch_.Unpatch();
+}
+
+ProductionCookieAccountant::ProductionCookieAccountant() {
+}
+
+CookieAccountant* CookieAccountant::GetInstance() {
+ // Unit tests can set singleton_instance_ directly.
+ if (singleton_instance_ == NULL)
+ singleton_instance_ = ProductionCookieAccountant::get();
+ return singleton_instance_;
+}
+
+DWORD WINAPI CookieAccountant::InternetSetCookieExAPatch(
+ LPCSTR url, LPCSTR cookie_name, LPCSTR cookie_data,
+ DWORD flags, DWORD_PTR reserved) {
+ base::Time current_time = base::Time::Now();
+ DWORD cookie_state = ::InternetSetCookieExA(url, cookie_name, cookie_data,
+ flags, reserved);
+ CookieAccountant::GetInstance()->RecordCookie(url, cookie_data,
+ current_time);
+ return cookie_state;
+}
+
+DWORD WINAPI CookieAccountant::InternetSetCookieExWPatch(
+ LPCWSTR url, LPCWSTR cookie_name, LPCWSTR cookie_data,
+ DWORD flags, DWORD_PTR reserved) {
+ base::Time current_time = base::Time::Now();
+ DWORD cookie_state = ::InternetSetCookieExW(url, cookie_name, cookie_data,
+ flags, reserved);
+ CookieAccountant::GetInstance()->RecordCookie(
+ std::string(CW2A(url)), std::string(CW2A(cookie_data)), current_time);
+ return cookie_state;
+}
+
+class CurrentProcessFilter : public base::ProcessFilter {
+ public:
+ CurrentProcessFilter() : current_process_id_(base::GetCurrentProcId()) {
+ }
+
+ virtual bool Includes(const base::ProcessEntry& entry) const {
+ return entry.pid() == current_process_id_;
+ }
+
+ private:
+ base::ProcessId current_process_id_;
+
+ DISALLOW_COPY_AND_ASSIGN(CurrentProcessFilter);
+};
+
+// TODO(cindylau@chromium.org): Make this function more robust.
+void CookieAccountant::RecordCookie(
+ const std::string& url, const std::string& cookie_data,
+ const base::Time& current_time) {
+ cookie_api::CookieInfo cookie;
+ std::string cookie_data_string = cookie_data;
+ net::CookieMonster::ParsedCookie parsed_cookie(cookie_data_string);
+ DCHECK(parsed_cookie.IsValid());
+ if (!parsed_cookie.IsValid())
+ return;
+
+ // Fill the cookie info from the parsed cookie.
+ // TODO(cindylau@chromium.org): Add a helper function to convert an
+ // std::string to a BSTR.
+ cookie.name = ::SysAllocString(ASCIIToWide(parsed_cookie.Name()).c_str());
+ cookie.value = ::SysAllocString(ASCIIToWide(parsed_cookie.Value()).c_str());
+ SetScriptCookieDomain(parsed_cookie, &cookie);
+ SetScriptCookiePath(parsed_cookie, &cookie);
+ cookie.secure = parsed_cookie.IsSecure() ? TRUE : FALSE;
+ cookie.http_only = parsed_cookie.IsHttpOnly() ? TRUE : FALSE;
+ SetScriptCookieExpirationDate(parsed_cookie, current_time, &cookie);
+ SetScriptCookieStoreId(&cookie);
+
+ // Send the cookie event to the broker.
+ // TODO(cindylau@chromium.org): Set the removed parameter properly.
+ cookie_events_funnel().OnChanged(false, cookie);
+}
+
+void CookieAccountant::SetScriptCookieDomain(
+ const net::CookieMonster::ParsedCookie& parsed_cookie,
+ cookie_api::CookieInfo* cookie) {
+ if (parsed_cookie.HasDomain()) {
+ cookie->domain = ::SysAllocString(
+ ASCIIToWide(parsed_cookie.Domain()).c_str());
+ cookie->host_only = FALSE;
+ } else {
+ // TODO(cindylau@chromium.org): If the domain is not provided, get
+ // it from the URL.
+ cookie->host_only = TRUE;
+ }
+}
+
+void CookieAccountant::SetScriptCookiePath(
+ const net::CookieMonster::ParsedCookie& parsed_cookie,
+ cookie_api::CookieInfo* cookie) {
+ // TODO(cindylau@chromium.org): If the path is not provided, get it
+ // from the URL.
+ if (parsed_cookie.HasPath())
+ cookie->path = ::SysAllocString(ASCIIToWide(parsed_cookie.Path()).c_str());
+}
+
+void CookieAccountant::SetScriptCookieExpirationDate(
+ const net::CookieMonster::ParsedCookie& parsed_cookie,
+ const base::Time& current_time,
+ cookie_api::CookieInfo* cookie) {
+ // First, try the Max-Age attribute.
+ uint64 max_age = 0;
+ if (parsed_cookie.HasMaxAge() &&
+ sscanf_s(parsed_cookie.MaxAge().c_str(), " %" PRIu64, &max_age) == 1) {
+ cookie->session = FALSE;
+ base::Time expiration_time = current_time +
+ base::TimeDelta::FromSeconds(max_age);
+ cookie->expiration_date = expiration_time.ToDoubleT();
+ } else if (parsed_cookie.HasExpires()) {
+ cookie->session = FALSE;
+ base::Time expiration_time = net::CookieMonster::ParseCookieTime(
+ parsed_cookie.Expires());
+ cookie->expiration_date = expiration_time.ToDoubleT();
+ } else {
+ cookie->session = TRUE;
+ }
+}
+
+void CookieAccountant::SetScriptCookieStoreId(cookie_api::CookieInfo* cookie) {
+ // The store ID is either the current process ID, or the process ID of the
+ // parent process, if that parent process is an IE frame process.
+ // First collect all IE process IDs.
+ std::set<base::ProcessId> ie_pids;
+ base::NamedProcessIterator ie_iter(
+ ceee_module_util::kInternetExplorerModuleName, NULL);
+ while (const base::ProcessEntry* process_entry = ie_iter.NextProcessEntry()) {
+ ie_pids.insert(process_entry->pid());
+ }
+ // Now get the store ID process by finding the current process, and seeing if
+ // its parent process is an IE process.
+ DWORD process_id = 0;
+ CurrentProcessFilter filter;
+ base::ProcessIterator it(&filter);
+ while (const base::ProcessEntry* process_entry = it.NextProcessEntry()) {
+ // There should only be one matching process entry.
+ DCHECK_EQ(process_id, DWORD(0));
+ if (ie_pids.find(process_entry->parent_pid()) != ie_pids.end()) {
+ process_id = process_entry->parent_pid();
+ } else {
+ DCHECK(ie_pids.find(process_entry->pid()) != ie_pids.end());
+ process_id = process_entry->pid();
+ }
+ }
+ DCHECK_NE(process_id, DWORD(0));
+ std::ostringstream store_id_stream;
+ store_id_stream << process_id;
+ // The broker is responsible for checking that the store ID is registered.
+ cookie->store_id =
+ ::SysAllocString(ASCIIToWide(store_id_stream.str()).c_str());
+}
+
+void CookieAccountant::RecordHttpResponseCookies(
+ const std::string& response_headers, const base::Time& current_time) {
+ StringTokenizer t(response_headers, kHttpResponseHeaderDelimiter);
+ while (t.GetNext()) {
+ std::string header_line = t.token();
+ size_t name_pos = header_line.find(kSetCookieHeaderName);
+ if (name_pos == std::string::npos)
+ continue; // Skip non-cookie headers.
+ std::string cookie_data = header_line.substr(
+ name_pos + std::string(kSetCookieHeaderName).size());
+ // TODO(cindylau@chromium.org): Get the URL for the HTTP request from
+ // IHttpNegotiate::BeginningTransaction.
+ RecordCookie(std::string(), cookie_data, current_time);
+ }
+}
+
+void CookieAccountant::PatchWininetFunctions() {
+ if (!internet_set_cookie_ex_a_patch_.is_patched()) {
+ DWORD error = internet_set_cookie_ex_a_patch_.Patch(
+ kMsHtmlModuleName, kWinInetModuleName,
+ kInternetSetCookieExAFunctionName, InternetSetCookieExAPatch);
+ DCHECK(error == NO_ERROR || !internet_set_cookie_ex_a_patch_.is_patched());
+ }
+ if (!internet_set_cookie_ex_w_patch_.is_patched()) {
+ DWORD error = internet_set_cookie_ex_w_patch_.Patch(
+ kMsHtmlModuleName, kWinInetModuleName,
+ kInternetSetCookieExWFunctionName, InternetSetCookieExWPatch);
+ DCHECK(error == NO_ERROR || !internet_set_cookie_ex_w_patch_.is_patched());
+ }
+ DCHECK(internet_set_cookie_ex_a_patch_.is_patched() ||
+ internet_set_cookie_ex_w_patch_.is_patched());
+}
diff --git a/ceee/ie/plugin/bho/cookie_accountant.h b/ceee/ie/plugin/bho/cookie_accountant.h
new file mode 100644
index 0000000..f26b102
--- /dev/null
+++ b/ceee/ie/plugin/bho/cookie_accountant.h
@@ -0,0 +1,113 @@
+// 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.
+//
+// Defines the CookieAccountant class, which is responsible for observing
+// and recording all cookie-related information generated by a particular
+// IE browser session. It records and fires cookie change events, it provides
+// access to session and persistent cookies.
+
+#ifndef CEEE_IE_PLUGIN_BHO_COOKIE_ACCOUNTANT_H_
+#define CEEE_IE_PLUGIN_BHO_COOKIE_ACCOUNTANT_H_
+
+#include <string>
+
+#include "app/win/iat_patch_function.h"
+#include "base/singleton.h"
+#include "base/time.h"
+#include "ceee/ie/plugin/bho/cookie_events_funnel.h"
+#include "net/base/cookie_monster.h"
+
+// The class that accounts for all cookie-related activity for a single IE
+// browser session context. There should only need to be one of these allocated
+// per process; use ProductionCookieAccountant instead of using this class
+// directly.
+class CookieAccountant {
+ public:
+ // Patch cookie-related functions to observe IE session cookies.
+ void PatchWininetFunctions();
+
+ // Record Set-Cookie changes coming from the HTTP response headers.
+ void RecordHttpResponseCookies(
+ const std::string& response_headers, const base::Time& current_time);
+
+ // An accessor for the singleton (useful for unit testing).
+ static CookieAccountant* GetInstance();
+
+ // InternetSetCookieExA function patch implementation for recording scripted
+ // cookie changes.
+ static DWORD WINAPI InternetSetCookieExAPatch(
+ LPCSTR lpszURL, LPCSTR lpszCookieName, LPCSTR lpszCookieData,
+ DWORD dwFlags, DWORD_PTR dwReserved);
+
+ // InternetSetCookieExW function patch implementation for recording scripted
+ // cookie changes.
+ static DWORD WINAPI InternetSetCookieExWPatch(
+ LPCWSTR lpszURL, LPCWSTR lpszCookieName, LPCWSTR lpszCookieData,
+ DWORD dwFlags, DWORD_PTR dwReserved);
+
+ protected:
+ // Exposed to subclasses mainly for unit testing purposes; production code
+ // should use the ProductionCookieAccountant class instead.
+ CookieAccountant() {}
+ virtual ~CookieAccountant();
+
+ // Records the modification or creation of a cookie. Fires off a
+ // cookies.onChanged event to Chrome Frame.
+ virtual void RecordCookie(
+ const std::string& url, const std::string& cookie_data,
+ const base::Time& current_time);
+
+ // Unit test seam.
+ virtual CookieEventsFunnel& cookie_events_funnel() {
+ return cookie_events_funnel_;
+ }
+
+ // Function patches that allow us to intercept scripted cookie changes.
+ app::win::IATPatchFunction internet_set_cookie_ex_a_patch_;
+ app::win::IATPatchFunction internet_set_cookie_ex_w_patch_;
+
+ // Cached singleton instance. Useful for unit testing.
+ static CookieAccountant* singleton_instance_;
+
+ private:
+ // Helper functions for extracting cookie information from a scripted cookie
+ // being set, to pass to the cookie onChanged event.
+
+ // Sets the cookie domain for a script cookie event.
+ void SetScriptCookieDomain(
+ const net::CookieMonster::ParsedCookie& parsed_cookie,
+ cookie_api::CookieInfo* cookie);
+
+ // Sets the cookie path for a script cookie event.
+ void SetScriptCookiePath(
+ const net::CookieMonster::ParsedCookie& parsed_cookie,
+ cookie_api::CookieInfo* cookie);
+
+ // Sets the cookie expiration date for a script cookie event.
+ void SetScriptCookieExpirationDate(
+ const net::CookieMonster::ParsedCookie& parsed_cookie,
+ const base::Time& current_time,
+ cookie_api::CookieInfo* cookie);
+
+ // Sets the cookie store ID for a script cookie event.
+ void SetScriptCookieStoreId(cookie_api::CookieInfo* cookie);
+
+ // The funnel for sending cookie events to the broker.
+ CookieEventsFunnel cookie_events_funnel_;
+
+ DISALLOW_COPY_AND_ASSIGN(CookieAccountant);
+};
+
+// A singleton that initializes and keeps the CookieAccountant used by
+// production code. This class is separate so that CookieAccountant can still
+// be accessed for unit testing.
+class ProductionCookieAccountant : public CookieAccountant,
+ public Singleton<ProductionCookieAccountant> {
+ private:
+ // This ensures no construction is possible outside of the class itself.
+ friend struct DefaultSingletonTraits<ProductionCookieAccountant>;
+ DISALLOW_IMPLICIT_CONSTRUCTORS(ProductionCookieAccountant);
+};
+
+#endif // CEEE_IE_PLUGIN_BHO_COOKIE_ACCOUNTANT_H_
diff --git a/ceee/ie/plugin/bho/cookie_accountant_unittest.cc b/ceee/ie/plugin/bho/cookie_accountant_unittest.cc
new file mode 100644
index 0000000..6bd00ec
--- /dev/null
+++ b/ceee/ie/plugin/bho/cookie_accountant_unittest.cc
@@ -0,0 +1,141 @@
+// 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.
+//
+// IE browser helper object implementation.
+#include "ceee/ie/plugin/bho/cookie_accountant.h"
+
+#include <wininet.h>
+
+#include "ceee/ie/testing/mock_broker_and_friends.h"
+#include "ceee/testing/utils/mock_static.h"
+#include "ceee/testing/utils/test_utils.h"
+#include "gmock/gmock.h"
+#include "gtest/gtest.h"
+
+namespace {
+
+using testing::_;
+using testing::MockCookieEventsFunnel;
+using testing::Return;
+
+bool StringsNullOrEqual(wchar_t* a, wchar_t* b) {
+ if (a == NULL && b == NULL)
+ return true;
+ if (a != NULL && b != NULL && wcscmp(a, b) == 0)
+ return true;
+ return false;
+}
+
+MATCHER_P(CookiesEqual, cookie, "") {
+ return
+ StringsNullOrEqual(arg.name, cookie->name) &&
+ StringsNullOrEqual(arg.value, cookie->value) &&
+ StringsNullOrEqual(arg.domain, cookie->domain) &&
+ arg.host_only == cookie->host_only &&
+ StringsNullOrEqual(arg.path, cookie->path) &&
+ arg.secure == cookie->secure &&
+ arg.http_only == cookie->http_only &&
+ arg.session == cookie->session &&
+ arg.expiration_date == cookie->expiration_date;
+}
+
+// Mock WinInet functions.
+MOCK_STATIC_CLASS_BEGIN(MockWinInet)
+ MOCK_STATIC_INIT_BEGIN(MockWinInet)
+ MOCK_STATIC_INIT(InternetSetCookieExA);
+ MOCK_STATIC_INIT(InternetSetCookieExW);
+ MOCK_STATIC_INIT_END()
+
+ MOCK_STATIC5(DWORD, CALLBACK, InternetSetCookieExA, LPCSTR, LPCSTR, LPCSTR,
+ DWORD, DWORD_PTR);
+ MOCK_STATIC5(DWORD, CALLBACK, InternetSetCookieExW, LPCWSTR, LPCWSTR,
+ LPCWSTR, DWORD, DWORD_PTR);
+MOCK_STATIC_CLASS_END(MockWinInet)
+
+class MockCookieAccountant : public CookieAccountant {
+ public:
+ MOCK_METHOD3(RecordCookie,
+ void(const std::string&, const std::string&,
+ const base::Time&));
+
+ void CallRecordCookie(const std::string& url,
+ const std::string& cookie_data,
+ const base::Time& current_time) {
+ CookieAccountant::RecordCookie(url, cookie_data, current_time);
+ }
+
+ virtual CookieEventsFunnel& cookie_events_funnel() {
+ return mock_cookie_events_funnel_;
+ }
+
+ static void set_singleton_instance(CookieAccountant* instance) {
+ singleton_instance_ = instance;
+ }
+
+ MockCookieEventsFunnel mock_cookie_events_funnel_;
+};
+
+class CookieAccountantTest : public testing::Test {
+};
+
+TEST_F(CookieAccountantTest, SetCookiePatchFiresCookieEvent) {
+ MockWinInet mock_wininet;
+ MockCookieAccountant cookie_accountant;
+ MockCookieAccountant::set_singleton_instance(&cookie_accountant);
+
+ EXPECT_CALL(mock_wininet, InternetSetCookieExA(_, _, _, _, _)).
+ WillOnce(Return(5));
+ EXPECT_CALL(cookie_accountant, RecordCookie("foo.com", "foo=bar", _));
+ EXPECT_EQ(5, CookieAccountant::InternetSetCookieExAPatch(
+ "foo.com", NULL, "foo=bar", 0, NULL));
+
+ EXPECT_CALL(mock_wininet, InternetSetCookieExW(_, _, _, _, _)).
+ WillOnce(Return(6));
+ EXPECT_CALL(cookie_accountant, RecordCookie("foo.com", "foo=bar", _));
+ EXPECT_EQ(6, CookieAccountant::InternetSetCookieExWPatch(
+ L"foo.com", NULL, L"foo=bar", 0, NULL));
+}
+
+TEST_F(CookieAccountantTest, RecordCookie) {
+ testing::LogDisabler no_dchecks;
+ MockCookieAccountant cookie_accountant;
+ cookie_api::CookieInfo expected_cookie;
+ expected_cookie.name = ::SysAllocString(L"FOO");
+ expected_cookie.value = ::SysAllocString(L"bar");
+ expected_cookie.host_only = TRUE;
+ expected_cookie.http_only = TRUE;
+ expected_cookie.expiration_date = 1278201600;
+ EXPECT_CALL(cookie_accountant.mock_cookie_events_funnel_,
+ OnChanged(false, CookiesEqual(&expected_cookie)));
+ cookie_accountant.CallRecordCookie(
+ "http://www.google.com",
+ "FOO=bar; httponly; expires=Sun, 4 Jul 2010 00:00:00 UTC",
+ base::Time::Now());
+
+ cookie_api::CookieInfo expected_cookie2;
+ expected_cookie2.name = ::SysAllocString(L"");
+ expected_cookie2.value = ::SysAllocString(L"helloworld");
+ expected_cookie2.domain = ::SysAllocString(L"omg.com");
+ expected_cookie2.path = ::SysAllocString(L"/leaping/lizards");
+ expected_cookie2.secure = TRUE;
+ expected_cookie2.session = TRUE;
+ EXPECT_CALL(cookie_accountant.mock_cookie_events_funnel_,
+ OnChanged(false, CookiesEqual(&expected_cookie2)));
+ cookie_accountant.CallRecordCookie(
+ "http://www.omg.com",
+ "helloworld; path=/leaping/lizards; secure; domain=omg.com",
+ base::Time::Now());
+}
+
+TEST_F(CookieAccountantTest, RecordHttpResponseCookies) {
+ testing::LogDisabler no_dchecks;
+ MockCookieAccountant cookie_accountant;
+ EXPECT_CALL(cookie_accountant, RecordCookie("", " foo=bar", _));
+ EXPECT_CALL(cookie_accountant, RecordCookie("", "HELLO=world235", _));
+ cookie_accountant.RecordHttpResponseCookies(
+ "HTTP/1.1 200 OK\nSet-Cookie: foo=bar\nCookie: not_a=cookie\n"
+ "Set-Cookie:HELLO=world235", base::Time::Now());
+}
+
+} // namespace
diff --git a/ceee/ie/plugin/bho/cookie_events_funnel.cc b/ceee/ie/plugin/bho/cookie_events_funnel.cc
new file mode 100644
index 0000000..39ea727
--- /dev/null
+++ b/ceee/ie/plugin/bho/cookie_events_funnel.cc
@@ -0,0 +1,28 @@
+// 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.
+//
+// Funnel of Chrome Extension Cookie Events to the Broker.
+
+#include "ceee/ie/plugin/bho/cookie_events_funnel.h"
+
+#include "base/logging.h"
+#include "base/scoped_ptr.h"
+#include "chrome/browser/extensions/extension_cookies_api_constants.h"
+
+HRESULT CookieEventsFunnel::OnChanged(bool removed,
+ const cookie_api::CookieInfo& cookie) {
+ DictionaryValue change_info;
+ change_info.SetBoolean(extension_cookies_api_constants::kRemovedKey,
+ removed);
+ cookie_api::CookieApiResult api_result(
+ cookie_api::CookieApiResult::kNoRequestId);
+ bool success = api_result.CreateCookieValue(cookie);
+ DCHECK(success);
+ if (!success) {
+ return E_FAIL;
+ }
+ change_info.Set(extension_cookies_api_constants::kCookieKey,
+ api_result.value()->DeepCopy());
+ return SendEvent(extension_cookies_api_constants::kOnChanged, change_info);
+}
diff --git a/ceee/ie/plugin/bho/cookie_events_funnel.h b/ceee/ie/plugin/bho/cookie_events_funnel.h
new file mode 100644
index 0000000..06ada896
--- /dev/null
+++ b/ceee/ie/plugin/bho/cookie_events_funnel.h
@@ -0,0 +1,28 @@
+// 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.
+//
+// Funnel of Chrome Extension Cookie Events.
+
+#ifndef CEEE_IE_PLUGIN_BHO_COOKIE_EVENTS_FUNNEL_H_
+#define CEEE_IE_PLUGIN_BHO_COOKIE_EVENTS_FUNNEL_H_
+
+#include "ceee/ie/broker/cookie_api_module.h"
+#include "ceee/ie/plugin/bho/events_funnel.h"
+
+// Implements a set of methods to send cookie related events to the Broker.
+class CookieEventsFunnel : public EventsFunnel {
+ public:
+ CookieEventsFunnel() : EventsFunnel(false) {}
+
+ // Sends the cookies.onChanged event to the Broker.
+ // @param removed True if the cookie was removed vs. set.
+ // @param cookie Information about the cookie that was set or removed.
+ virtual HRESULT OnChanged(bool removed,
+ const cookie_api::CookieInfo& cookie);
+
+ private:
+ DISALLOW_COPY_AND_ASSIGN(CookieEventsFunnel);
+};
+
+#endif // CEEE_IE_PLUGIN_BHO_COOKIE_EVENTS_FUNNEL_H_
diff --git a/ceee/ie/plugin/bho/cookie_events_funnel_unittest.cc b/ceee/ie/plugin/bho/cookie_events_funnel_unittest.cc
new file mode 100644
index 0000000..650e31d
--- /dev/null
+++ b/ceee/ie/plugin/bho/cookie_events_funnel_unittest.cc
@@ -0,0 +1,59 @@
+// 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.
+//
+// Unit tests for CookieEventsFunnel.
+
+#include "ceee/ie/plugin/bho/cookie_events_funnel.h"
+#include "chrome/browser/extensions/extension_cookies_api_constants.h"
+#include "gmock/gmock.h"
+#include "gtest/gtest.h"
+
+namespace {
+
+using testing::Return;
+using testing::StrEq;
+
+MATCHER_P(ValuesEqual, value, "") {
+ return arg.Equals(value);
+}
+
+class TestCookieEventsFunnel : public CookieEventsFunnel {
+ public:
+ MOCK_METHOD2(SendEvent, HRESULT(const char*, const Value&));
+};
+
+TEST(CookieEventsFunnelTest, OnChanged) {
+ TestCookieEventsFunnel cookie_events_funnel;
+
+ bool removed = true;
+ cookie_api::CookieInfo cookie_info;
+ cookie_info.name = ::SysAllocString(L"FOO");
+ cookie_info.value = ::SysAllocString(L"BAR");
+ cookie_info.secure = TRUE;
+ cookie_info.session = TRUE;
+ cookie_info.store_id = ::SysAllocString(L"a store id!!");
+
+ DictionaryValue dict;
+ dict.SetBoolean(extension_cookies_api_constants::kRemovedKey, removed);
+ DictionaryValue* cookie = new DictionaryValue();
+ cookie->SetString(extension_cookies_api_constants::kNameKey, "FOO");
+ cookie->SetString(extension_cookies_api_constants::kValueKey, "BAR");
+ cookie->SetString(extension_cookies_api_constants::kDomainKey, "");
+ cookie->SetBoolean(extension_cookies_api_constants::kHostOnlyKey, false);
+ cookie->SetString(extension_cookies_api_constants::kPathKey, "");
+ cookie->SetBoolean(extension_cookies_api_constants::kSecureKey, true);
+ cookie->SetBoolean(extension_cookies_api_constants::kHttpOnlyKey, false);
+ cookie->SetBoolean(extension_cookies_api_constants::kSessionKey, true);
+ cookie->SetString(extension_cookies_api_constants::kStoreIdKey,
+ "a store id!!");
+ dict.Set(extension_cookies_api_constants::kCookieKey, cookie);
+
+ EXPECT_CALL(cookie_events_funnel, SendEvent(
+ StrEq(extension_cookies_api_constants::kOnChanged), ValuesEqual(&dict))).
+ WillOnce(Return(S_OK));
+ EXPECT_HRESULT_SUCCEEDED(
+ cookie_events_funnel.OnChanged(removed, cookie_info));
+}
+
+} // namespace
diff --git a/ceee/ie/plugin/bho/dom_utils.cc b/ceee/ie/plugin/bho/dom_utils.cc
new file mode 100644
index 0000000..714e108
--- /dev/null
+++ b/ceee/ie/plugin/bho/dom_utils.cc
@@ -0,0 +1,126 @@
+// 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.
+//
+// A collection of utility functions that interrogate or mutate the IE DOM.
+#include "ceee/ie/plugin/bho/dom_utils.h"
+
+#include <atlbase.h>
+
+#include "ceee/common/com_utils.h"
+#include "base/logging.h"
+
+HRESULT DomUtils::InjectStyleTag(IHTMLDocument2* document,
+ IHTMLDOMNode* head_node,
+ const wchar_t* code) {
+ DCHECK(document != NULL);
+ DCHECK(head_node != NULL);
+
+ CComPtr<IHTMLElement> elem;
+ HRESULT hr = document->createElement(CComBSTR(L"style"), &elem);
+ if (FAILED(hr)) {
+ LOG(ERROR) << "Could not create style element " << com::LogHr(hr);
+ return hr;
+ }
+
+ CComQIPtr<IHTMLStyleElement> style_elem(elem);
+ DCHECK(style_elem != NULL) <<
+ "Could not QueryInterface for IHTMLStyleElement";
+
+ hr = style_elem->put_type(CComBSTR(L"text/css"));
+ DCHECK(SUCCEEDED(hr)) << "Could not set type of style element" <<
+ com::LogHr(hr);
+
+ CComPtr<IHTMLStyleSheet> style_sheet;
+ hr = style_elem->get_styleSheet(&style_sheet);
+ DCHECK(SUCCEEDED(hr)) << "Could not get styleSheet of style element." <<
+ com::LogHr(hr);
+
+ hr = style_sheet->put_cssText(CComBSTR(code));
+ if (FAILED(hr)) {
+ LOG(ERROR) << "Could not set cssText of styleSheet." << com::LogHr(hr);
+ return hr;
+ }
+
+ CComQIPtr<IHTMLDOMNode> style_node(style_elem);
+ DCHECK(style_node != NULL) << "Could not query interface for IHTMLDomNode.";
+
+ CComPtr<IHTMLDOMNode> dummy;
+ hr = head_node->appendChild(style_node, &dummy);
+ if (FAILED(hr))
+ LOG(ERROR) << "Could not append style node to head node." << com::LogHr(hr);
+
+ return hr;
+}
+
+HRESULT DomUtils::GetHeadNode(IHTMLDocument* document,
+ IHTMLDOMNode** head_node) {
+ DCHECK(document != NULL);
+ DCHECK(head_node != NULL && *head_node == NULL);
+
+ // Find the HEAD element through document.getElementsByTagName.
+ CComQIPtr<IHTMLDocument3> document3(document);
+ CComPtr<IHTMLElementCollection> head_elements;
+ DCHECK(document3 != NULL); // Should be there on IE >= 5
+ if (document3 == NULL) {
+ LOG(ERROR) << L"Unable to retrieve IHTMLDocument3 interface";
+ return E_NOINTERFACE;
+ }
+ HRESULT hr = GetElementsByTagName(document3, CComBSTR(L"head"),
+ &head_elements, NULL);
+ if (FAILED(hr)) {
+ LOG(ERROR) << "Could not retrieve head elements collection "
+ << com::LogHr(hr);
+ return hr;
+ }
+
+ return GetElementFromCollection(head_elements, 0, IID_IHTMLDOMNode,
+ reinterpret_cast<void**>(head_node));
+}
+
+HRESULT DomUtils::GetElementsByTagName(IHTMLDocument3* document,
+ BSTR tag_name,
+ IHTMLElementCollection** elements,
+ long* length) {
+ DCHECK(document != NULL);
+ DCHECK(tag_name != NULL);
+ DCHECK(elements != NULL && *elements == NULL);
+
+ HRESULT hr = document->getElementsByTagName(tag_name, elements);
+ if (FAILED(hr) || *elements == NULL) {
+ hr = com::AlwaysError(hr);
+ LOG(ERROR) << "Could not retrieve elements collection " << com::LogHr(hr);
+ return hr;
+ }
+
+ if (length != NULL) {
+ hr = (*elements)->get_length(length);
+ if (FAILED(hr)) {
+ (*elements)->Release();
+ *elements = NULL;
+ LOG(ERROR) << "Could not retrieve collection length " << com::LogHr(hr);
+ }
+ }
+ return hr;
+}
+
+HRESULT DomUtils::GetElementFromCollection(IHTMLElementCollection* collection,
+ long index,
+ REFIID id,
+ void** element) {
+ DCHECK(collection != NULL);
+ DCHECK(element != NULL && *element == NULL);
+
+ CComPtr<IDispatch> item;
+ CComVariant index_variant(index, VT_I4);
+ HRESULT hr = collection->item(index_variant, index_variant, &item);
+ // As per http://msdn.microsoft.com/en-us/library/aa703930(VS.85).aspx
+ // item may still be NULL even if S_OK is returned.
+ if (FAILED(hr) || item == NULL) {
+ hr = com::AlwaysError(hr);
+ LOG(ERROR) << "Could not access item " << com::LogHr(hr);
+ return hr;
+ }
+
+ return item->QueryInterface(id, element);
+}
diff --git a/ceee/ie/plugin/bho/dom_utils.h b/ceee/ie/plugin/bho/dom_utils.h
new file mode 100644
index 0000000..e988a68
--- /dev/null
+++ b/ceee/ie/plugin/bho/dom_utils.h
@@ -0,0 +1,53 @@
+// 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.
+//
+// A collection of utility functions that interrogate or mutate the IE DOM.
+#ifndef CEEE_IE_PLUGIN_BHO_DOM_UTILS_H_
+#define CEEE_IE_PLUGIN_BHO_DOM_UTILS_H_
+
+#include <mshtml.h>
+#include <string>
+
+// This class is a namespace for hosting utility functions that interrogate
+// or mutate the IE DOM.
+// TODO(siggi@chromium.org): should this be a namespace?
+class DomUtils {
+ public:
+ // Inject a style tag into the head of the document.
+ // @param document The DOM document object.
+ // @param head_node The HEAD DOM node from |document|.
+ // @param code The CSS code to inject.
+ static HRESULT InjectStyleTag(IHTMLDocument2* document,
+ IHTMLDOMNode* head_node,
+ const wchar_t* code);
+
+ // Retrieve the "HEAD" DOM node from @p document.
+ // @param document the DOM document object.
+ // @param head_node on success returns the "HEAD" DOM node.
+ static HRESULT GetHeadNode(IHTMLDocument* document, IHTMLDOMNode** head_node);
+
+ // Retrieves all elements with the specified tag name from the document.
+ // @param document The document object.
+ // @param tag_name The tag name.
+ // @param elements On success returns a collection of elements.
+ // @param length On success returns the number of elements in the collection.
+ // The caller could pass in NULL to indicate that length
+ // information is not needed.
+ static HRESULT GetElementsByTagName(IHTMLDocument3* document,
+ BSTR tag_name,
+ IHTMLElementCollection** elements,
+ long* length);
+
+ // Retrieves an element from the collection.
+ // @param collection The collection object.
+ // @param index The zero-based index of the element to retrieve.
+ // @param id The interface ID of the element to retrieve.
+ // @param element On success returns the element.
+ static HRESULT GetElementFromCollection(IHTMLElementCollection* collection,
+ long index,
+ REFIID id,
+ void** element);
+};
+
+#endif // CEEE_IE_PLUGIN_BHO_DOM_UTILS_H_
diff --git a/ceee/ie/plugin/bho/dom_utils_unittest.cc b/ceee/ie/plugin/bho/dom_utils_unittest.cc
new file mode 100644
index 0000000..180c1cc
--- /dev/null
+++ b/ceee/ie/plugin/bho/dom_utils_unittest.cc
@@ -0,0 +1,197 @@
+// 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.
+//
+// Unittests for DOM utils.
+#include "ceee/ie/plugin/bho/dom_utils.h"
+#include "gtest/gtest.h"
+#include "gmock/gmock.h"
+#include "ceee/common/initializing_coclass.h"
+#include "ceee/testing/utils/mshtml_mocks.h"
+#include "ceee/testing/utils/test_utils.h"
+
+namespace {
+
+using testing::_;
+using testing::CopyInterfaceToArgument;
+using testing::DoAll;
+using testing::Return;
+using testing::SetArgumentPointee;
+using testing::StrictMock;
+using testing::StrEq;
+using testing::VariantEq;
+
+class MockDocument
+ : public CComObjectRootEx<CComSingleThreadModel>,
+ public InitializingCoClass<MockDocument>,
+ public StrictMock<IHTMLDocument2MockImpl>,
+ public StrictMock<IHTMLDocument3MockImpl> {
+ public:
+ BEGIN_COM_MAP(MockDocument)
+ COM_INTERFACE_ENTRY2(IDispatch, IHTMLDocument2)
+ COM_INTERFACE_ENTRY(IHTMLDocument)
+ COM_INTERFACE_ENTRY(IHTMLDocument2)
+ COM_INTERFACE_ENTRY(IHTMLDocument3)
+ END_COM_MAP()
+
+ HRESULT Initialize(MockDocument** self) {
+ *self = this;
+ return S_OK;
+ }
+};
+
+class MockElementNode
+ : public CComObjectRootEx<CComSingleThreadModel>,
+ public InitializingCoClass<MockElementNode>,
+ public StrictMock<IHTMLElementMockImpl>,
+ public StrictMock<IHTMLDOMNodeMockImpl> {
+ BEGIN_COM_MAP(MockElementNode)
+ COM_INTERFACE_ENTRY2(IDispatch, IHTMLElement)
+ COM_INTERFACE_ENTRY(IHTMLElement)
+ COM_INTERFACE_ENTRY(IHTMLDOMNode)
+ END_COM_MAP()
+
+ HRESULT Initialize(MockElementNode** self) {
+ *self = this;
+ return S_OK;
+ }
+};
+
+class MockStyleElementNode
+ : public CComObjectRootEx<CComSingleThreadModel>,
+ public InitializingCoClass<MockStyleElementNode>,
+ public StrictMock<IHTMLElementMockImpl>,
+ public StrictMock<IHTMLStyleElementMockImpl>,
+ public StrictMock<IHTMLDOMNodeMockImpl> {
+ BEGIN_COM_MAP(MockStyleElementNode)
+ COM_INTERFACE_ENTRY2(IDispatch, IHTMLElement)
+ COM_INTERFACE_ENTRY(IHTMLElement)
+ COM_INTERFACE_ENTRY(IHTMLStyleElement)
+ COM_INTERFACE_ENTRY(IHTMLDOMNode)
+ END_COM_MAP()
+
+ HRESULT Initialize(MockStyleElementNode** self) {
+ *self = this;
+ return S_OK;
+ }
+};
+
+class MockStyleSheet
+ : public CComObjectRootEx<CComSingleThreadModel>,
+ public InitializingCoClass<MockStyleSheet>,
+ public StrictMock<IHTMLStyleSheetMockImpl> {
+ BEGIN_COM_MAP(MockStyleSheet)
+ COM_INTERFACE_ENTRY(IDispatch)
+ COM_INTERFACE_ENTRY(IHTMLStyleSheet)
+ END_COM_MAP()
+
+ HRESULT Initialize(MockStyleSheet** self) {
+ *self = this;
+ return S_OK;
+ }
+};
+
+class DomUtilsTest: public testing::Test {
+ public:
+ virtual void SetUp() {
+ ASSERT_HRESULT_SUCCEEDED(
+ MockDocument::CreateInitialized(&document_, &document_keeper_));
+ ASSERT_HRESULT_SUCCEEDED(
+ MockElementNode::CreateInitialized(&head_node_, &head_node_keeper_));
+ }
+
+ virtual void TearDown() {
+ document_ = NULL;
+ document_keeper_.Release();
+ head_node_ = NULL;
+ head_node_keeper_.Release();
+ }
+
+ protected:
+ MockDocument* document_;
+ CComPtr<IHTMLDocument2> document_keeper_;
+
+ MockElementNode* head_node_;
+ CComPtr<IHTMLDOMNode> head_node_keeper_;
+};
+
+TEST_F(DomUtilsTest, InjectStyleTag) {
+ MockStyleElementNode* style_node;
+ CComPtr<IHTMLElement> style_node_keeper;
+ ASSERT_HRESULT_SUCCEEDED(
+ MockStyleElementNode::CreateInitialized(&style_node, &style_node_keeper));
+
+ MockStyleSheet* style_sheet;
+ CComPtr<IHTMLStyleSheet> style_sheet_keeper;
+ ASSERT_HRESULT_SUCCEEDED(
+ MockStyleSheet::CreateInitialized(&style_sheet, &style_sheet_keeper));
+
+ EXPECT_CALL(*document_, createElement(StrEq(L"style"), _)).
+ WillOnce(DoAll(CopyInterfaceToArgument<1>(style_node_keeper),
+ Return(S_OK)));
+
+ EXPECT_CALL(*style_node, put_type(StrEq(L"text/css"))).
+ WillOnce(Return(S_OK));
+
+ EXPECT_CALL(*style_node, get_styleSheet(_)).
+ WillOnce(DoAll(CopyInterfaceToArgument<0>(style_sheet_keeper),
+ Return(S_OK)));
+
+ EXPECT_CALL(*style_sheet, put_cssText(StrEq(L"foo"))).WillOnce(Return(S_OK));
+
+ EXPECT_CALL(*head_node_, appendChild(style_node, _)).
+ WillOnce(Return(S_OK));
+
+ ASSERT_HRESULT_SUCCEEDED(
+ DomUtils::InjectStyleTag(document_keeper_, head_node_keeper_, L"foo"));
+}
+
+class MockElementCollection
+ : public CComObjectRootEx<CComSingleThreadModel>,
+ public InitializingCoClass<MockElementCollection>,
+ public StrictMock<IHTMLElementCollectionMockImpl> {
+ public:
+ BEGIN_COM_MAP(MockElementCollection)
+ COM_INTERFACE_ENTRY(IDispatch)
+ COM_INTERFACE_ENTRY(IHTMLElementCollection)
+ END_COM_MAP()
+
+ HRESULT Initialize(MockElementCollection** self) {
+ *self = this;
+ return S_OK;
+ }
+};
+
+TEST_F(DomUtilsTest, GetHeadNode) {
+ MockElementCollection* collection;
+ CComPtr<IHTMLElementCollection> collection_keeper;
+ ASSERT_HRESULT_SUCCEEDED(
+ MockElementCollection::CreateInitialized(&collection,
+ &collection_keeper));
+
+ EXPECT_CALL(*document_, getElementsByTagName(StrEq(L"head"), _))
+ .WillRepeatedly(DoAll(CopyInterfaceToArgument<1>(collection_keeper),
+ Return(S_OK)));
+
+ CComVariant zero(0L);
+ // First verify that we gracefuly fail when there are no heads.
+ // bb2333090
+ EXPECT_CALL(*collection, item(_, VariantEq(zero), _))
+ .WillOnce(Return(S_OK));
+
+ CComPtr<IHTMLDOMNode> head_node;
+ ASSERT_HRESULT_FAILED(DomUtils::GetHeadNode(document_, &head_node));
+ ASSERT_EQ(static_cast<IHTMLDOMNode*>(NULL), head_node);
+
+ // And now properly return a valid head node.
+ EXPECT_CALL(*collection, item(_, VariantEq(zero), _))
+ .WillOnce(DoAll(CopyInterfaceToArgument<2>(
+ static_cast<IDispatch*>(head_node_keeper_)), Return(S_OK)));
+
+ ASSERT_HRESULT_SUCCEEDED(
+ DomUtils::GetHeadNode(document_, &head_node));
+
+ ASSERT_TRUE(head_node == head_node_);
+}
+
+} // namespace
diff --git a/ceee/ie/plugin/bho/events_funnel.cc b/ceee/ie/plugin/bho/events_funnel.cc
new file mode 100644
index 0000000..dfa0cdc
--- /dev/null
+++ b/ceee/ie/plugin/bho/events_funnel.cc
@@ -0,0 +1,42 @@
+// 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.
+//
+// Common base class for funnels of Chrome Extension events that originate
+// from the BHO and are sent to the Broker.
+
+#include "ceee/ie/plugin/bho/events_funnel.h"
+
+#include "base/json/json_writer.h"
+#include "base/logging.h"
+#include "base/values.h"
+#include "ceee/ie/common/ceee_module_util.h"
+
+
+EventsFunnel::EventsFunnel(bool keep_broker_alive)
+ : keep_broker_alive_(keep_broker_alive) {
+ if (keep_broker_alive_)
+ ceee_module_util::AddRefModuleWorkerThread();
+}
+
+EventsFunnel::~EventsFunnel() {
+ if (keep_broker_alive_)
+ ceee_module_util::ReleaseModuleWorkerThread();
+}
+
+HRESULT EventsFunnel::SendEvent(const char* event_name,
+ const Value& event_args) {
+ // Event arguments for FireEventToBroker always need to be stored in a list.
+ std::string event_args_str;
+ if (event_args.IsType(Value::TYPE_LIST)) {
+ base::JSONWriter::Write(&event_args, false, &event_args_str);
+ } else {
+ ListValue list;
+ list.Append(event_args.DeepCopy());
+ base::JSONWriter::Write(&list, false, &event_args_str);
+ }
+
+ EventsFunnel thread_locker(!keep_broker_alive_);
+ ceee_module_util::FireEventToBroker(event_name, event_args_str);
+ return S_OK;
+}
diff --git a/ceee/ie/plugin/bho/events_funnel.h b/ceee/ie/plugin/bho/events_funnel.h
new file mode 100644
index 0000000..add53f8
--- /dev/null
+++ b/ceee/ie/plugin/bho/events_funnel.h
@@ -0,0 +1,38 @@
+// 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.
+//
+// Common base class for funnels of Chrome Extension events that originate
+// from the BHO.
+
+#ifndef CEEE_IE_PLUGIN_BHO_EVENTS_FUNNEL_H_
+#define CEEE_IE_PLUGIN_BHO_EVENTS_FUNNEL_H_
+
+#include <windows.h>
+
+#include "base/basictypes.h"
+
+class Value;
+
+// Defines a base class for sending events to the Broker.
+class EventsFunnel {
+ protected:
+ // @param keep_broker_alive If true broker will be alive during
+ // lifetime of this funnel, otherwise only during SendEvent.
+ explicit EventsFunnel(bool keep_broker_alive);
+ virtual ~EventsFunnel();
+
+ // Send the given event to the Broker.
+ // @param event_name The name of the event.
+ // @param event_args The arguments to be sent with the event.
+ // protected virtual for testability...
+ virtual HRESULT SendEvent(const char* event_name, const Value& event_args);
+
+ private:
+ // If true constructor/destructor of class increments/decrements ref counter
+ // of broker thread. Otherwise SendEvent does it for every event.
+ const bool keep_broker_alive_;
+ DISALLOW_COPY_AND_ASSIGN(EventsFunnel);
+};
+
+#endif // CEEE_IE_PLUGIN_BHO_EVENTS_FUNNEL_H_
diff --git a/ceee/ie/plugin/bho/events_funnel_unittest.cc b/ceee/ie/plugin/bho/events_funnel_unittest.cc
new file mode 100644
index 0000000..64f7061
--- /dev/null
+++ b/ceee/ie/plugin/bho/events_funnel_unittest.cc
@@ -0,0 +1,62 @@
+// 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.
+//
+// Unit tests for EventsFunnel.
+
+#include "base/json/json_writer.h"
+#include "base/values.h"
+#include "ceee/ie/common/ceee_module_util.h"
+#include "ceee/ie/plugin/bho/events_funnel.h"
+#include "ceee/testing/utils/mock_static.h"
+#include "gmock/gmock.h"
+#include "gtest/gtest.h"
+
+namespace {
+
+using testing::StrEq;
+
+MOCK_STATIC_CLASS_BEGIN(MockCeeeModuleUtils)
+ MOCK_STATIC_INIT_BEGIN(MockCeeeModuleUtils)
+ MOCK_STATIC_INIT2(ceee_module_util::FireEventToBroker,
+ FireEventToBroker);
+ MOCK_STATIC_INIT_END()
+ MOCK_STATIC2(void, , FireEventToBroker, const std::string&,
+ const std::string&);
+MOCK_STATIC_CLASS_END(MockCeeeModuleUtils)
+
+// Test subclass used to provide access to protected functionality in the
+// EventsFunnel class.
+class TestEventsFunnel : public EventsFunnel {
+ public:
+ TestEventsFunnel() : EventsFunnel(true) {}
+
+ HRESULT CallSendEvent(const char* event_name, const Value& event_args) {
+ return SendEvent(event_name, event_args);
+ }
+};
+
+TEST(EventsFunnelTest, SendEvent) {
+ TestEventsFunnel events_funnel;
+ MockCeeeModuleUtils mock_ceee_module;
+
+ static const char* kEventName = "MADness";
+ DictionaryValue event_args;
+ event_args.SetInteger("Answer to the Ultimate Question of Life,"
+ "the Universe, and Everything", 42);
+ event_args.SetString("AYBABTU", "All your base are belong to us");
+ event_args.SetReal("www.unrealtournament.com", 3.0);
+
+ ListValue args_list;
+ args_list.Append(event_args.DeepCopy());
+
+ std::string event_args_str;
+ base::JSONWriter::Write(&args_list, false, &event_args_str);
+
+ EXPECT_CALL(mock_ceee_module, FireEventToBroker(StrEq(kEventName),
+ StrEq(event_args_str))).Times(1);
+ EXPECT_HRESULT_SUCCEEDED(
+ events_funnel.CallSendEvent(kEventName, event_args));
+}
+
+} // namespace
diff --git a/ceee/ie/plugin/bho/executor.cc b/ceee/ie/plugin/bho/executor.cc
new file mode 100644
index 0000000..d7719f1
--- /dev/null
+++ b/ceee/ie/plugin/bho/executor.cc
@@ -0,0 +1,771 @@
+// 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.
+//
+// CeeeExecutor implementation
+//
+// We use interfaces named ITabWindowManager and ITabWindow
+// (documented at
+// http://www.geoffchappell.com/viewer.htm?doc=studies/windows/ie/ieframe/interfaces/itabwindowmanager.htm
+// and
+// http://www.geoffchappell.com/viewer.htm?doc=studies/windows/ie/ieframe/interfaces/itabwindow.htm)
+// to help implement this API. These are available in IE7, IE8 and
+// IE9 (with minor differences between browser versions), so we use a
+// wrapper class that takes care of delegating to the available
+// interface.
+//
+// Alternate approach considered: Using the IAccessible interface to find out
+// about the order (indexes) of tabs, create new tabs and close tabs in a
+// reliable way. The main drawback was that currently, the only way we've
+// found to go from the IAccessible interface to the tab window itself (and
+// hence the IWebBrowser2 object) is to match the description
+// fetched using IAccessible::get_accDescription(), which contains the title
+// and URL of the tab, to the title and URL retrieved via the IWebBrowser2
+// object. This limitation would mean that tab indexes could be incorrect
+// when two or more tabs are navigated to the same page (and have the same
+// title).
+
+#include "ceee/ie/plugin/bho/executor.h"
+
+#include <atlcomcli.h>
+#include <mshtml.h>
+#include <wininet.h>
+
+#include <vector>
+
+#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"
+#include "ceee/common/window_utils.h"
+#include "ceee/common/windows_constants.h"
+#include "ceee/ie/common/ie_util.h"
+#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 "chrome_frame/utils.h"
+
+#include "broker_lib.h" // NOLINT
+
+namespace {
+
+// Static per-process variable to indicate whether the process has been
+// registered as a cookie store yet.
+static bool g_cookie_store_is_registered = false;
+
+// INTERNET_COOKIE_HTTPONLY is only available for IE8 or later, which allows
+// Wininet API to read cookies that are marked as HTTPOnly.
+#ifndef INTERNET_COOKIE_HTTPONLY
+#define INTERNET_COOKIE_HTTPONLY 0x00002000
+#endif
+
+// Default maximum height of the infobar. From the experience with the design of
+// infobars this value is found to provide enough space and not to be too
+// restrictive - for example this is approximately the height of Chrome infobar.
+const int kMaxInfobarHeight = 39;
+} // namespace
+
+// The message which will be posted to the destination thread.
+const UINT CeeeExecutorCreator::kCreateWindowExecutorMessage =
+ ::RegisterWindowMessage(
+ L"CeeeExecutor{D91E23A6-1C2E-4984-8528-1F1771004F37}");
+
+CeeeExecutorCreator::CeeeExecutorCreator()
+ : current_thread_id_(0), hook_(NULL) {
+}
+
+void CeeeExecutorCreator::FinalRelease() {
+ if (hook_ != NULL) {
+ HRESULT hr = Teardown(current_thread_id_);
+ DCHECK(SUCCEEDED(hr)) << "Self-Tearing down. " << com::LogHr(hr);
+ }
+}
+
+HRESULT CeeeExecutorCreator::CreateWindowExecutor(long thread_id,
+ CeeeWindowHandle window) {
+ DCHECK_EQ(0, current_thread_id_);
+ current_thread_id_ = thread_id;
+ // Verify we're a window, not just a tab.
+ DCHECK_EQ(window_utils::GetTopLevelParent(reinterpret_cast<HWND>(window)),
+ reinterpret_cast<HWND>(window));
+
+ hook_ = ::SetWindowsHookEx(WH_GETMESSAGE, GetMsgProc,
+ static_cast<HINSTANCE>(_AtlBaseModule.GetModuleInstance()), thread_id);
+ if (hook_ == NULL) {
+ LOG(ERROR) << "Couldn't hook into thread: " << thread_id << " " <<
+ com::LogWe();
+ current_thread_id_ = 0;
+ return E_FAIL;
+ }
+
+ // We unfortunately can't Send a synchronous message here.
+ // If we do, any calls back to the broker fail with the following error:
+ // "An outgoing call cannot be made since the application is dispatching an
+ // input synchronous call."
+ BOOL success = ::PostThreadMessage(thread_id, kCreateWindowExecutorMessage,
+ 0, static_cast<LPARAM>(window));
+ if (success)
+ return S_OK;
+ else
+ return HRESULT_FROM_WIN32(::GetLastError());
+}
+
+HRESULT CeeeExecutorCreator::Teardown(long thread_id) {
+ if (hook_ != NULL) {
+ DCHECK(current_thread_id_ == thread_id);
+ current_thread_id_ = 0;
+ // Don't return the failure since it may fail when we get called after
+ // the destination thread/module we hooked to vanished into thin air.
+ BOOL success = ::UnhookWindowsHookEx(hook_);
+ LOG_IF(INFO, !success) << "Failed to unhook. " << com::LogWe();
+ hook_ = NULL;
+ }
+ return S_OK;
+}
+
+LRESULT CeeeExecutorCreator::GetMsgProc(int code, WPARAM wparam,
+ LPARAM lparam) {
+ if (code == HC_ACTION) {
+ MSG* message_data = reinterpret_cast<MSG*>(lparam);
+ if (message_data != NULL &&
+ message_data->message == kCreateWindowExecutorMessage) {
+ // Remove the message from the queue so that we don't get called again
+ // while we wait for CoCreateInstance to complete, since COM will run
+ // a message loop in there. And some loop don't PM_REMOVE us (some do).
+ if (wparam != PM_REMOVE) {
+ MSG dummy;
+ BOOL success = ::PeekMessage(&dummy, NULL, kCreateWindowExecutorMessage,
+ kCreateWindowExecutorMessage, PM_REMOVE);
+ DCHECK(success) << "Peeking Hook Message. " << com::LogWe();
+ // We must return here since we will get called again from within
+ // PeekMessage, and with PM_REMOVE this time (so no, we won't
+ // infinitely recurse :-), so this ensure that we get called just once.
+ return 0;
+ }
+
+ CComPtr<ICeeeWindowExecutor> executor;
+ HRESULT hr = executor.CoCreateInstance(CLSID_CeeeExecutor);
+ LOG_IF(ERROR, FAILED(hr)) << "Failed to create Executor, hr=" <<
+ com::LogHr(hr);
+ DCHECK(SUCCEEDED(hr)) << "CoCreating Executor. " << com::LogHr(hr);
+
+ if (SUCCEEDED(hr)) {
+ CeeeWindowHandle window = static_cast<CeeeWindowHandle>(
+ message_data->lParam);
+ if (window) {
+ hr = executor->Initialize(window);
+ LOG_IF(ERROR, FAILED(hr)) << "Failed to create Executor, hr=" <<
+ com::LogHr(hr);
+ DCHECK(SUCCEEDED(hr)) << "CoCreating Executor. " << com::LogHr(hr);
+ }
+
+ CComPtr<ICeeeBrokerRegistrar> broker;
+ hr = broker.CoCreateInstance(CLSID_CeeeBroker);
+ LOG_IF(ERROR, FAILED(hr)) << "Failed to create broker, hr=" <<
+ com::LogHr(hr);
+ DCHECK(SUCCEEDED(hr)) << "CoCreating Broker. " << com::LogHr(hr);
+
+ if (SUCCEEDED(hr)) {
+ hr = broker->RegisterWindowExecutor(::GetCurrentThreadId(), executor);
+ DCHECK(SUCCEEDED(hr)) << "Registering Executor. " << com::LogHr(hr);
+ }
+ }
+ return 0;
+ }
+ }
+ return ::CallNextHookEx(NULL, code, wparam, lparam);
+}
+
+HRESULT CeeeExecutor::Initialize(CeeeWindowHandle hwnd) {
+ DCHECK(hwnd);
+ hwnd_ = reinterpret_cast<HWND>(hwnd);
+
+ HRESULT hr = EnsureWindowThread();
+ if (FAILED(hr)) {
+ return hr;
+ }
+
+ // If this is tab window then create the infobab manager.
+ // TODO(mad@chromium.org): We are starting to need to have different classes
+ // for the different executors.
+ if (window_utils::GetTopLevelParent(hwnd_) != hwnd_)
+ infobar_manager_.reset(new infobar_api::InfobarManager(hwnd_));
+
+ return S_OK;
+}
+
+HRESULT CeeeExecutor::GetWebBrowser(IWebBrowser2** browser) {
+ DCHECK(browser);
+ CComPtr<IFrameEventHandlerHost> frame_handler_host;
+ HRESULT hr = GetSite(IID_IFrameEventHandlerHost,
+ reinterpret_cast<void**>(&frame_handler_host));
+ if (FAILED(hr)) {
+ NOTREACHED() << "No frame event handler host for executor. " <<
+ com::LogHr(hr);
+ return hr;
+ }
+ return frame_handler_host->GetTopLevelBrowser(browser);
+}
+
+STDMETHODIMP CeeeExecutor::GetWindow(BOOL populate_tabs,
+ CeeeWindowInfo* window_info) {
+ DCHECK(window_info);
+ HRESULT hr = EnsureWindowThread();
+ if (FAILED(hr)) {
+ return hr;
+ }
+
+ // Zero the window info.
+ ZeroMemory(window_info, sizeof(CeeeWindowInfo));
+
+ // The window we use to represent IE windows is the top-level (parentless)
+ // frame for the collection of windows that make up a logical "window" in the
+ // sense of the chrome.window.* API. Therefore, compare the provided window
+ // with the top-level parent of the current foreground (focused) window to
+ // see if the logical window has focus.
+ HWND top_level = window_utils::GetTopLevelParent(::GetForegroundWindow());
+ window_info->focused = (top_level == hwnd_);
+
+ if (!::GetWindowRect(hwnd_, &window_info->rect)) {
+ DWORD we = ::GetLastError();
+ DCHECK(false) << "GetWindowRect failed " << com::LogWe(we);
+ return HRESULT_FROM_WIN32(we);
+ }
+
+ if (populate_tabs) {
+ return GetTabs(&window_info->tab_list);
+ }
+
+ return S_OK;
+}
+
+BOOL CALLBACK CeeeExecutor::GetTabsEnumProc(HWND window, LPARAM param) {
+ if (window_utils::IsWindowClass(window, windows::kIeTabWindowClass)) {
+ std::vector<HWND>* tab_windows =
+ reinterpret_cast<std::vector<HWND>*>(param);
+ DCHECK(tab_windows);
+ tab_windows->push_back(window);
+ }
+ return TRUE;
+}
+
+STDMETHODIMP CeeeExecutor::GetTabs(BSTR* tab_list) {
+ DCHECK(tab_list);
+ HRESULT hr = EnsureWindowThread();
+ if (FAILED(hr)) {
+ return hr;
+ }
+
+ scoped_ptr<TabWindowManager> manager;
+ hr = CreateTabWindowManager(hwnd_, &manager);
+ DCHECK(SUCCEEDED(hr)) << "Failed to initialize TabWindowManager.";
+ if (FAILED(hr)) {
+ return hr;
+ }
+
+ std::vector<HWND> tab_windows;
+ ::EnumChildWindows(hwnd_, GetTabsEnumProc,
+ reinterpret_cast<LPARAM>(&tab_windows));
+ // We don't DCHECK that we found as many windows as the tab window manager
+ // GetCount(), because there are cases where it sees more than we do... :-(
+ // When we navigate to a new page, IE8 actually creates a new temporary page
+ // (not sure if it always do it, or just in some cases), and the
+ // TabWindowManager actually count this temporary tab, even though we can't
+ // see it when we enumerate the kIeTabWindowClass windows.
+ ListValue tabs_list;
+ for (size_t index = 0; index < tab_windows.size(); ++index) {
+ HWND tab_window = tab_windows[index];
+ long tab_index = -1;
+ hr = manager->IndexFromHWND(tab_window, &tab_index);
+ if (SUCCEEDED(hr)) {
+ tabs_list.Append(Value::CreateIntegerValue(
+ reinterpret_cast<int>(tab_window)));
+ tabs_list.Append(Value::CreateIntegerValue(static_cast<int>(tab_index)));
+ // The tab window may have died by the time we get here.
+ // Simply ignore that tab in this case.
+ } else if (::IsWindow(tab_window)) {
+ // But if it's still alive, then something wrong happened.
+ return hr;
+ }
+ }
+ std::string tabs_json;
+ base::JSONWriter::Write(&tabs_list, false, &tabs_json);
+ *tab_list = ::SysAllocString(CA2W(tabs_json.c_str()));
+ if (*tab_list == NULL) {
+ return E_OUTOFMEMORY;
+ }
+ return S_OK;
+}
+
+STDMETHODIMP CeeeExecutor::UpdateWindow(
+ long left, long top, long width, long height,
+ CeeeWindowInfo* window_info) {
+ DCHECK(window_info);
+ HRESULT hr = EnsureWindowThread();
+ if (FAILED(hr)) {
+ return hr;
+ }
+
+ // Any part of the window rect can optionally be set by the caller.
+ // We need the original rect if only some dimensions are set by caller.
+ RECT rect = { 0 };
+ BOOL success = ::GetWindowRect(hwnd_, &rect);
+ DCHECK(success);
+
+ if (left == -1) {
+ left = rect.left;
+ }
+ if (top == -1) {
+ top = rect.top;
+ }
+ if (width == -1) {
+ width = rect.right - left;
+ }
+ if (height == -1) {
+ height = rect.bottom - top;
+ }
+
+ // In IE8 this would yield ERROR_ACCESS_DENIED when called from another
+ // thread/process and protected mode is enabled, because the process owning
+ // the frame window is medium integrity. See UIPI in MSDN.
+ // So this is why we must do this via an injected executor.
+ success = ::MoveWindow(hwnd_, left, top, width, height, TRUE);
+ DCHECK(success) << "Failed to move the window to the update rect. " <<
+ com::LogWe();
+ return GetWindow(FALSE, window_info);
+}
+
+STDMETHODIMP CeeeExecutor::RemoveWindow() {
+ HRESULT hr = EnsureWindowThread();
+ if (FAILED(hr)) {
+ return hr;
+ }
+
+ scoped_ptr<TabWindowManager> manager;
+ hr = CreateTabWindowManager(hwnd_, &manager);
+ DCHECK(SUCCEEDED(hr)) << "Failed to initialize TabWindowManager.";
+ if (FAILED(hr)) {
+ return hr;
+ }
+ return manager->CloseAllTabs();
+}
+
+STDMETHODIMP CeeeExecutor::GetTabInfo(CeeeTabInfo* tab_info) {
+ DCHECK(tab_info);
+ HRESULT hr = EnsureWindowThread();
+ if (FAILED(hr)) {
+ return hr;
+ }
+
+ // Zero the info.
+ ZeroMemory(tab_info, sizeof(CeeeTabInfo));
+
+ CComPtr<IFrameEventHandlerHost> frame_handler_host;
+ hr = GetSite(IID_IFrameEventHandlerHost,
+ reinterpret_cast<void**>(&frame_handler_host));
+ if (FAILED(hr)) {
+ NOTREACHED() << "No frame event handler host for executor. " <<
+ com::LogHr(hr);
+ return hr;
+ }
+ READYSTATE ready_state = READYSTATE_UNINITIALIZED;
+ hr = frame_handler_host->GetReadyState(&ready_state);
+ if (FAILED(hr)) {
+ NOTREACHED() << "Can't get ReadyState, Wazzup???. " << com::LogHr(hr);
+ return hr;
+ }
+
+ tab_info->status = kCeeeTabStatusComplete;
+ if (ready_state != READYSTATE_COMPLETE) {
+ // Chrome only has two states, so all incomplete states are "loading".
+ tab_info->status = kCeeeTabStatusLoading;
+ }
+
+ CComPtr<IWebBrowser2> browser;
+ hr = frame_handler_host->GetTopLevelBrowser(&browser);
+ if (FAILED(hr)) {
+ NOTREACHED();
+ return hr;
+ }
+
+ hr = browser->get_LocationURL(&tab_info->url);
+ DCHECK(SUCCEEDED(hr)) << "get_LocationURL()" << com::LogHr(hr);
+
+ hr = browser->get_LocationName(&tab_info->title);
+ DCHECK(SUCCEEDED(hr)) << "get_LocationName()" << com::LogHr(hr);
+
+ // TODO(mad@chromium.org): Favicon support (see Chrome
+ // implementation, kFavIconUrlKey). AFAJoiCT, this is only set if
+ // there is a <link rel="icon" ...> tag, so we could parse this out
+ // of the IHTMLDocument2::get_links() collection.
+ tab_info->fav_icon_url = NULL;
+ return S_OK;
+}
+
+STDMETHODIMP CeeeExecutor::GetTabIndex(CeeeWindowHandle tab, long* index) {
+ DCHECK(index);
+ HRESULT hr = EnsureWindowThread();
+ if (FAILED(hr)) {
+ return hr;
+ }
+
+ scoped_ptr<TabWindowManager> manager;
+ hr = CreateTabWindowManager(hwnd_, &manager);
+ DCHECK(SUCCEEDED(hr)) << "Failed to initialize TabWindowManager.";
+ if (FAILED(hr)) {
+ return hr;
+ }
+
+ hr = manager->IndexFromHWND(reinterpret_cast<HWND>(tab), index);
+ DCHECK(SUCCEEDED(hr)) << "Couldn't get index for tab: " <<
+ tab << ", " << com::LogHr(hr);
+ return hr;
+}
+
+STDMETHODIMP CeeeExecutor::MoveTab(CeeeWindowHandle tab, long index) {
+ HRESULT hr = EnsureWindowThread();
+ if (FAILED(hr)) {
+ return hr;
+ }
+
+ scoped_ptr<TabWindowManager> manager;
+ hr = CreateTabWindowManager(hwnd_, &manager);
+ DCHECK(SUCCEEDED(hr)) << "Failed to initialize TabWindowManager.";
+ if (FAILED(hr)) {
+ return hr;
+ }
+
+ long src_index = 0;
+ hr = manager->IndexFromHWND(reinterpret_cast<HWND>(tab), &src_index);
+ if (FAILED(hr)) {
+ NOTREACHED() << "Failed IndexFromHWND " << com::LogHr(hr);
+ return hr;
+ }
+
+ LONG num_tabs = -1;
+ hr = manager->GetCount(&num_tabs);
+ if (FAILED(hr)) {
+ NOTREACHED() << "Failed to GetCount " << com::LogHr(hr);
+ return hr;
+ }
+
+ // Clamp new index (as per Chrome implementation) so that extension authors
+ // can for convenience sakes use index=999 (or some such) to move the tab
+ // to the far right.
+ if (index >= num_tabs) {
+ index = num_tabs - 1;
+ }
+
+ // Clamp current index so we can move newly-created tabs easily.
+ if (src_index >= num_tabs) {
+ src_index = num_tabs - 1;
+ }
+
+ if (index == src_index)
+ return S_FALSE; // nothing to be done
+
+ scoped_ptr<TabWindow> dest_tab;
+ hr = manager->GetItemWrapper(index, &dest_tab);
+ if (FAILED(hr)) {
+ NOTREACHED() << "Failed GetItem or QI on dest tab " << com::LogHr(hr);
+ return hr;
+ }
+
+ long dest_id = -1;
+ hr = dest_tab->GetID(&dest_id);
+ if (FAILED(hr)) {
+ NOTREACHED() << "Failed GetID on dest tab " << com::LogHr(hr);
+ return hr;
+ }
+
+ scoped_ptr<TabWindow> moving_tab;
+ hr = manager->GetItemWrapper(src_index, &moving_tab);
+ if (FAILED(hr)) {
+ NOTREACHED() << "Failed GetItem or QI on moving tab " << com::LogHr(hr);
+ return hr;
+ }
+
+ long moving_id = -1;
+ hr = moving_tab->GetID(&moving_id);
+ if (FAILED(hr)) {
+ NOTREACHED() << "Failed GetID on moving tab " << com::LogHr(hr);
+ return hr;
+ }
+
+ hr = manager->RepositionTab(moving_id, dest_id, 0);
+ if (FAILED(hr)) {
+ NOTREACHED() << "Failed to reposition tab " << com::LogHr(hr);
+ return hr;
+ }
+
+ return hr;
+}
+
+STDMETHODIMP CeeeExecutor::Navigate(BSTR url, long flags, BSTR target) {
+ DCHECK(url);
+ HRESULT hr = EnsureWindowThread();
+ if (FAILED(hr)) {
+ return hr;
+ }
+
+ CComPtr<IWebBrowser2> tab_browser;
+ hr = GetWebBrowser(&tab_browser);
+ if (FAILED(hr)) {
+ NOTREACHED() << "Failed to get browser " << com::LogHr(hr);
+ return hr;
+ }
+
+ CComBSTR current_url;
+ hr = tab_browser->get_LocationURL(&current_url);
+ if (FAILED(hr)) {
+ NOTREACHED() << "Failed to get URL " << com::LogHr(hr);
+ return hr;
+ }
+
+ if (current_url == url &&
+ 0 != lstrcmpW(L"_blank", com::ToString(target))) {
+ LOG(INFO) << "Got update request, but URL & target is unchanged: " << url;
+ return S_FALSE;
+ }
+
+ hr = tab_browser->Navigate(url, &CComVariant(flags), &CComVariant(target),
+ &CComVariant(), &CComVariant());
+ // We don't DCHECK here since there are cases where we get an error
+ // 0x800700aa "The requested resource is in use." if the main UI
+ // thread is currently blocked... and sometimes... it is blocked by
+ // us... if we are too slow to respond (e.g. because too busy
+ // navigating when the user performs an extension action that causes
+ // navigation again and again and again)... So we might as well
+ // abandon ship and let the UI thread be happy...
+ LOG_IF(ERROR, FAILED(hr)) << "Failed to navigate tab: " << hwnd_ <<
+ " to " << url << ". " << com::LogHr(hr);
+ return hr;
+}
+
+STDMETHODIMP CeeeExecutor::RemoveTab(CeeeWindowHandle tab) {
+ HRESULT hr = EnsureWindowThread();
+ if (FAILED(hr)) {
+ return hr;
+ }
+
+ scoped_ptr<TabWindowManager> manager;
+ hr = CreateTabWindowManager(hwnd_, &manager);
+ DCHECK(SUCCEEDED(hr)) << "Failed to initialize TabWindowManager.";
+ if (FAILED(hr)) {
+ return hr;
+ }
+
+ long index = -1;
+ hr = manager->IndexFromHWND(reinterpret_cast<HWND>(tab), &index);
+ if (FAILED(hr)) {
+ NOTREACHED() << "Failed to get index of tab " << com::LogHr(hr);
+ return hr;
+ }
+
+ scoped_ptr<TabWindow> tab_item;
+ hr = manager->GetItemWrapper(index, &tab_item);
+ if (FAILED(hr)) {
+ NOTREACHED() << "Failed to get tab object " << com::LogHr(hr);
+ return hr;
+ }
+
+ hr = tab_item->Close();
+ DCHECK(SUCCEEDED(hr)) << "Failed to close tab " << com::LogHr(hr);
+ return hr;
+}
+
+STDMETHODIMP CeeeExecutor::SelectTab(CeeeWindowHandle tab) {
+ HRESULT hr = EnsureWindowThread();
+ if (FAILED(hr)) {
+ return hr;
+ }
+
+ scoped_ptr<TabWindowManager> manager;
+ hr = CreateTabWindowManager(hwnd_, &manager);
+ DCHECK(SUCCEEDED(hr)) << "Failed to initialize TabWindowManager.";
+ if (FAILED(hr)) {
+ return hr;
+ }
+
+ long index = -1;
+ hr = manager->IndexFromHWND(reinterpret_cast<HWND>(tab), &index);
+ if (FAILED(hr)) {
+ NOTREACHED() << "Failed to get index of tab, wnd=" <<
+ std::hex << hwnd_ << " " << com::LogHr(hr);
+ return hr;
+ }
+
+ hr = manager->SelectTab(index);
+ DCHECK(SUCCEEDED(hr)) << "Failed to select window, wnd=" <<
+ std::hex << hwnd_ << " " << com::LogHr(hr);
+ return hr;
+}
+
+STDMETHODIMP CeeeExecutor::InsertCode(BSTR code, BSTR file, BOOL all_frames,
+ CeeeTabCodeType type) {
+ HRESULT hr = EnsureWindowThread();
+ if (FAILED(hr)) {
+ return hr;
+ }
+
+ CComPtr<IFrameEventHandlerHost> frame_handler_host;
+ hr = GetSite(IID_IFrameEventHandlerHost,
+ reinterpret_cast<void**>(&frame_handler_host));
+ if (FAILED(hr)) {
+ NOTREACHED() << "No frame event handler host for executor. "
+ << com::LogHr(hr);
+ return hr;
+ }
+
+ hr = frame_handler_host->InsertCode(code, file, all_frames, type);
+ if (FAILED(hr)) {
+ NOTREACHED() << "Failed to insert code. " << com::LogHr(hr);
+ return hr;
+ }
+
+ return S_OK;
+}
+
+HRESULT CeeeExecutor::GetCookieValue(BSTR url, BSTR name, BSTR* value) {
+ DCHECK(value);
+ if (!value)
+ return E_POINTER;
+
+ // INTERNET_COOKIE_HTTPONLY only works for IE8+.
+ DWORD flags = 0;
+ if (ie_util::GetIeVersion() > ie_util::IEVERSION_IE7)
+ flags |= INTERNET_COOKIE_HTTPONLY;
+
+ // First find out the size of the cookie data.
+ DWORD size = 0;
+ BOOL cookie_found = ::InternetGetCookieExW(
+ url, name, NULL, &size, flags, NULL);
+ if (!cookie_found) {
+ if (::GetLastError() == ERROR_NO_MORE_ITEMS)
+ return S_FALSE;
+ else
+ return E_FAIL;
+ } else if (size == 0) {
+ return E_FAIL;
+ }
+
+ // Now retrieve the data.
+ std::vector<wchar_t> cookie_data(size + 1);
+ cookie_found = ::InternetGetCookieExW(
+ url, name, &cookie_data[0], &size, flags, NULL);
+ DCHECK(cookie_found);
+ if (!cookie_found)
+ return E_FAIL;
+
+ // Copy the data to the output parameter.
+ cookie_data[size] = 0;
+ std::wstring cookie_data_string(&cookie_data[0], size);
+ std::wstring data_prefix(name);
+ data_prefix.append(L"=");
+ if (cookie_data_string.find(data_prefix) != 0) {
+ DCHECK(false) << "The cookie name or data format does not match the "
+ << "expected 'name=value'. Name: " << name << ", Data: "
+ << cookie_data_string;
+ return E_FAIL;
+ }
+ *value = ::SysAllocString(
+ cookie_data_string.substr(data_prefix.size()).c_str());
+ return S_OK;
+}
+
+STDMETHODIMP CeeeExecutor::GetCookie(BSTR url, BSTR name,
+ CeeeCookieInfo* cookie_info) {
+ DCHECK(cookie_info);
+ if (!cookie_info)
+ return E_POINTER;
+ HRESULT hr = GetCookieValue(url, name, &cookie_info->value);
+ if (hr == S_OK) {
+ cookie_info->name = ::SysAllocString(name);
+ }
+ DCHECK(hr == S_OK || cookie_info->value == NULL);
+ return hr;
+}
+
+void CeeeExecutor::set_cookie_store_is_registered(bool is_registered) {
+ g_cookie_store_is_registered = is_registered;
+}
+
+STDMETHODIMP CeeeExecutor::RegisterCookieStore() {
+ set_cookie_store_is_registered(true);
+ return S_OK;
+}
+
+HRESULT CeeeExecutor::EnsureWindowThread() {
+ if (!window_utils::IsWindowThread(hwnd_)) {
+ LOG(ERROR) << "Executor not running in appropriate thread for window: " <<
+ hwnd_;
+ return E_UNEXPECTED;
+ }
+
+ return S_OK;
+}
+
+STDMETHODIMP CeeeExecutor::CookieStoreIsRegistered() {
+ return g_cookie_store_is_registered ? S_OK : S_FALSE;
+}
+
+STDMETHODIMP CeeeExecutor::SetExtensionId(BSTR extension_id) {
+ DCHECK(extension_id);
+ if (extension_id == NULL)
+ return E_FAIL;
+
+ WideToUTF8(extension_id, SysStringLen(extension_id), &extension_id_);
+ return S_OK;
+}
+
+STDMETHODIMP CeeeExecutor::ShowInfobar(BSTR url,
+ CeeeWindowHandle* window_handle) {
+ DCHECK(infobar_manager_ != NULL) << "infobar_manager_ is not initialized";
+ if (infobar_manager_ == NULL)
+ return E_FAIL;
+
+ // Consider infobar navigation to an empty url as the request to hide it.
+ // Note that this is not a part of the spec so it is up to the implementation
+ // how to treat this.
+ size_t url_string_length = SysStringLen(url);
+ if (0 == url_string_length) {
+ infobar_manager_->HideAll();
+ return S_OK;
+ }
+
+ // Translate relative path to the absolute path using our extension URL
+ // as the root.
+ std::string url_utf8;
+ WideToUTF8(url, url_string_length, &url_utf8);
+ if (extension_id_.empty()) {
+ LOG(ERROR) << "Extension id is not set before the request to show infobar.";
+ } else {
+ url_utf8 = ResolveURL(
+ StringPrintf("chrome-extension://%s", extension_id_.c_str()), url_utf8);
+ }
+ std::wstring full_url;
+ UTF8ToWide(url_utf8.c_str(), url_utf8.size(), &full_url);
+
+ // Show and navigate the infobar window.
+ HRESULT hr = infobar_manager_->Show(infobar_api::TOP_INFOBAR,
+ kMaxInfobarHeight, full_url, true);
+ if (SUCCEEDED(hr) && window_handle != NULL) {
+ *window_handle = reinterpret_cast<CeeeWindowHandle>(
+ window_utils::GetTopLevelParent(hwnd_));
+ }
+
+ return hr;
+}
+
+STDMETHODIMP CeeeExecutor::OnTopFrameBeforeNavigate(BSTR url) {
+ DCHECK(infobar_manager_ != NULL) << "infobar_manager_ is not initialized";
+ if (infobar_manager_ == NULL)
+ return E_FAIL;
+
+ // According to the specification, tab navigation closes the infobar.
+ infobar_manager_->HideAll();
+ return S_OK;
+}
diff --git a/ceee/ie/plugin/bho/executor.h b/ceee/ie/plugin/bho/executor.h
new file mode 100644
index 0000000..379ba32
--- /dev/null
+++ b/ceee/ie/plugin/bho/executor.h
@@ -0,0 +1,166 @@
+// 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.
+//
+// @file
+// CeeeExecutor & CeeeExecutorCreator implementation, interfaces to
+// execute code in other threads which can be running in other another process.
+
+#ifndef CEEE_IE_PLUGIN_BHO_EXECUTOR_H_
+#define CEEE_IE_PLUGIN_BHO_EXECUTOR_H_
+
+#include <atlbase.h>
+#include <atlcom.h>
+#include <string.h>
+
+#include "base/scoped_ptr.h"
+#include "ceee/ie/plugin/bho/infobar_manager.h"
+#include "ceee/ie/plugin/toolband/resource.h"
+
+#include "toolband.h" // NOLINT
+
+struct IWebBrowser2;
+namespace infobar_api {
+ class InfobarManager;
+};
+
+// The executor creator hooks itself in the destination thread where
+// the executor will then be created and register in the CeeeBroker.
+
+// The creator of CeeeExecutors.
+class ATL_NO_VTABLE CeeeExecutorCreator
+ : public CComObjectRootEx<CComSingleThreadModel>,
+ public CComCoClass<CeeeExecutorCreator,
+ &CLSID_CeeeExecutorCreator>,
+ public ICeeeExecutorCreator {
+ public:
+ CeeeExecutorCreator();
+ void FinalRelease();
+
+ DECLARE_REGISTRY_RESOURCEID(IDR_EXECUTOR_CREATOR)
+
+ DECLARE_NOT_AGGREGATABLE(CeeeExecutorCreator)
+ BEGIN_COM_MAP(CeeeExecutorCreator)
+ COM_INTERFACE_ENTRY(ICeeeExecutorCreator)
+ END_COM_MAP()
+ DECLARE_PROTECT_FINAL_CONSTRUCT()
+
+ // @name ICeeeExecutorCreator implementation.
+ // @{
+ STDMETHOD(CreateWindowExecutor)(long thread_id, CeeeWindowHandle window);
+ STDMETHOD(Teardown)(long thread_id);
+ // @}
+
+ protected:
+ // The registered message we use to communicate with the destination thread.
+ static const UINT kCreateWindowExecutorMessage;
+
+ // The function that will be hooked in the destination thread.
+ // See http://msdn.microsoft.com/en-us/library/ms644981(VS.85).aspx
+ // for more details.
+ static LRESULT CALLBACK GetMsgProc(int code, WPARAM wparam, LPARAM lparam);
+
+ // We must remember the hook so that we can unhook when we are done.
+ HHOOK hook_;
+
+ // We can only work for one thread at a time. Used to validate that the
+ // call to ICeeeExecutorCreator::Teardown are balanced to a previous call
+ // to ICeeeExecutorCreator::CreateExecutor.
+ long current_thread_id_;
+};
+
+// 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 ICeeeWindowExecutor,
+ public ICeeeTabExecutor,
+ public ICeeeCookieExecutor,
+ public ICeeeInfobarExecutor {
+ public:
+ DECLARE_REGISTRY_RESOURCEID(IDR_EXECUTOR)
+
+ DECLARE_NOT_AGGREGATABLE(CeeeExecutor)
+ BEGIN_COM_MAP(CeeeExecutor)
+ COM_INTERFACE_ENTRY(IObjectWithSite)
+ COM_INTERFACE_ENTRY(ICeeeWindowExecutor)
+ COM_INTERFACE_ENTRY(ICeeeTabExecutor)
+ COM_INTERFACE_ENTRY(ICeeeCookieExecutor)
+ COM_INTERFACE_ENTRY(ICeeeInfobarExecutor)
+ END_COM_MAP()
+ DECLARE_PROTECT_FINAL_CONSTRUCT()
+ DECLARE_CLASSFACTORY()
+
+ // @name ICeeeWindowExecutor implementation.
+ // @{
+ STDMETHOD(Initialize)(CeeeWindowHandle hwnd);
+ STDMETHOD(GetWindow)(BOOL populate_tabs, CeeeWindowInfo* window_info);
+ STDMETHOD(GetTabs)(BSTR* tab_list);
+ STDMETHOD(UpdateWindow)(long left, long top, long width, long height,
+ CeeeWindowInfo* window_info);
+ STDMETHOD(RemoveWindow)();
+ STDMETHOD(GetTabIndex)(CeeeWindowHandle tab, long* index);
+ STDMETHOD(MoveTab)(CeeeWindowHandle tab, long index);
+ STDMETHOD(RemoveTab)(CeeeWindowHandle tab);
+ STDMETHOD(SelectTab)(CeeeWindowHandle tab);
+ // @}
+
+ // @name ICeeeTabExecutor implementation.
+ // @{
+ // Initialize was already declared in ICeeeWindowExecutor, so we don't
+ // add it here, even if it's part of the interface.
+ STDMETHOD(GetTabInfo)(CeeeTabInfo* tab_info);
+ STDMETHOD(Navigate)(BSTR url, long flags, BSTR target);
+ STDMETHOD(InsertCode)(BSTR code, BSTR file, BOOL all_frames,
+ CeeeTabCodeType type);
+ // @}
+
+ // @name ICeeeCookieExecutor implementation.
+ // @{
+ STDMETHOD(GetCookie)(BSTR url, BSTR name, CeeeCookieInfo* cookie_info);
+ STDMETHOD(RegisterCookieStore)();
+ STDMETHOD(CookieStoreIsRegistered)();
+ // @}
+
+ // @name ICeeeInfobarExecutor implementation.
+ // @{
+ STDMETHOD(SetExtensionId)(BSTR extension_id);
+ STDMETHOD(ShowInfobar)(BSTR url, CeeeWindowHandle* window_handle);
+ STDMETHOD(OnTopFrameBeforeNavigate)(BSTR url);
+ // @}
+
+ CeeeExecutor() : hwnd_(NULL) {}
+
+ protected:
+ // Get the IWebBrowser2 interface of the
+ // frame event host that was set as our site.
+ virtual HRESULT GetWebBrowser(IWebBrowser2** browser);
+
+ // Used via EnumChildWindows to get all tabs.
+ static BOOL CALLBACK GetTabsEnumProc(HWND window, LPARAM param);
+
+ // Ensure we're running inside the right thread.
+ HRESULT EnsureWindowThread();
+
+ // The HWND of the tab/window we are associated to.
+ HWND hwnd_;
+
+ // Extension id.
+ std::string extension_id_;
+
+ // Get the value of the cookie with the given name, associated with the given
+ // URL. Returns S_FALSE if the cookie does not exist, and returns an error
+ // code if something unexpected occurs.
+ virtual HRESULT GetCookieValue(BSTR url, BSTR name, BSTR* value);
+
+ // Mainly for unit testing purposes.
+ void set_cookie_store_is_registered(bool is_registered);
+
+ // Instance of InfobarManager for the tab associated with the thread to which
+ // the executor is attached.
+ scoped_ptr<infobar_api::InfobarManager> infobar_manager_;
+};
+
+#endif // CEEE_IE_PLUGIN_BHO_EXECUTOR_H_
diff --git a/ceee/ie/plugin/bho/executor.rgs b/ceee/ie/plugin/bho/executor.rgs
new file mode 100644
index 0000000..4fed5bc
--- /dev/null
+++ b/ceee/ie/plugin/bho/executor.rgs
@@ -0,0 +1,9 @@
+HKCR {
+ NoRemove CLSID {
+ ForceRemove '{057FCFE3-F872-483d-86B0-0430E375E41F}' = s 'Google CEEE Executor' {
+ InprocServer32 = s '%MODULE%' {
+ val ThreadingModel = s 'Apartment'
+ }
+ }
+ }
+} \ No newline at end of file
diff --git a/ceee/ie/plugin/bho/executor_creator.rgs b/ceee/ie/plugin/bho/executor_creator.rgs
new file mode 100644
index 0000000..3a81441
--- /dev/null
+++ b/ceee/ie/plugin/bho/executor_creator.rgs
@@ -0,0 +1,9 @@
+HKCR {
+ NoRemove CLSID {
+ ForceRemove '{4A562910-2D54-4e98-B87F-D4A7F5F5D0B9}' = s 'Google CEEE Executor Creator' {
+ InprocServer32 = s '%MODULE%' {
+ val ThreadingModel = s 'Free'
+ }
+ }
+ }
+}
diff --git a/ceee/ie/plugin/bho/executor_unittest.cc b/ceee/ie/plugin/bho/executor_unittest.cc
new file mode 100644
index 0000000..e260fd3
--- /dev/null
+++ b/ceee/ie/plugin/bho/executor_unittest.cc
@@ -0,0 +1,1080 @@
+// 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 implementation unit tests.
+
+// MockWin32 must not be included after atlwin, which is included by some
+// headers in here, so we need to put it at the top:
+#include "ceee/testing/utils/mock_win32.h" // NOLINT
+
+#include <wininet.h>
+
+#include <vector>
+
+#include "ceee/ie/broker/cookie_api_module.h"
+#include "ceee/ie/broker/common_api_module.h"
+#include "ceee/ie/broker/tab_api_module.h"
+#include "ceee/ie/broker/window_api_module.h"
+#include "ceee/ie/common/ie_util.h"
+#include "ceee/ie/common/mock_ie_tab_interfaces.h"
+#include "ceee/ie/plugin/bho/executor.h"
+#include "ceee/ie/testing/mock_broker_and_friends.h"
+#include "ceee/ie/testing/mock_frame_event_handler_host.h"
+#include "ceee/common/initializing_coclass.h"
+#include "ceee/testing/utils/mock_com.h"
+#include "ceee/testing/utils/mshtml_mocks.h"
+#include "ceee/testing/utils/mock_static.h"
+#include "ceee/testing/utils/mock_window_utils.h"
+#include "ceee/testing/utils/test_utils.h"
+#include "ceee/testing/utils/instance_count_mixin.h"
+#include "testing/gmock/include/gmock/gmock.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+#include "broker_lib.h" // NOLINT
+
+namespace {
+
+using testing::_;
+using testing::AddRef;
+using testing::CopyInterfaceToArgument;
+using testing::CopyBSTRToArgument;
+using testing::InstanceCountMixin;
+using testing::Invoke;
+using testing::IsNull;
+using testing::NotNull;
+using testing::Return;
+using testing::SetArgumentPointee;
+using testing::StrictMock;
+
+const int kGoodWindowId = 42;
+const HWND kGoodWindow = reinterpret_cast<HWND>(kGoodWindowId);
+const int kOtherGoodWindowId = 84;
+const HWND kOtherGoodWindow = reinterpret_cast<HWND>(kOtherGoodWindowId);
+const int kTabIndex = 26;
+const int kOtherTabIndex = 62;
+
+const wchar_t* kUrl1 = L"http://www.google.com";
+const wchar_t* kUrl2 = L"http://myintranet";
+const wchar_t* kTitle1 = L"MyLord";
+const wchar_t* kTitle2 = L"Your MADness";
+
+class TestingMockExecutorCreatorTeardown
+ : public CeeeExecutorCreator,
+ public InitializingCoClass<
+ StrictMock<TestingMockExecutorCreatorTeardown>> {
+ public:
+ // TODO(mad@chromium.org): Add reference counting testing/validation.
+ TestingMockExecutorCreatorTeardown() {
+ hook_ = reinterpret_cast<HHOOK>(1);
+ current_thread_id_ = 42L;
+ }
+ HRESULT Initialize(StrictMock<TestingMockExecutorCreatorTeardown>** self) {
+ // Yes, this seems fishy, but it is called from InitializingCoClass
+ // which does it on the class we pass it as a template, so we are OK.
+ *self = static_cast<StrictMock<TestingMockExecutorCreatorTeardown>*>(this);
+ return S_OK;
+ }
+
+ MOCK_METHOD1_WITH_CALLTYPE(__stdcall, Teardown, HRESULT(long));
+};
+
+TEST(ExecutorCreator, ProperTearDownOnDestruction) {
+ StrictMock<TestingMockExecutorCreatorTeardown>* executor_creator = NULL;
+ CComPtr<ICeeeExecutorCreator> executor_creator_keeper;
+ ASSERT_HRESULT_SUCCEEDED(StrictMock<TestingMockExecutorCreatorTeardown>::
+ CreateInitialized(&executor_creator, &executor_creator_keeper));
+ EXPECT_CALL(*executor_creator, Teardown(42L)).WillOnce(Return(S_OK));
+ // The Release of the last reference should call FinalRelease.
+}
+
+// Mock object for some functions used for hooking.
+MOCK_STATIC_CLASS_BEGIN(MockHooking)
+ MOCK_STATIC_INIT_BEGIN(MockHooking)
+ MOCK_STATIC_INIT(SetWindowsHookEx);
+ MOCK_STATIC_INIT(UnhookWindowsHookEx);
+ MOCK_STATIC_INIT(PostThreadMessage);
+ MOCK_STATIC_INIT(PeekMessage);
+ MOCK_STATIC_INIT(CallNextHookEx);
+ MOCK_STATIC_INIT(CoCreateInstance);
+ MOCK_STATIC_INIT_END()
+
+ MOCK_STATIC4(HHOOK, CALLBACK, SetWindowsHookEx, int, HOOKPROC, HINSTANCE,
+ DWORD);
+ MOCK_STATIC1(BOOL, CALLBACK, UnhookWindowsHookEx, HHOOK);
+ MOCK_STATIC4(BOOL, CALLBACK, PostThreadMessage, DWORD, UINT, WPARAM, LPARAM);
+ MOCK_STATIC5(BOOL, CALLBACK, PeekMessage, LPMSG, HWND, UINT, UINT, UINT);
+ MOCK_STATIC4(LRESULT, CALLBACK, CallNextHookEx, HHOOK, int, WPARAM, LPARAM);
+ MOCK_STATIC5(HRESULT, CALLBACK, CoCreateInstance, REFCLSID, LPUNKNOWN,
+ DWORD, REFIID, LPVOID*);
+MOCK_STATIC_CLASS_END(MockHooking)
+
+class TestingExecutorCreator : public CeeeExecutorCreator {
+ public:
+ // TODO(mad@chromium.org): Add reference counting testing/validation.
+ STDMETHOD_(ULONG, AddRef)() { return 1; }
+ STDMETHOD_(ULONG, Release)() { return 1; }
+ STDMETHOD (QueryInterface)(REFIID, LPVOID*) { return S_OK; }
+
+ // Accessorize... :-)
+ long current_thread_id() const { return current_thread_id_; }
+ void set_current_thread_id(long thread_id) { current_thread_id_ = thread_id; }
+ HHOOK hook() const { return hook_; }
+ void set_hook(HHOOK hook) { hook_ = hook; }
+
+ // Publicize...
+ using CeeeExecutorCreator::kCreateWindowExecutorMessage;
+ using CeeeExecutorCreator::GetMsgProc;
+};
+
+TEST(ExecutorCreator, CreateExecutor) {
+ testing::LogDisabler no_dchecks;
+
+ // Start with hooking failure.
+ StrictMock<MockHooking> mock_hooking;
+ EXPECT_CALL(mock_hooking, SetWindowsHookEx(WH_GETMESSAGE, _, _, 42L)).
+ WillOnce(Return(static_cast<HHOOK>(NULL)));
+ TestingExecutorCreator executor_creator;
+ EXPECT_HRESULT_FAILED(executor_creator.CreateWindowExecutor(42L, 42L));
+ EXPECT_EQ(0L, executor_creator.current_thread_id());
+ EXPECT_EQ(NULL, executor_creator.hook());
+
+ // Then succeed hooking but fail message posting.
+ EXPECT_CALL(mock_hooking, SetWindowsHookEx(WH_GETMESSAGE, _, _, 42L)).
+ WillRepeatedly(Return(reinterpret_cast<HHOOK>(1)));
+ EXPECT_CALL(mock_hooking, PostThreadMessage(42L, _, 0, 0)).
+ WillOnce(Return(FALSE));
+ ::SetLastError(ERROR_INVALID_ACCESS);
+ EXPECT_HRESULT_FAILED(executor_creator.CreateWindowExecutor(42L, 0L));
+ ::SetLastError(ERROR_SUCCESS);
+
+ // Success!!!
+ EXPECT_CALL(mock_hooking, PostThreadMessage(42L, _, 0, 0)).
+ WillRepeatedly(Return(TRUE));
+ EXPECT_HRESULT_SUCCEEDED(executor_creator.CreateWindowExecutor(42L, 0L));
+}
+
+TEST(ExecutorCreator, Teardown) {
+ testing::LogDisabler no_dchecks;
+
+ // Start with nothing to do.
+ TestingExecutorCreator executor_creator;
+ EXPECT_HRESULT_SUCCEEDED(executor_creator.Teardown(0));
+
+ // OK check that we properly unhook now...
+ StrictMock<MockHooking> mock_hooking;
+ HHOOK fake_hook = reinterpret_cast<HHOOK>(1);
+ EXPECT_CALL(mock_hooking, UnhookWindowsHookEx(fake_hook)).
+ WillOnce(Return(TRUE));
+ executor_creator.set_current_thread_id(42L);
+ executor_creator.set_hook(fake_hook);
+ EXPECT_HRESULT_SUCCEEDED(executor_creator.Teardown(42L));
+ EXPECT_EQ(0L, executor_creator.current_thread_id());
+ EXPECT_EQ(NULL, executor_creator.hook());
+}
+
+class ExecutorCreatorTest : public testing::Test {
+ public:
+ virtual void SetUp() {
+ ASSERT_HRESULT_SUCCEEDED(testing::MockWindowExecutor::CreateInitialized(
+ &executor_, &executor_keeper_));
+ ASSERT_HRESULT_SUCCEEDED(testing::MockBroker::CreateInitialized(
+ &broker_, &broker_keeper_));
+ }
+
+ virtual void TearDown() {
+ executor_ = NULL;
+ executor_keeper_.Release();
+
+ broker_ = NULL;
+ broker_keeper_.Release();
+
+ // Everything should have been relinquished.
+ ASSERT_EQ(0, testing::InstanceCountMixinBase::all_instance_count());
+ }
+
+ protected:
+ testing::MockWindowExecutor* executor_;
+ CComPtr<ICeeeWindowExecutor> executor_keeper_;
+
+ testing::MockBroker* broker_;
+ CComPtr<ICeeeBrokerRegistrar> broker_keeper_;
+};
+
+TEST_F(ExecutorCreatorTest, GetMsgProc) {
+ testing::LogDisabler no_dchecks;
+
+ // Start with nothing to do.
+ StrictMock<MockHooking> mock_hooking;
+ EXPECT_CALL(mock_hooking, CallNextHookEx(_, _, _, _)).WillOnce(Return(0));
+
+ TestingExecutorCreator executor_creator;
+ EXPECT_EQ(0, executor_creator.GetMsgProc(HC_SKIP, 0, 0));
+
+ // NULL message.
+ EXPECT_CALL(mock_hooking, CallNextHookEx(_, _, _, _)).WillOnce(Return(0));
+ EXPECT_EQ(0, executor_creator.GetMsgProc(HC_ACTION, 0, 0));
+
+ // Not our message.
+ MSG message;
+ message.message = WM_TIMER;
+ EXPECT_CALL(mock_hooking, CallNextHookEx(_, _, _, _)).WillOnce(Return(0));
+ EXPECT_EQ(0, executor_creator.GetMsgProc(HC_ACTION, 0,
+ reinterpret_cast<LPARAM>(&message)));
+
+ // OK check our own code paths now.
+ message.message = executor_creator.kCreateWindowExecutorMessage;
+ EXPECT_CALL(mock_hooking, CallNextHookEx(_, _, _, _)).Times(0);
+
+ // Not a PM_REMOVE message, delegates to PeekMessage.
+ EXPECT_CALL(mock_hooking, PeekMessage(_, _, _, _, _)).WillOnce(Return(TRUE));
+ EXPECT_EQ(0, executor_creator.GetMsgProc(HC_ACTION, 0,
+ reinterpret_cast<LPARAM>(&message)));
+
+ // With a PM_REMOVE, we get the job done.
+ // But lets see if we can silently handle a CoCreateInstance Failure
+ EXPECT_CALL(mock_hooking, CoCreateInstance(_, _, _, _, _)).
+ WillOnce(Return(REGDB_E_CLASSNOTREG));
+ EXPECT_EQ(0, executor_creator.GetMsgProc(HC_ACTION, PM_REMOVE,
+ reinterpret_cast<LPARAM>(&message)));
+
+ // Now fail getting the broker registrar.
+ message.lParam = reinterpret_cast<LPARAM>(kGoodWindow);
+ EXPECT_CALL(*executor_, Initialize(_)).WillOnce(Return(S_OK));
+ EXPECT_CALL(mock_hooking, CoCreateInstance(_, _, _, _, _)).
+ WillOnce(DoAll(SetArgumentPointee<4>(executor_keeper_.p),
+ AddRef(executor_keeper_.p), Return(S_OK))).
+ WillOnce(Return(REGDB_E_CLASSNOTREG));
+ EXPECT_EQ(0, executor_creator.GetMsgProc(HC_ACTION, PM_REMOVE,
+ reinterpret_cast<LPARAM>(&message)));
+
+ // Now fail the registration itself.
+ message.lParam = reinterpret_cast<LPARAM>(kGoodWindow);
+ EXPECT_CALL(mock_hooking, CoCreateInstance(_, _, _, _, _)).
+ WillOnce(DoAll(SetArgumentPointee<4>(executor_keeper_.p),
+ AddRef(executor_keeper_.p), Return(S_OK))).
+ WillOnce(DoAll(SetArgumentPointee<4>(broker_keeper_.p),
+ AddRef(broker_keeper_.p), Return(S_OK)));
+ EXPECT_CALL(*executor_, Initialize(_)).WillOnce(Return(S_OK));
+ DWORD current_thread_id = ::GetCurrentThreadId();
+ EXPECT_CALL(*broker_, RegisterWindowExecutor(current_thread_id,
+ executor_keeper_.p)).WillOnce(Return(E_FAIL));
+ EXPECT_EQ(0, executor_creator.GetMsgProc(HC_ACTION, PM_REMOVE,
+ reinterpret_cast<LPARAM>(&message)));
+
+ // Success!!!
+ message.lParam = reinterpret_cast<LPARAM>(kGoodWindow);
+ EXPECT_CALL(mock_hooking, CoCreateInstance(_, _, _, _, _)).
+ WillOnce(DoAll(SetArgumentPointee<4>(executor_keeper_.p),
+ AddRef(executor_keeper_.p), Return(S_OK))).
+ WillOnce(DoAll(SetArgumentPointee<4>(broker_keeper_.p),
+ AddRef(broker_keeper_.p), Return(S_OK)));
+ EXPECT_CALL(*executor_, Initialize(_)).WillOnce(Return(S_OK));
+ EXPECT_CALL(*broker_, RegisterWindowExecutor(current_thread_id,
+ executor_keeper_.p)).WillOnce(Return(S_OK));
+ EXPECT_EQ(0, executor_creator.GetMsgProc(HC_ACTION, PM_REMOVE,
+ reinterpret_cast<LPARAM>(&message)));
+}
+
+MOCK_STATIC_CLASS_BEGIN(MockWinInet)
+ MOCK_STATIC_INIT_BEGIN(MockWinInet)
+ MOCK_STATIC_INIT(InternetGetCookieExW);
+ MOCK_STATIC_INIT_END()
+
+ MOCK_STATIC6(BOOL, CALLBACK, InternetGetCookieExW, LPCWSTR, LPCWSTR, LPWSTR,
+ LPDWORD, DWORD, LPVOID);
+MOCK_STATIC_CLASS_END(MockWinInet)
+
+
+// Mock object for some functions used for hooking.
+MOCK_STATIC_CLASS_BEGIN(MockIeUtil)
+ MOCK_STATIC_INIT_BEGIN(MockIeUtil)
+ MOCK_STATIC_INIT2(ie_util::GetIeVersion, GetIeVersion);
+ MOCK_STATIC_INIT_END()
+
+ MOCK_STATIC0(ie_util::IeVersion, , GetIeVersion);
+MOCK_STATIC_CLASS_END(MockIeUtil)
+
+class TestingExecutor
+ : public CeeeExecutor,
+ public InitializingCoClass<TestingExecutor> {
+ public:
+ HRESULT Initialize(TestingExecutor** self) {
+ *self = this;
+ return S_OK;
+ }
+ static void set_tab_windows(std::vector<HWND> tab_windows) {
+ tab_windows_ = tab_windows;
+ }
+ void set_id(HWND hwnd) {
+ hwnd_ = hwnd;
+ }
+ static BOOL MockEnumChildWindows(HWND, WNDENUMPROC, LPARAM p) {
+ std::vector<HWND>* tab_windows = reinterpret_cast<std::vector<HWND>*>(p);
+ *tab_windows = tab_windows_;
+ return TRUE;
+ }
+ static void set_cookie_data(const std::wstring& cookie_data) {
+ cookie_data_ = cookie_data;
+ }
+ static BOOL MockInternetGetCookieExW(LPCWSTR, LPCWSTR, LPWSTR data,
+ LPDWORD size, DWORD, LPVOID) {
+ EXPECT_TRUE(data != NULL);
+ EXPECT_TRUE(*size > cookie_data_.size());
+ wcscpy_s(data, *size, cookie_data_.data());
+ *size = cookie_data_.size() + 1;
+ return TRUE;
+ }
+
+ MOCK_METHOD1(GetWebBrowser, HRESULT(IWebBrowser2** browser));
+ HRESULT CallGetWebBrowser(IWebBrowser2** browser) {
+ return CeeeExecutor::GetWebBrowser(browser);
+ }
+
+ MOCK_METHOD1_WITH_CALLTYPE(__stdcall, GetTabs, HRESULT(BSTR*));
+ HRESULT CallGetTabs(BSTR* tab_list) {
+ return CeeeExecutor::GetTabs(tab_list);
+ }
+
+ MOCK_METHOD3(GetCookieValue, HRESULT(BSTR, BSTR, BSTR*));
+ HRESULT CallGetCookieValue(BSTR url, BSTR name, BSTR* value) {
+ return CeeeExecutor::GetCookieValue(url, name, value);
+ }
+
+ // Publicize...
+ using CeeeExecutor::GetTabsEnumProc;
+ using CeeeExecutor::MoveTab;
+ using CeeeExecutor::set_cookie_store_is_registered;
+ private:
+ static std::vector<HWND> tab_windows_;
+ static std::wstring cookie_data_;
+};
+std::vector<HWND> TestingExecutor::tab_windows_;
+std::wstring TestingExecutor::cookie_data_;
+
+// Override to handle DISPID_READYSTATE.
+class ExecutorTests: public testing::Test {
+ public:
+ void SetUp() {
+ ASSERT_HRESULT_SUCCEEDED(TestingExecutor::CreateInitialized(
+ &executor_, &executor_keeper_));
+
+ browser_ = NULL;
+ manager_ = NULL;
+ }
+
+ void MockBrowser() {
+ CComObject<StrictMock<testing::MockIWebBrowser2>>::CreateInstance(
+ &browser_);
+ DCHECK(browser_ != NULL);
+ browser_keeper_ = browser_;
+ EXPECT_CALL(*executor_, GetWebBrowser(NotNull())).
+ WillRepeatedly(DoAll(CopyInterfaceToArgument<0>(browser_keeper_),
+ Return(S_OK)));
+ }
+
+ void MockSite() {
+ ASSERT_HRESULT_SUCCEEDED(
+ testing::MockFrameEventHandlerHost::CreateInitializedIID(
+ &mock_site_, IID_IUnknown, &mock_site_keeper_));
+ executor_->SetSite(mock_site_keeper_);
+ }
+
+ void MockTabManager() {
+ CComObject<StrictMock<testing::MockITabWindowManagerIe7>>::CreateInstance(
+ &manager_);
+ DCHECK(manager_ != NULL);
+ manager_keeper_ = manager_;
+ EXPECT_CALL(ie_tab_interfaces_, TabWindowManagerFromFrame(_, _, _)).
+ WillRepeatedly(DoAll(AddRef(manager_keeper_.p), SetArgumentPointee<2>(
+ reinterpret_cast<void*>(manager_keeper_.p)), Return(S_OK)));
+
+ EXPECT_CALL(*manager_, IndexFromHWND(_, _)).WillRepeatedly(DoAll(
+ SetArgumentPointee<1>(kTabIndex), Return(S_OK)));
+ }
+
+ void NotRunningInThisWindowThread(HWND window) {
+ EXPECT_CALL(mock_window_utils_, IsWindowThread(window)).
+ WillOnce(Return(false));
+ }
+
+ void RepeatedlyRunningInThisWindowThread(HWND window) {
+ EXPECT_CALL(mock_window_utils_, IsWindowThread(window)).
+ WillRepeatedly(Return(true));
+ }
+
+ void RepeatedlyRunningInThisParentWindowThread(HWND child_window,
+ HWND parent_window) {
+ EXPECT_CALL(mock_window_utils_, GetTopLevelParent(child_window)).
+ WillRepeatedly(Return(parent_window));
+ EXPECT_CALL(mock_window_utils_, IsWindowThread(parent_window)).
+ WillRepeatedly(Return(true));
+ }
+ protected:
+ TestingExecutor* executor_;
+ StrictMock<testing::MockWindowUtils> mock_window_utils_;
+
+
+ // TODO(mad@chromium.org): We should standardize on the Mock COM
+ // objects creation.
+ // Using InitializingCoClass would probably be better.
+ CComObject<StrictMock<testing::MockIWebBrowser2>>* browser_;
+ CComPtr<IWebBrowser2> browser_keeper_;
+ CComObject<StrictMock<testing::MockITabWindowManagerIe7>>* manager_;
+
+ testing::MockFrameEventHandlerHost* mock_site_;
+
+ StrictMock<testing::MockUser32> user32_;
+ StrictMock<testing::MockIeTabInterfaces> ie_tab_interfaces_;
+
+ private:
+ CComPtr<IUnknown> executor_keeper_;
+ CComPtr<IUnknown> mock_site_keeper_;
+ CComPtr<ITabWindowManagerIe7> manager_keeper_;
+};
+
+TEST_F(ExecutorTests, GetWebBrowser) {
+ MockSite();
+ CComPtr<IWebBrowser2> browser;
+ {
+ EXPECT_CALL(*mock_site_, GetTopLevelBrowser(NotNull())).
+ WillOnce(Return(E_FAIL));
+ EXPECT_HRESULT_FAILED(executor_->CallGetWebBrowser(&browser));
+ }
+ EXPECT_CALL(*mock_site_, GetTopLevelBrowser(NotNull())).
+ WillOnce(DoAll(SetArgumentPointee<0>(browser_), Return(S_OK)));
+ EXPECT_HRESULT_SUCCEEDED(executor_->CallGetWebBrowser(&browser));
+ EXPECT_EQ(browser, browser.p);
+}
+
+TEST_F(ExecutorTests, GetWindow) {
+ testing::LogDisabler no_dchecks;
+ executor_->set_id(kGoodWindow);
+
+ // Not running in appropriate thread.
+ NotRunningInThisWindowThread(kGoodWindow);
+ EXPECT_HRESULT_FAILED(executor_->GetWindow(FALSE, NULL));
+
+ // Fail getting the window RECT.
+ RepeatedlyRunningInThisWindowThread(kGoodWindow);
+ EXPECT_CALL(user32_, GetForegroundWindow()).
+ WillRepeatedly(Return(kGoodWindow));
+ EXPECT_CALL(mock_window_utils_, GetTopLevelParent(kGoodWindow)).
+ WillRepeatedly(Return(kGoodWindow));
+ EXPECT_CALL(user32_, GetWindowRect(kGoodWindow, NotNull())).
+ WillOnce(Return(FALSE));
+ ::SetLastError(ERROR_INVALID_ACCESS);
+ common_api::WindowInfo window_info;
+ EXPECT_HRESULT_FAILED(executor_->GetWindow(FALSE, &window_info));
+ EXPECT_EQ(NULL, window_info.tab_list);
+ ::SetLastError(ERROR_SUCCESS);
+
+ // Success without tab population.
+ RECT window_rect = {1, 2, 3, 5};
+ EXPECT_CALL(user32_, GetWindowRect(kGoodWindow, NotNull())).
+ WillRepeatedly(DoAll(SetArgumentPointee<1>(window_rect), Return(TRUE)));
+ EXPECT_HRESULT_SUCCEEDED(executor_->GetWindow(FALSE, &window_info));
+ EXPECT_EQ(TRUE, window_info.focused);
+ EXPECT_EQ(window_rect.top, window_info.rect.top);
+ EXPECT_EQ(window_rect.left, window_info.rect.left);
+ EXPECT_EQ(window_rect.bottom, window_info.rect.bottom);
+ EXPECT_EQ(window_rect.right, window_info.rect.right);
+ EXPECT_EQ(NULL, window_info.tab_list);
+
+ // Try the not focused case and a bigger rect.
+ window_rect.left = 8;
+ window_rect.top = 13;
+ window_rect.right = 21;
+ window_rect.bottom = 34;
+ EXPECT_CALL(user32_, GetWindowRect(kOtherGoodWindow, NotNull())).
+ WillRepeatedly(DoAll(SetArgumentPointee<1>(window_rect), Return(TRUE)));
+ // Different parent, means we are not focused.
+ EXPECT_CALL(mock_window_utils_, GetTopLevelParent(kOtherGoodWindow)).
+ WillRepeatedly(Return(kGoodWindow));
+ RepeatedlyRunningInThisWindowThread(kOtherGoodWindow);
+ executor_->set_id(kOtherGoodWindow);
+ EXPECT_HRESULT_SUCCEEDED(executor_->GetWindow(FALSE, &window_info));
+ EXPECT_EQ(FALSE, window_info.focused);
+ EXPECT_EQ(window_rect.top, window_info.rect.top);
+ EXPECT_EQ(window_rect.left, window_info.rect.left);
+ EXPECT_EQ(window_rect.bottom, window_info.rect.bottom);
+ EXPECT_EQ(window_rect.right, window_info.rect.right);
+ EXPECT_EQ(NULL, window_info.tab_list);
+
+ // Fail with tab population. We'll test tab population with GetTabs later.
+ // GetTabs will fail but at least we confirm that it gets called :-)...
+ EXPECT_CALL(*executor_, GetTabs(NotNull())).
+ WillOnce(Return(E_FAIL));
+ EXPECT_HRESULT_FAILED(executor_->GetWindow(TRUE, &window_info));
+ EXPECT_EQ(NULL, window_info.tab_list);
+}
+
+TEST_F(ExecutorTests, GetTabsEnumProc) {
+ std::vector<HWND> tab_windows;
+ HWND bad_window1 = reinterpret_cast<HWND>(666);
+ HWND bad_window2 = reinterpret_cast<HWND>(999);
+ EXPECT_CALL(mock_window_utils_, IsWindowClass(kGoodWindow, _)).
+ WillOnce(Return(true));
+ EXPECT_CALL(mock_window_utils_, IsWindowClass(kOtherGoodWindow, _)).
+ WillOnce(Return(true));
+ EXPECT_CALL(mock_window_utils_, IsWindowClass(bad_window1, _)).
+ WillOnce(Return(false));
+ EXPECT_CALL(mock_window_utils_, IsWindowClass(bad_window2, _)).
+ WillOnce(Return(false));
+ LPARAM param = reinterpret_cast<LPARAM>(&tab_windows);
+ EXPECT_TRUE(TestingExecutor::GetTabsEnumProc(kGoodWindow, param));
+ EXPECT_TRUE(TestingExecutor::GetTabsEnumProc(bad_window1, param));
+ EXPECT_TRUE(TestingExecutor::GetTabsEnumProc(kOtherGoodWindow, param));
+ EXPECT_TRUE(TestingExecutor::GetTabsEnumProc(bad_window2, param));
+ EXPECT_EQ(2, tab_windows.size());
+ EXPECT_EQ(kGoodWindow, tab_windows[0]);
+ EXPECT_EQ(kOtherGoodWindow, tab_windows[1]);
+}
+
+TEST_F(ExecutorTests, GetTabs) {
+ testing::LogDisabler no_dchecks;
+ executor_->set_id(kGoodWindow);
+
+ // Not running in appropriate thread.
+ NotRunningInThisWindowThread(kGoodWindow);
+ EXPECT_HRESULT_FAILED(executor_->CallGetTabs(NULL));
+
+ // We already tested the case where we don't have a tab manager.
+ RepeatedlyRunningInThisWindowThread(kGoodWindow);
+ MockTabManager();
+ EXPECT_CALL(user32_, EnumChildWindows(kGoodWindow, _, _)).
+ WillRepeatedly(Invoke(TestingExecutor::MockEnumChildWindows));
+
+ // No tabs case.
+ CComBSTR result;
+ EXPECT_HRESULT_SUCCEEDED(executor_->CallGetTabs(&result));
+ EXPECT_STREQ(L"[]", result.m_str);
+ result.Empty();
+
+ static const int kBadWindowId = 21;
+ static const HWND kBadWindow = reinterpret_cast<HWND>(kBadWindowId);
+
+ // Fail to get a tab index.
+ EXPECT_CALL(*manager_, IndexFromHWND(kGoodWindow, NotNull())).
+ WillOnce(Return(E_FAIL));
+ std::vector<HWND> tab_windows;
+ tab_windows.push_back(kGoodWindow);
+ EXPECT_CALL(user32_, IsWindow(kGoodWindow)).WillOnce(Return(TRUE));
+ executor_->set_tab_windows(tab_windows);
+ EXPECT_HRESULT_FAILED(executor_->CallGetTabs(&result));
+
+ // Successfully return 1 tab.
+ EXPECT_CALL(*manager_, IndexFromHWND(kGoodWindow, NotNull())).
+ WillOnce(DoAll(SetArgumentPointee<1>(kTabIndex), Return(S_OK)));
+ EXPECT_HRESULT_SUCCEEDED(executor_->CallGetTabs(&result));
+ wchar_t expected_result[16];
+ wnsprintf(expected_result, 16, L"[%d,%d]", kGoodWindowId, kTabIndex);
+ EXPECT_STREQ(expected_result, result.m_str);
+ result.Empty();
+
+ // Successfully return 2 tabs.
+ EXPECT_CALL(*manager_, IndexFromHWND(kGoodWindow, NotNull())).
+ WillOnce(DoAll(SetArgumentPointee<1>(kTabIndex), Return(S_OK)));
+ EXPECT_CALL(*manager_, IndexFromHWND(kOtherGoodWindow, NotNull())).
+ WillOnce(DoAll(SetArgumentPointee<1>(kOtherTabIndex), Return(S_OK)));
+ tab_windows.push_back(kOtherGoodWindow);
+ executor_->set_tab_windows(tab_windows);
+ EXPECT_HRESULT_SUCCEEDED(executor_->CallGetTabs(&result));
+ wnsprintf(expected_result, 16, L"[%d,%d,%d,%d]", kGoodWindowId, kTabIndex,
+ kOtherGoodWindowId, kOtherTabIndex);
+ EXPECT_STREQ(expected_result, result.m_str);
+
+ // Successfully return 2 out of 3 tabs.
+ EXPECT_CALL(*manager_, IndexFromHWND(kGoodWindow, NotNull())).
+ WillOnce(DoAll(SetArgumentPointee<1>(kTabIndex), Return(S_OK)));
+ EXPECT_CALL(*manager_, IndexFromHWND(kOtherGoodWindow, NotNull())).
+ WillOnce(DoAll(SetArgumentPointee<1>(kOtherTabIndex), Return(S_OK)));
+ EXPECT_CALL(*manager_, IndexFromHWND(kBadWindow, NotNull())).
+ WillOnce(Return(E_FAIL));
+ EXPECT_CALL(user32_, IsWindow(kBadWindow)).WillOnce(Return(FALSE));
+ tab_windows.push_back(kBadWindow);
+ executor_->set_tab_windows(tab_windows);
+ EXPECT_HRESULT_SUCCEEDED(executor_->CallGetTabs(&result));
+ wnsprintf(expected_result, 16, L"[%d,%d,%d,%d]", kGoodWindowId, kTabIndex,
+ kOtherGoodWindowId, kOtherTabIndex);
+ EXPECT_STREQ(expected_result, result.m_str);
+}
+
+TEST_F(ExecutorTests, UpdateWindow) {
+ testing::LogDisabler no_dchecks;
+ executor_->set_id(kGoodWindow);
+
+ // Not running in appropriate thread.
+ NotRunningInThisWindowThread(kGoodWindow);
+ EXPECT_HRESULT_FAILED(executor_->UpdateWindow(0, 0, 0, 0, NULL));
+ // No other failure path, go straight to success...
+ RepeatedlyRunningInThisWindowThread(kGoodWindow);
+
+ RECT window_rect = {1, 2, 3, 5};
+ EXPECT_CALL(user32_, GetWindowRect(kGoodWindow, NotNull())).
+ WillRepeatedly(DoAll(SetArgumentPointee<1>(window_rect), Return(TRUE)));
+ // These will be called from GetWindow which is called at the end of Update.
+ EXPECT_CALL(user32_, GetForegroundWindow()).
+ WillRepeatedly(Return(kGoodWindow));
+ EXPECT_CALL(mock_window_utils_, GetTopLevelParent(kGoodWindow)).
+ WillRepeatedly(Return(kGoodWindow));
+ // Try with no change at first.
+ EXPECT_CALL(user32_, MoveWindow(kGoodWindow, 1, 2, 2, 3, TRUE)).Times(1);
+ common_api::WindowInfo window_info;
+ EXPECT_HRESULT_SUCCEEDED(executor_->UpdateWindow(
+ -1, -1, -1, -1, &window_info));
+ EXPECT_EQ(TRUE, window_info.focused);
+ EXPECT_EQ(window_rect.top, window_info.rect.top);
+ EXPECT_EQ(window_rect.left, window_info.rect.left);
+ EXPECT_EQ(window_rect.bottom, window_info.rect.bottom);
+ EXPECT_EQ(window_rect.right, window_info.rect.right);
+ EXPECT_EQ(NULL, window_info.tab_list);
+
+ // Now try with some changes incrementally.
+ EXPECT_CALL(user32_, MoveWindow(kGoodWindow, 1, 2, 2, 30, TRUE)).Times(1);
+ EXPECT_HRESULT_SUCCEEDED(executor_->UpdateWindow(
+ -1, -1, -1, 30, &window_info));
+ EXPECT_EQ(NULL, window_info.tab_list);
+
+ EXPECT_CALL(user32_, MoveWindow(kGoodWindow, 1, 2, 20, 3, TRUE)).Times(1);
+ EXPECT_HRESULT_SUCCEEDED(executor_->UpdateWindow(
+ -1, -1, 20, -1, &window_info));
+ EXPECT_EQ(NULL, window_info.tab_list);
+
+ EXPECT_CALL(user32_, MoveWindow(kGoodWindow, 1, 0, 2, 5, TRUE)).Times(1);
+ EXPECT_HRESULT_SUCCEEDED(executor_->UpdateWindow(
+ -1, 0, -1, -1, &window_info));
+ EXPECT_EQ(NULL, window_info.tab_list);
+
+ EXPECT_CALL(user32_, MoveWindow(kGoodWindow, 0, 2, 3, 3, TRUE)).Times(1);
+ EXPECT_HRESULT_SUCCEEDED(executor_->UpdateWindow(
+ 0, -1, -1, -1, &window_info));
+ EXPECT_EQ(NULL, window_info.tab_list);
+
+ EXPECT_CALL(user32_, MoveWindow(kGoodWindow, 8, 13, 21, 34, TRUE)).Times(1);
+ EXPECT_HRESULT_SUCCEEDED(executor_->UpdateWindow(
+ 8, 13, 21, 34, &window_info));
+ EXPECT_EQ(NULL, window_info.tab_list);
+}
+
+TEST_F(ExecutorTests, RemoveWindow) {
+ testing::LogDisabler no_dchecks;
+ executor_->set_id(kGoodWindow);
+
+ // Not running in appropriate thread.
+ NotRunningInThisWindowThread(kGoodWindow);
+ EXPECT_HRESULT_FAILED(executor_->RemoveWindow());
+
+ // Now let the manager succeed.
+ RepeatedlyRunningInThisWindowThread(kGoodWindow);
+ MockTabManager();
+ EXPECT_CALL(*manager_, CloseAllTabs()).WillOnce(Return(S_OK));
+ EXPECT_CALL(user32_, PostMessage(kGoodWindow, WM_CLOSE, 0, 0)).
+ Times(0);
+ EXPECT_HRESULT_SUCCEEDED(executor_->RemoveWindow());
+}
+
+TEST_F(ExecutorTests, GetTabInfo) {
+ testing::LogDisabler no_dchecks;
+ MockSite();
+ executor_->set_id(kGoodWindow);
+
+ // Not running in appropriate thread.
+ NotRunningInThisWindowThread(kGoodWindow);
+ EXPECT_HRESULT_FAILED(executor_->GetTabInfo(NULL));
+
+ // Now can't get ready state.
+ RepeatedlyRunningInThisWindowThread(kGoodWindow);
+ EXPECT_CALL(*mock_site_, GetReadyState(NotNull())).WillOnce(Return(E_FAIL));
+ tab_api::TabInfo tab_info;
+ EXPECT_HRESULT_FAILED(executor_->GetTabInfo(&tab_info));
+ DCHECK_EQ((BSTR)NULL, tab_info.url);
+ DCHECK_EQ((BSTR)NULL, tab_info.title);
+ DCHECK_EQ((BSTR)NULL, tab_info.fav_icon_url);
+
+ // And can't get the top level browser.
+ EXPECT_CALL(*mock_site_, GetReadyState(NotNull())).WillOnce(DoAll(
+ SetArgumentPointee<0>(READYSTATE_COMPLETE), Return(S_OK)));
+ EXPECT_CALL(*mock_site_, GetTopLevelBrowser(NotNull())).
+ WillOnce(Return(E_FAIL));
+ tab_info.Clear();
+ EXPECT_HRESULT_FAILED(executor_->GetTabInfo(&tab_info));
+ DCHECK_EQ((BSTR)NULL, tab_info.url);
+ DCHECK_EQ((BSTR)NULL, tab_info.title);
+ DCHECK_EQ((BSTR)NULL, tab_info.fav_icon_url);
+
+ // Success time!
+ EXPECT_CALL(*mock_site_, GetReadyState(NotNull())).WillOnce(DoAll(
+ SetArgumentPointee<0>(READYSTATE_COMPLETE), Return(S_OK)));
+ CComObject<StrictMock<testing::MockIWebBrowser2>>::CreateInstance(&browser_);
+ DCHECK(browser_ != NULL);
+ browser_keeper_ = browser_;
+ EXPECT_CALL(*mock_site_, GetTopLevelBrowser(NotNull())).
+ WillRepeatedly(DoAll(CopyInterfaceToArgument<0>(browser_keeper_),
+ Return(S_OK)));
+ EXPECT_CALL(*browser_, get_LocationURL(NotNull())).
+ WillOnce(DoAll(CopyBSTRToArgument<0>(kUrl1), Return(S_OK)));
+ EXPECT_CALL(*browser_, get_LocationName(NotNull())).
+ WillOnce(DoAll(CopyBSTRToArgument<0>(kTitle1), Return(S_OK)));
+
+ tab_info.Clear();
+ EXPECT_HRESULT_SUCCEEDED(executor_->GetTabInfo(&tab_info));
+ EXPECT_STREQ(kUrl1, tab_info.url);
+ EXPECT_STREQ(kTitle1, tab_info.title);
+ EXPECT_EQ(kCeeeTabStatusComplete, tab_info.status);
+
+ // With other values
+ RepeatedlyRunningInThisWindowThread(kOtherGoodWindow);
+ // Reset the HWND
+ executor_->set_id(kOtherGoodWindow);
+ EXPECT_CALL(*mock_site_, GetReadyState(NotNull())).WillOnce(DoAll(
+ SetArgumentPointee<0>(READYSTATE_LOADING), Return(S_OK)));
+ EXPECT_CALL(*browser_, get_LocationURL(NotNull())).
+ WillOnce(DoAll(CopyBSTRToArgument<0>(kUrl2), Return(S_OK)));
+ EXPECT_CALL(*browser_, get_LocationName(NotNull())).
+ WillOnce(DoAll(CopyBSTRToArgument<0>(kTitle2), Return(S_OK)));
+
+ tab_info.Clear();
+ EXPECT_HRESULT_SUCCEEDED(executor_->GetTabInfo(&tab_info));
+ EXPECT_STREQ(kUrl2, tab_info.url);
+ EXPECT_STREQ(kTitle2, tab_info.title);
+ EXPECT_EQ(kCeeeTabStatusLoading, tab_info.status);
+}
+
+TEST_F(ExecutorTests, GetTabIndex) {
+ testing::LogDisabler no_dchecks;
+ MockSite();
+ executor_->set_id(kGoodWindow);
+
+ // Not running in appropriate thread.
+ EXPECT_CALL(mock_window_utils_, IsWindowThread(kGoodWindow)).
+ WillOnce(Return(false));
+ EXPECT_HRESULT_FAILED(executor_->GetTabIndex(kGoodWindowId, NULL));
+
+ // Success.
+ EXPECT_CALL(mock_window_utils_, IsWindowThread(kGoodWindow)).
+ WillOnce(Return(true));
+ MockTabManager();
+ long index = 0;
+ EXPECT_HRESULT_SUCCEEDED(executor_->GetTabIndex(kOtherGoodWindowId, &index));
+ EXPECT_EQ(kTabIndex, index);
+}
+
+TEST_F(ExecutorTests, Navigate) {
+ testing::LogDisabler no_dchecks;
+ MockSite();
+ executor_->set_id(kGoodWindow);
+
+ // Not running in appropriate thread.
+ NotRunningInThisWindowThread(kGoodWindow);
+ EXPECT_HRESULT_FAILED(executor_->Navigate(NULL, 0, NULL));
+
+ // Now make GetWebBrowser fail.
+ RepeatedlyRunningInThisWindowThread(kGoodWindow);
+ EXPECT_CALL(*executor_, GetWebBrowser(NotNull())).
+ WillRepeatedly(Return(E_FAIL));
+ EXPECT_HRESULT_FAILED(executor_->Navigate(NULL, 0, NULL));
+
+ // Now get URL fails.
+ MockBrowser();
+ EXPECT_CALL(*browser_, get_LocationURL(NotNull())).WillOnce(Return(E_FAIL));
+ EXPECT_HRESULT_FAILED(executor_->Navigate(NULL, 0, NULL));
+
+ // Now succeed by not needing to navigate since same URL.
+ EXPECT_CALL(*browser_, get_LocationURL(NotNull())).
+ WillRepeatedly(DoAll(CopyBSTRToArgument<0>(kUrl1), Return(S_OK)));
+ EXPECT_HRESULT_SUCCEEDED(executor_->Navigate(CComBSTR(kUrl1), 0, NULL));
+
+ // And finally succeed completely.
+ EXPECT_CALL(*browser_, Navigate(_, _, _, _, _)).WillOnce(Return(S_OK));
+ EXPECT_HRESULT_SUCCEEDED(executor_->Navigate(CComBSTR(kUrl2), 0, NULL));
+ // And fail if navigate fails.
+ EXPECT_CALL(*browser_, Navigate(_, _, _, _, _)).WillOnce(Return(E_FAIL));
+ EXPECT_HRESULT_FAILED(executor_->Navigate(CComBSTR(kUrl2), 0, NULL));
+}
+
+TEST_F(ExecutorTests, RemoveTab) {
+ testing::LogDisabler no_dchecks;
+ MockSite();
+
+ // Since we're in a WindowExecutor and not a TabExecutor, we don't get our
+ // HWND and there's no call to parent.
+ executor_->set_id(kOtherGoodWindow);
+
+ // Not running in appropriate thread.
+ NotRunningInThisWindowThread(kOtherGoodWindow);
+ EXPECT_HRESULT_FAILED(executor_->RemoveTab(kGoodWindowId));
+
+ // Fail to get the tab index.
+ RepeatedlyRunningInThisWindowThread(kOtherGoodWindow);
+ EXPECT_CALL(user32_, GetParent(_)).WillRepeatedly(Return(kGoodWindow));
+ MockTabManager();
+ EXPECT_CALL(*manager_, IndexFromHWND(kGoodWindow, NotNull())).
+ WillOnce(Return(E_FAIL));
+ EXPECT_HRESULT_FAILED(executor_->RemoveTab(kGoodWindowId));
+
+ // Fail to get the tab interface.
+ EXPECT_CALL(*manager_, IndexFromHWND(kGoodWindow, NotNull())).
+ WillRepeatedly(DoAll(SetArgumentPointee<1>(kTabIndex), Return(S_OK)));
+ EXPECT_CALL(*manager_, GetItem(kTabIndex, NotNull())).
+ WillOnce(Return(E_FAIL));
+ EXPECT_HRESULT_FAILED(executor_->RemoveTab(kGoodWindowId));
+
+ // Success.
+ CComObject<StrictMock<testing::MockITabWindowIe7>>* mock_tab;
+ CComObject<StrictMock<testing::MockITabWindowIe7>>::CreateInstance(&mock_tab);
+ CComPtr<ITabWindowIe7> mock_tab_holder(mock_tab);
+ EXPECT_CALL(*manager_, GetItem(kTabIndex, NotNull())).WillRepeatedly(DoAll(
+ SetArgumentPointee<1>(mock_tab), AddRef(mock_tab), Return(S_OK)));
+ EXPECT_CALL(*mock_tab, Close()).WillOnce(Return(S_OK));
+ EXPECT_HRESULT_SUCCEEDED(executor_->RemoveTab(kGoodWindowId));
+
+ // Failure to close.
+ EXPECT_CALL(*mock_tab, Close()).WillOnce(Return(E_FAIL));
+ EXPECT_HRESULT_FAILED(executor_->RemoveTab(kGoodWindowId));
+}
+
+TEST_F(ExecutorTests, SelectTab) {
+ testing::LogDisabler no_dchecks;
+ MockSite();
+
+ executor_->set_id(kOtherGoodWindow);
+
+ // Not running in appropriate thread.
+ NotRunningInThisWindowThread(kOtherGoodWindow);
+ EXPECT_HRESULT_FAILED(executor_->SelectTab(kGoodWindowId));
+
+ // Fail to get the tab index.
+ RepeatedlyRunningInThisWindowThread(kOtherGoodWindow);
+ EXPECT_CALL(user32_, GetParent(_)).WillRepeatedly(Return(kGoodWindow));
+ MockTabManager();
+ EXPECT_CALL(*manager_, IndexFromHWND(kGoodWindow, NotNull())).
+ WillOnce(Return(E_FAIL));
+ EXPECT_HRESULT_FAILED(executor_->SelectTab(kGoodWindowId));
+
+ // Success!
+ EXPECT_CALL(*manager_, IndexFromHWND(kGoodWindow, NotNull())).
+ WillRepeatedly(DoAll(SetArgumentPointee<1>(kTabIndex), Return(S_OK)));
+ EXPECT_CALL(*manager_, SelectTab(kTabIndex)).WillOnce(Return(S_OK));
+ EXPECT_HRESULT_SUCCEEDED(executor_->SelectTab(kGoodWindowId));
+
+ // Failure to Select.
+ EXPECT_CALL(*manager_, SelectTab(kTabIndex)).WillOnce(Return(E_FAIL));
+ EXPECT_HRESULT_FAILED(executor_->SelectTab(kGoodWindowId));
+}
+
+TEST_F(ExecutorTests, MoveTab) {
+ testing::LogDisabler no_dchecks;
+ MockTabManager();
+ MockSite();
+ RepeatedlyRunningInThisWindowThread(kGoodWindow);
+ executor_->set_id(kGoodWindow);
+
+ // Fail to get tab count.
+ EXPECT_CALL(*manager_, GetCount(NotNull())).WillOnce(Return(E_FAIL));
+ EXPECT_HRESULT_FAILED(executor_->MoveTab(kOtherGoodWindowId, 0));
+
+ // Nothing to be done.
+ LONG nb_tabs = 3;
+ EXPECT_CALL(*manager_, GetCount(NotNull())).WillRepeatedly(DoAll(
+ SetArgumentPointee<0>(nb_tabs), Return(S_OK)));
+ EXPECT_HRESULT_SUCCEEDED(executor_->MoveTab(kOtherGoodWindowId, 2));
+ EXPECT_HRESULT_SUCCEEDED(executor_->MoveTab(kOtherGoodWindowId, 99));
+
+ // Fail to get the first tab interface.
+ EXPECT_CALL(*manager_, GetItem(0, NotNull())).WillOnce(Return(E_FAIL));
+ EXPECT_HRESULT_FAILED(executor_->MoveTab(kOtherGoodWindowId, 0));
+
+ // Fail to get the tab id.
+ CComObject<StrictMock<testing::MockITabWindowIe7>>* mock_tab0;
+ CComObject<StrictMock<testing::MockITabWindowIe7>>::CreateInstance(
+ &mock_tab0);
+ CComPtr<ITabWindowIe7> mock_tab_holder0(mock_tab0);
+ EXPECT_CALL(*manager_, GetItem(0, NotNull())).WillRepeatedly(DoAll(
+ SetArgumentPointee<1>(mock_tab0), AddRef(mock_tab0), Return(S_OK)));
+ EXPECT_CALL(*mock_tab0, GetID(NotNull())).WillOnce(Return(E_FAIL));
+ EXPECT_HRESULT_FAILED(executor_->MoveTab(kOtherGoodWindowId, 0));
+
+ // Fail to get the second tab interface.
+ EXPECT_CALL(*mock_tab0, GetID(NotNull())).WillRepeatedly(
+ DoAll(SetArgumentPointee<0>(kGoodWindowId), Return(S_OK)));
+ EXPECT_CALL(*manager_, GetItem(nb_tabs - 1, NotNull()))
+ .WillOnce(Return(E_FAIL));
+ EXPECT_HRESULT_FAILED(executor_->MoveTab(kOtherGoodWindowId, 0));
+
+ // Fail to get the second tab id.
+ CComObject<StrictMock<testing::MockITabWindowIe7>>* mock_tab1;
+ CComObject<StrictMock<testing::MockITabWindowIe7>>::CreateInstance(
+ &mock_tab1);
+ CComPtr<ITabWindowIe7> mock_tab_holder1(mock_tab1);
+ EXPECT_CALL(*manager_, GetItem(nb_tabs - 1, NotNull())).WillRepeatedly(DoAll(
+ SetArgumentPointee<1>(mock_tab1), AddRef(mock_tab1), Return(S_OK)));
+ EXPECT_CALL(*mock_tab1, GetID(NotNull())).WillOnce(Return(E_FAIL));
+ EXPECT_HRESULT_FAILED(executor_->MoveTab(kOtherGoodWindowId, 0));
+
+ // Fail to reposition.
+ EXPECT_CALL(*mock_tab1, GetID(NotNull())).WillRepeatedly(
+ DoAll(SetArgumentPointee<0>(kOtherGoodWindowId), Return(S_OK)));
+ EXPECT_CALL(*manager_, RepositionTab(kOtherGoodWindowId, kGoodWindowId, 0))
+ .WillOnce(Return(E_FAIL));
+ EXPECT_HRESULT_FAILED(executor_->MoveTab(kOtherGoodWindowId, 0));
+
+ // Success!!
+ EXPECT_CALL(*mock_tab1, GetID(NotNull())).WillRepeatedly(
+ DoAll(SetArgumentPointee<0>(kOtherGoodWindowId), Return(S_OK)));
+ EXPECT_CALL(*manager_, RepositionTab(kOtherGoodWindowId, kGoodWindowId, 0))
+ .WillRepeatedly(Return(S_OK));
+ EXPECT_HRESULT_SUCCEEDED(executor_->MoveTab(kOtherGoodWindowId, 0));
+}
+
+TEST_F(ExecutorTests, GetCookieValue) {
+ testing::LogDisabler no_dchecks;
+ MockWinInet mock_wininet;
+
+ CComBSTR url(L"http://foobar.com");
+ CComBSTR name(L"HELLOWORLD");
+ // Bad parameters.
+ EXPECT_HRESULT_FAILED(executor_->CallGetCookieValue(url, name, NULL));
+
+ // Failure to get cookie.
+ EXPECT_CALL(mock_wininet, InternetGetCookieExW(_, _, IsNull(), _, _, _))
+ .WillOnce(Return(FALSE));
+ ::SetLastError(ERROR_INVALID_PARAMETER);
+ CComBSTR value;
+ EXPECT_HRESULT_FAILED(executor_->CallGetCookieValue(url, name, &value));
+ EXPECT_EQ((BSTR)NULL, value);
+
+ // Nonexistent cookie.
+ EXPECT_CALL(mock_wininet, InternetGetCookieExW(_, _, IsNull(), _, _, _))
+ .WillOnce(Return(FALSE));
+ ::SetLastError(ERROR_NO_MORE_ITEMS);
+ EXPECT_EQ(S_FALSE, executor_->CallGetCookieValue(url, name, &value));
+ EXPECT_EQ((BSTR)NULL, value);
+
+ // Malformed cookie.
+ EXPECT_CALL(mock_wininet, InternetGetCookieExW(_, _, IsNull(), _, _, _))
+ .WillRepeatedly(DoAll(SetArgumentPointee<3>(100), Return(TRUE)));
+ EXPECT_CALL(mock_wininet, InternetGetCookieExW(_, _, NotNull(), _, _, _))
+ .WillRepeatedly(Invoke(TestingExecutor::MockInternetGetCookieExW));
+ executor_->set_cookie_data(L"malformed_cookie_data");
+ EXPECT_EQ(E_FAIL, executor_->CallGetCookieValue(url, name, &value));
+ EXPECT_EQ((BSTR)NULL, value);
+ executor_->set_cookie_data(L"AnotherCookie=FOOBAR");
+ EXPECT_EQ(E_FAIL, executor_->CallGetCookieValue(url, name, &value));
+ EXPECT_EQ((BSTR)NULL, value);
+
+ // Well-behaved cookie.
+ executor_->set_cookie_data(L"HELLOWORLD=1234567890");
+ EXPECT_EQ(S_OK, executor_->CallGetCookieValue(url, name, &value));
+ EXPECT_STREQ(L"1234567890", value);
+ executor_->set_cookie_data(L"=1234567890");
+ EXPECT_EQ(S_OK, executor_->CallGetCookieValue(url, CComBSTR(L""), &value));
+ EXPECT_STREQ(L"1234567890", value);
+ executor_->set_cookie_data(L"HELLOWORLD=");
+ EXPECT_EQ(S_OK, executor_->CallGetCookieValue(url, name, &value));
+ EXPECT_STREQ(L"", value);
+ executor_->set_cookie_data(L"=");
+ EXPECT_EQ(S_OK, executor_->CallGetCookieValue(url, CComBSTR(L""), &value));
+ EXPECT_STREQ(L"", value);
+}
+
+TEST_F(ExecutorTests, GetCookieValueFlagsByIeVersion) {
+ testing::LogDisabler no_dchecks;
+ MockWinInet mock_wininet;
+ MockIeUtil mock_ie_util;
+
+ CComBSTR url(L"http://foobar.com");
+ CComBSTR name(L"HELLOWORLD");
+
+ // Test IE7 and below.
+ DWORD expected_flags = 0;
+ EXPECT_CALL(mock_wininet, InternetGetCookieExW(_, _, IsNull(), _,
+ expected_flags, _))
+ .WillRepeatedly(DoAll(SetArgumentPointee<3>(100), Return(TRUE)));
+ EXPECT_CALL(mock_wininet, InternetGetCookieExW(_, _, NotNull(), _,
+ expected_flags, _))
+ .WillRepeatedly(Invoke(TestingExecutor::MockInternetGetCookieExW));
+
+ EXPECT_CALL(mock_ie_util, GetIeVersion())
+ .WillOnce(Return(ie_util::IEVERSION_IE6));
+ executor_->set_cookie_data(L"HELLOWORLD=1234567890");
+ CComBSTR value;
+ EXPECT_EQ(S_OK, executor_->CallGetCookieValue(url, name, &value));
+
+ EXPECT_CALL(mock_ie_util, GetIeVersion())
+ .WillOnce(Return(ie_util::IEVERSION_IE7));
+ executor_->set_cookie_data(L"HELLOWORLD=1234567890");
+ EXPECT_EQ(S_OK, executor_->CallGetCookieValue(url, name, &value));
+
+ // Test IE8 and above.
+ expected_flags = INTERNET_COOKIE_HTTPONLY;
+ EXPECT_CALL(mock_wininet, InternetGetCookieExW(_, _, IsNull(), _,
+ expected_flags, _))
+ .WillRepeatedly(DoAll(SetArgumentPointee<3>(100), Return(TRUE)));
+ EXPECT_CALL(mock_wininet, InternetGetCookieExW(_, _, NotNull(), _,
+ expected_flags, _))
+ .WillRepeatedly(Invoke(TestingExecutor::MockInternetGetCookieExW));
+
+ EXPECT_CALL(mock_ie_util, GetIeVersion())
+ .WillOnce(Return(ie_util::IEVERSION_IE8));
+ executor_->set_cookie_data(L"HELLOWORLD=1234567890");
+ EXPECT_EQ(S_OK, executor_->CallGetCookieValue(url, name, &value));
+
+ EXPECT_CALL(mock_ie_util, GetIeVersion())
+ .WillOnce(Return(ie_util::IEVERSION_IE9));
+ executor_->set_cookie_data(L"HELLOWORLD=1234567890");
+ EXPECT_EQ(S_OK, executor_->CallGetCookieValue(url, name, &value));
+
+}
+
+TEST_F(ExecutorTests, GetCookie) {
+ testing::LogDisabler no_dchecks;
+ CComBSTR url(L"http://foobar.com");
+ CComBSTR name(L"HELLOWORLD");
+
+ EXPECT_HRESULT_FAILED(executor_->GetCookie(url, name, NULL));
+
+ // Failure to get cookie.
+ EXPECT_CALL(*executor_, GetCookieValue(_, _, NotNull()))
+ .WillOnce(Return(E_FAIL));
+ cookie_api::CookieInfo cookie_info;
+ cookie_info.name = NULL;
+ cookie_info.value = NULL;
+ EXPECT_HRESULT_FAILED(executor_->GetCookie(url, name, &cookie_info));
+ EXPECT_EQ((BSTR)NULL, cookie_info.name);
+ EXPECT_EQ((BSTR)NULL, cookie_info.value);
+
+ // Nonexistent cookie.
+ EXPECT_CALL(*executor_, GetCookieValue(_, _, NotNull()))
+ .WillOnce(Return(S_FALSE));
+ EXPECT_EQ(S_FALSE, executor_->GetCookie(url, name, &cookie_info));
+ EXPECT_EQ((BSTR)NULL, cookie_info.name);
+ EXPECT_EQ((BSTR)NULL, cookie_info.value);
+
+ // Success.
+ EXPECT_CALL(*executor_, GetCookieValue(_, _, NotNull()))
+ .WillRepeatedly(DoAll(
+ SetArgumentPointee<2>(::SysAllocString(L"abcde")),
+ Return(S_OK)));
+ EXPECT_EQ(S_OK, executor_->GetCookie(url, name, &cookie_info));
+ EXPECT_STREQ(L"HELLOWORLD", cookie_info.name);
+ EXPECT_STREQ(L"abcde", cookie_info.value);
+ EXPECT_EQ(S_OK, executor_->GetCookie(url, CComBSTR("ABC"), &cookie_info));
+ EXPECT_STREQ(L"ABC", cookie_info.name);
+ EXPECT_STREQ(L"abcde", cookie_info.value);
+}
+
+TEST_F(ExecutorTests, RegisterCookieStore) {
+ testing::LogDisabler no_dchecks;
+
+ executor_->set_cookie_store_is_registered(false);
+ EXPECT_EQ(S_FALSE, executor_->CookieStoreIsRegistered());
+ EXPECT_HRESULT_SUCCEEDED(executor_->RegisterCookieStore());
+ EXPECT_EQ(S_OK, executor_->CookieStoreIsRegistered());
+ EXPECT_HRESULT_SUCCEEDED(executor_->RegisterCookieStore());
+ EXPECT_EQ(S_OK, executor_->CookieStoreIsRegistered());
+}
+
+// TODO(vadimb@google.com): Add unit tests for infobar APIs.
+
+} // namespace
diff --git a/ceee/ie/plugin/bho/extension_port_manager.cc b/ceee/ie/plugin/bho/extension_port_manager.cc
new file mode 100644
index 0000000..9bc28f5
--- /dev/null
+++ b/ceee/ie/plugin/bho/extension_port_manager.cc
@@ -0,0 +1,193 @@
+// 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.
+//
+// Extension port manager takes care of managing the state of
+// connecting and connected ports.
+#include "ceee/ie/plugin/bho/extension_port_manager.h"
+#include "base/logging.h"
+#include "base/scoped_ptr.h"
+#include "base/values.h"
+#include "base/json/json_reader.h"
+#include "base/json/json_writer.h"
+#include "ceee/ie/common/chrome_frame_host.h"
+#include "ceee/ie/plugin/scripting/content_script_native_api.h"
+#include "chrome/browser/automation/extension_automation_constants.h"
+
+namespace ext = extension_automation_constants;
+
+ExtensionPortManager::ExtensionPortManager() {
+}
+
+ExtensionPortManager::~ExtensionPortManager() {
+}
+
+void ExtensionPortManager::Initialize(IChromeFrameHost* chrome_frame_host) {
+ chrome_frame_host_ = chrome_frame_host;
+}
+
+void ExtensionPortManager::CloseAll(IContentScriptNativeApi* instance) {
+ DCHECK(instance != NULL);
+
+ // TODO(siggi@chromium.org): Deal better with these cases. Connected
+ // ports probably ought to be closed, and connecting ports may
+ // need to hang around until we get the connected message, to be
+ // terminated at that point.
+ ConnectedPortMap::iterator it(connected_ports_.begin());
+ while (it != connected_ports_.end()) {
+ if (it->second.instance = instance) {
+ connected_ports_.erase(it++);
+ } else {
+ ++it;
+ }
+ }
+
+ ConnectingPortMap::iterator jt(connecting_ports_.begin());
+ while (jt != connecting_ports_.end()) {
+ if (jt->second.instance = instance) {
+ connecting_ports_.erase(jt++);
+ } else {
+ ++jt;
+ }
+ }
+}
+
+HRESULT ExtensionPortManager::OpenChannelToExtension(
+ IContentScriptNativeApi* instance, const std::string& extension,
+ const std::string& channel_name, Value* tab, int cookie) {
+ int connection_id = next_connection_id_++;
+
+ // Prepare the connection request.
+ scoped_ptr<DictionaryValue> dict(new DictionaryValue());
+ if (dict.get() == NULL)
+ return E_OUTOFMEMORY;
+
+ dict->SetInteger(ext::kAutomationRequestIdKey, ext::OPEN_CHANNEL);
+ dict->SetInteger(ext::kAutomationConnectionIdKey, connection_id);
+ dict->SetString(ext::kAutomationExtensionIdKey, extension);
+ dict->SetString(ext::kAutomationChannelNameKey, channel_name);
+ dict->Set(ext::kAutomationTabJsonKey, tab);
+
+ // JSON encode it.
+ std::string request_json;
+ base::JSONWriter::Write(dict.get(), false, &request_json);
+ // And fire it off.
+ HRESULT hr = PostMessageToHost(request_json,
+ ext::kAutomationPortRequestTarget);
+ if (FAILED(hr))
+ return hr;
+
+ ConnectingPort connecting_port = { instance, cookie };
+ connecting_ports_[connection_id] = connecting_port;
+
+ return S_OK;
+}
+
+HRESULT ExtensionPortManager::PostMessage(int port_id,
+ const std::string& message) {
+ // Wrap the message for sending as a port request.
+ scoped_ptr<DictionaryValue> dict(new DictionaryValue());
+ if (dict.get() == NULL)
+ return E_OUTOFMEMORY;
+
+ dict->SetInteger(ext::kAutomationRequestIdKey, ext::POST_MESSAGE);
+ dict->SetInteger(ext::kAutomationPortIdKey, port_id);
+ dict->SetString(ext::kAutomationMessageDataKey, message);
+
+ // JSON encode it.
+ std::string message_json;
+ base::JSONWriter::Write(dict.get(), false, &message_json);
+
+ // And fire it off.
+ return PostMessageToHost(message_json,
+ std::string(ext::kAutomationPortRequestTarget));
+}
+
+void ExtensionPortManager::OnPortMessage(BSTR message) {
+ std::string message_json = CW2A(message);
+ scoped_ptr<Value> value(base::JSONReader::Read(message_json, true));
+ if (!value.get() || !value->IsType(Value::TYPE_DICTIONARY)) {
+ NOTREACHED();
+ LOG(ERROR) << "Invalid message";
+ return;
+ }
+
+ DictionaryValue* dict = static_cast<DictionaryValue*>(value.get());
+ int request = -1;
+ if (!dict->GetInteger(ext::kAutomationRequestIdKey, &request)) {
+ NOTREACHED();
+ LOG(ERROR) << "Request ID missing";
+ return;
+ }
+
+ if (request == ext::CHANNEL_OPENED) {
+ int connection_id = -1;
+ if (!dict->GetInteger(ext::kAutomationConnectionIdKey, &connection_id)) {
+ NOTREACHED();
+ LOG(ERROR) << "Connection ID missing";
+ return;
+ }
+
+ int port_id = -1;
+ if (!dict->GetInteger(ext::kAutomationPortIdKey, &port_id)) {
+ NOTREACHED();
+ LOG(ERROR) << "Port ID missing";
+ return;
+ }
+
+ ConnectingPortMap::iterator it(connecting_ports_.find(connection_id));
+ if (it == connecting_ports_.end()) {
+ // TODO(siggi@chromium.org): This can happen legitimately on a
+ // race between connect and document unload. We should
+ // probably respond with a close port message here.
+ NOTREACHED();
+ LOG(ERROR) << "No such connection id " << connection_id;
+ return;
+ }
+ ConnectingPort port = it->second;
+ connecting_ports_.erase(it);
+ // Did it connect successfully?
+ if (port_id != -1)
+ connected_ports_[port_id].instance = port.instance;
+
+ port.instance->OnChannelOpened(port.cookie, port_id);
+ return;
+ } else if (request == ext::POST_MESSAGE) {
+ int port_id = -1;
+ if (!dict->GetInteger(ext::kAutomationPortIdKey, &port_id)) {
+ NOTREACHED();
+ LOG(ERROR) << "No port id";
+ return;
+ }
+
+ std::string data;
+ if (!dict->GetString(ext::kAutomationMessageDataKey, &data)) {
+ NOTREACHED();
+ LOG(ERROR) << "No message data";
+ return;
+ }
+
+ ConnectedPortMap::iterator it(connected_ports_.find(port_id));
+ if (it == connected_ports_.end()) {
+ NOTREACHED();
+ LOG(ERROR) << "No such port " << port_id;
+ return;
+ }
+
+ it->second.instance->OnPostMessage(port_id, data);
+ return;
+ } else if (request == ext::CHANNEL_CLOSED) {
+ // TODO(siggi@chromium.org): handle correctly.
+ return;
+ }
+
+ NOTREACHED();
+}
+
+HRESULT ExtensionPortManager::PostMessageToHost(const std::string& message,
+ const std::string& target) {
+ // Post our message through the ChromeFrameHost. We allow queueing,
+ // because we don't synchronize to the destination extension loading.
+ return chrome_frame_host_->PostMessage(CComBSTR(message.c_str()),
+ CComBSTR(target.c_str()));
+}
diff --git a/ceee/ie/plugin/bho/extension_port_manager.h b/ceee/ie/plugin/bho/extension_port_manager.h
new file mode 100644
index 0000000..dc156da
--- /dev/null
+++ b/ceee/ie/plugin/bho/extension_port_manager.h
@@ -0,0 +1,86 @@
+// 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.
+//
+// Extension port manager takes care of managing the state of
+// connecting and connected ports.
+#ifndef CEEE_IE_PLUGIN_BHO_EXTENSION_PORT_MANAGER_H_
+#define CEEE_IE_PLUGIN_BHO_EXTENSION_PORT_MANAGER_H_
+
+#include <atlbase.h>
+#include <atlcom.h>
+#include <map>
+#include <string>
+#include "base/values.h"
+
+// Fwd.
+class IContentScriptNativeApi;
+class IChromeFrameHost;
+
+// The extension port manager takes care of:
+// - Managing the state of connecting and connected ports
+// - Transforming connection and post requests to outgoing message traffic.
+// - Processing incoming message traffic and routing it to the appropriate
+// Native API instances for handling.
+class ExtensionPortManager {
+ public:
+ ExtensionPortManager();
+ virtual ~ExtensionPortManager();
+
+ void Initialize(IChromeFrameHost* host);
+
+ // Cleanup ports on native API uninitialization.
+ // @param instance the instance that's going away.
+ void CloseAll(IContentScriptNativeApi* instance);
+
+ // Request opening a chnnel to an extension.
+ // @param instance the API instance that will be handling this connection.
+ // @param extension the extension to connect to.
+ // @param tab info on the tab we're initiating a connection from.
+ // @param cookie an opaque cookie that will be handed back to the
+ // instance once the connection completes or fails.
+ HRESULT OpenChannelToExtension(IContentScriptNativeApi* instance,
+ const std::string& extension,
+ const std::string& channel_name,
+ Value* tab,
+ int cookie);
+
+ // Post a message on an open port.
+ // @param port_id the port's ID.
+ // @param message the message to send.
+ HRESULT PostMessage(int port_id, const std::string& message);
+
+ // Process an incoming automation port message from the host.
+ // @param message the automation message to process.
+ void OnPortMessage(BSTR message);
+
+ private:
+ virtual HRESULT PostMessageToHost(const std::string& message,
+ const std::string& target);
+
+ // Represents a connected port.
+ struct ConnectedPort {
+ CComPtr<IContentScriptNativeApi> instance;
+ };
+ typedef std::map<int, ConnectedPort> ConnectedPortMap;
+
+ // Represents a connecting port.
+ struct ConnectingPort {
+ CComPtr<IContentScriptNativeApi> instance;
+ int cookie;
+ };
+ typedef std::map<int, ConnectingPort> ConnectingPortMap;
+
+ // Map from port_id to page api instance.
+ ConnectedPortMap connected_ports_;
+
+ // Map from connection_id to page api and callback instances.
+ ConnectingPortMap connecting_ports_;
+
+ // The next connection id we'll assign.
+ int next_connection_id_;
+
+ CComPtr<IChromeFrameHost> chrome_frame_host_;
+};
+
+#endif // CEEE_IE_PLUGIN_BHO_EXTENSION_PORT_MANAGER_H_
diff --git a/ceee/ie/plugin/bho/frame_event_handler.cc b/ceee/ie/plugin/bho/frame_event_handler.cc
new file mode 100644
index 0000000..a32cc6f
--- /dev/null
+++ b/ceee/ie/plugin/bho/frame_event_handler.cc
@@ -0,0 +1,518 @@
+// 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.
+//
+// @file
+// Frame event handler implementation.
+#include "ceee/ie/plugin/bho/frame_event_handler.h"
+#include <mshtml.h>
+#include <shlguid.h>
+#include "base/file_util.h"
+#include "base/logging.h"
+#include "ceee/ie/common/ceee_module_util.h"
+#include "ceee/ie/plugin/bho/dom_utils.h"
+#include "ceee/ie/plugin/scripting/script_host.h"
+#include "ceee/common/com_utils.h"
+#include "chrome/common/extensions/extension_resource.h"
+#include "third_party/activscp/activdbg.h"
+#include "toolband.h" // NOLINT
+
+// {242CD33B-B386-44a7-B685-3A9999B95420}
+const GUID IID_IFrameEventHandler =
+ { 0x242cd33b, 0xb386, 0x44a7,
+ { 0xb6, 0x85, 0x3a, 0x99, 0x99, 0xb9, 0x54, 0x20 } };
+
+// {E68D8538-0E0F-4d42-A4A2-1A635675F376}
+const GUID IID_IFrameEventHandlerHost =
+ { 0xe68d8538, 0xe0f, 0x4d42,
+ { 0xa4, 0xa2, 0x1a, 0x63, 0x56, 0x75, 0xf3, 0x76 } };
+
+
+FrameEventHandler::FrameEventHandler()
+ : property_notify_sink_cookie_(kInvalidCookie),
+ advise_sink_cookie_(kInvalidCookie),
+ document_ready_state_(READYSTATE_UNINITIALIZED),
+ initialized_debugging_(false),
+ loaded_css_(false),
+ loaded_start_scripts_(false),
+ loaded_end_scripts_(false) {
+}
+
+FrameEventHandler::~FrameEventHandler() {
+ DCHECK_EQ(property_notify_sink_cookie_, kInvalidCookie);
+ DCHECK_EQ(advise_sink_cookie_, kInvalidCookie);
+}
+
+HRESULT FrameEventHandler::Initialize(IWebBrowser2* browser,
+ IWebBrowser2* parent_browser,
+ IFrameEventHandlerHost* host) {
+ DCHECK(browser != NULL);
+ DCHECK(host != NULL);
+
+ InitializeContentScriptManager();
+
+ CComPtr<IDispatch> document_disp;
+ HRESULT hr = browser->get_Document(&document_disp);
+ if (FAILED(hr)) {
+ // This should never happen.
+ NOTREACHED() << "IWebBrowser2::get_Document failed " << com::LogHr(hr);
+ return hr;
+ }
+
+ // We used to check for whether the document implements IHTMLDocument2
+ // to see whether we have an HTML document instance, but that check
+ // proved not to be specific enough, as e.g. Google Chrome Frame implements
+ // IHTMLDocument2. So instead we check specifically for MSHTMLs CLSID.
+ CComQIPtr<IPersist> document_persist(document_disp);
+ bool document_is_mshtml = false;
+ if (document_persist != NULL) {
+ CLSID clsid = {};
+ hr = document_persist->GetClassID(&clsid);
+ if (SUCCEEDED(hr) && clsid == CLSID_HTMLDocument)
+ document_is_mshtml = true;
+ }
+
+ // Check that the document is an MSHTML instance, as opposed to e.g. a
+ // PDF document or a ChromeFrame server.
+ if (document_is_mshtml) {
+ document_ = document_disp;
+ DCHECK(document_ != NULL);
+
+ // Attach to the document, any error here is abnormal
+ // and should be returned to our caller.
+ hr = AttachToHtmlDocument(browser, parent_browser, host);
+
+ if (SUCCEEDED(hr))
+ // If we have a parent browser, then this frame is an iframe and we only
+ // want to match content scripts where all_frames is true.
+ hr = content_script_manager_->Initialize(host, parent_browser != NULL);
+
+ if (FAILED(hr))
+ TearDown();
+
+ return hr;
+ }
+
+ // It wasn't an HTML document, we kindly decline to attach to this one.
+ return E_DOCUMENT_NOT_MSHTML;
+}
+
+HRESULT FrameEventHandler::AttachToHtmlDocument(IWebBrowser2* browser,
+ IWebBrowser2* parent_browser,
+ IFrameEventHandlerHost* host) {
+ DCHECK(browser);
+ DCHECK(host);
+
+ // Check that we can retrieve the document's ready state.
+ READYSTATE new_ready_state = READYSTATE_UNINITIALIZED;
+ HRESULT hr = GetDocumentReadyState(&new_ready_state);
+ DCHECK(SUCCEEDED(hr)) << "Failed to retrieve document ready state "
+ << com::LogHr(hr);
+
+ // Set up the advise sink.
+ if (SUCCEEDED(hr)) {
+ CComQIPtr<IOleObject> ole_object(document_);
+ DCHECK(ole_object != NULL) << "Document is not an OLE Object";
+
+ hr = ole_object->Advise(this, &advise_sink_cookie_);
+ }
+
+ if (SUCCEEDED(hr)) {
+ DCHECK(advise_sink_cookie_ != kInvalidCookie);
+ } else {
+ DCHECK(advise_sink_cookie_ == kInvalidCookie);
+ LOG(ERROR) << "IOleObject::Advise failed " << com::LogHr(hr);
+ }
+
+ // Set up the property notify sink.
+ if (SUCCEEDED(hr)) {
+ hr = AtlAdvise(document_,
+ GetUnknown(),
+ IID_IPropertyNotifySink,
+ &property_notify_sink_cookie_);
+ if (SUCCEEDED(hr)) {
+ DCHECK(property_notify_sink_cookie_ != kInvalidCookie);
+ } else {
+ DCHECK(property_notify_sink_cookie_ == kInvalidCookie);
+ LOG(ERROR) << "Subscribing IPropertyNotifySink failed "
+ << com::LogHr(hr);
+ }
+ }
+
+ if (SUCCEEDED(hr)) {
+ // We're all good.
+ browser_ = browser;
+ parent_browser_ = parent_browser;
+ host_ = host;
+
+ if (!initialized_debugging_) {
+ initialized_debugging_ = true;
+ ScriptHost::default_debug_application()->Initialize(document_);
+ }
+
+ // Notify the host that we've attached this browser instance.
+ hr = host_->AttachBrowser(browser_, parent_browser_, this);
+ } else {
+ // We had some sort of failure, tear down any initialization we've done.
+ TearDown();
+ }
+
+ return hr;
+}
+
+HRESULT FrameEventHandler::AddSubHandler(IFrameEventHandler* handler) {
+ std::pair<SubHandlerSet::iterator, bool> ret =
+ sub_handlers_.insert(CAdapt<CComPtr<IFrameEventHandler> >(handler));
+ DCHECK(ret.second == true) << "Double notification for a sub handler";
+
+ return S_OK;
+}
+
+HRESULT FrameEventHandler::RemoveSubHandler(IFrameEventHandler* handler) {
+ size_t removed =
+ sub_handlers_.erase(CAdapt<CComPtr<IFrameEventHandler> >(handler));
+ DCHECK_NE(0U, removed) << "Errant removal notification for a sub handler";
+ DCHECK_EQ(1U, removed); // There can be only one in a set.
+
+ return S_OK;
+}
+
+void FrameEventHandler::InitializeContentScriptManager() {
+ content_script_manager_.reset(new ContentScriptManager);
+}
+
+HRESULT FrameEventHandler::GetExtensionResourceContents(const FilePath& file,
+ std::string* contents) {
+ DCHECK(contents);
+
+ std::wstring extension_path;
+ host_->GetExtensionPath(&extension_path);
+ DCHECK(extension_path.size());
+
+ FilePath file_path(ExtensionResource::GetFilePath(
+ FilePath(extension_path), file));
+
+ if (!file_util::ReadFileToString(file_path, contents)) {
+ return STG_E_FILENOTFOUND;
+ }
+
+ return S_OK;
+}
+
+HRESULT FrameEventHandler::GetCodeOrFileContents(BSTR code, BSTR file,
+ std::wstring* contents) {
+ DCHECK(contents);
+
+ // Must have one of code or file, but not both.
+ bool has_code = ::SysStringLen(code) > 0;
+ bool has_file = ::SysStringLen(file) > 0;
+ if (!(has_code ^ has_file))
+ return E_INVALIDARG;
+
+ if (has_code) {
+ *contents = code;
+ } else {
+ std::string code_string_a;
+ HRESULT hr = GetExtensionResourceContents(FilePath(file), &code_string_a);
+ if (FAILED(hr))
+ return hr;
+
+ *contents = CA2W(code_string_a.c_str());
+ }
+
+ return S_OK;
+}
+
+void FrameEventHandler::TearDownSubHandlers() {
+ // Copy the set to avoid problems with reentrant
+ // modification of the set during teardown.
+ SubHandlerSet sub_handlers(sub_handlers_);
+ SubHandlerSet::iterator it(sub_handlers.begin());
+ SubHandlerSet::iterator end(sub_handlers.end());
+ for (; it != end; ++it) {
+ DCHECK(it->m_T);
+ it->m_T->TearDown();
+ }
+
+ // In the case where we tear down subhandlers on a readystate
+ // drop, it appears that by this time, the child->parent relationship
+ // between the sub-browsers and our attached browser has been severed.
+ // We therefore can't expect our host to have issued RemoveSubHandler
+ // for our subhandlers, and should do manual cleanup.
+ sub_handlers_.clear();
+}
+
+void FrameEventHandler::TearDown() {
+ // Start by tearing down subframes.
+ TearDownSubHandlers();
+
+ // Flush content script state.
+ content_script_manager_->TearDown();
+
+ // Then detach all event sinks.
+ if (host_ != NULL) {
+ DCHECK(browser_ != NULL);
+
+ // Notify our host that we're detaching the browser.
+ HRESULT hr = host_->DetachBrowser(browser_, parent_browser_, this);
+ DCHECK(SUCCEEDED(hr));
+
+ parent_browser_.Release();
+ browser_.Release();
+ host_.Release();
+ }
+
+ // Unadvise any document events we're sinking.
+ if (property_notify_sink_cookie_ != kInvalidCookie) {
+ HRESULT hr = AtlUnadvise(document_,
+ IID_IPropertyNotifySink,
+ property_notify_sink_cookie_);
+ DCHECK(SUCCEEDED(hr)) << "Failed to unsubscribe IPropertyNotifySink "
+ << com::LogHr(hr);
+ property_notify_sink_cookie_ = kInvalidCookie;
+ }
+
+ if (advise_sink_cookie_ != kInvalidCookie) {
+ CComQIPtr<IOleObject> ole_object(document_);
+ DCHECK(ole_object != NULL);
+ HRESULT hr = ole_object->Unadvise(advise_sink_cookie_);
+ DCHECK(SUCCEEDED(hr)) << "Failed to unadvise IOleObject " << com::LogHr(hr);
+ advise_sink_cookie_ = kInvalidCookie;
+ }
+
+ document_.Release();
+}
+
+HRESULT FrameEventHandler::InsertCode(BSTR code, BSTR file,
+ CeeeTabCodeType type) {
+ std::wstring extension_id;
+ host_->GetExtensionId(&extension_id);
+ if (!extension_id.size()) {
+ // We haven't loaded an extension yet; defer until we do.
+ DeferredInjection injection = {code ? code : L"", file ? file : L"", type};
+ deferred_injections_.push_back(injection);
+ LOG(INFO) << "Deferring InsertCode";
+ return S_OK;
+ } else {
+ LOG(INFO) << "Executing InsertCode";
+ }
+
+ std::wstring code_string;
+ HRESULT hr = GetCodeOrFileContents(code, file, &code_string);
+ if (FAILED(hr))
+ return hr;
+
+ if (type == kCeeeTabCodeTypeCss) {
+ return content_script_manager_->InsertCss(code_string.c_str(), document_);
+ } else if (type == kCeeeTabCodeTypeJs) {
+ const wchar_t* file_string;
+ if (::SysStringLen(file) > 0) {
+ file_string = OLE2W(file);
+ } else {
+ // TODO(rogerta@chromium.org): should not use a hardcoded name,
+ // but one either extracted from the script itself or hashed
+ // from the code. bb2146033.
+ file_string = L"ExecuteScript.code";
+ }
+
+ return content_script_manager_->ExecuteScript(code_string.c_str(),
+ file_string,
+ document_);
+ }
+
+ return E_INVALIDARG;
+}
+
+void FrameEventHandler::RedoDoneInjections() {
+ // Any type of injection we attempted to do before extensions were
+ // loaded would have been a no-op. This function is called once
+ // extensions have been loaded to redo the ones that have
+ // already been attempted. Those that have not yet been attempted will
+ // happen later, when appropriate (e.g. on readystate complete).
+ GURL match_url(com::ToString(browser_url_));
+
+ if (loaded_css_) {
+ LoadCss(match_url);
+ }
+
+ if (loaded_start_scripts_) {
+ LoadStartScripts(match_url);
+ }
+
+ if (loaded_end_scripts_) {
+ LoadEndScripts(match_url);
+ }
+
+ // Take a copy to avoid an endless loop if we should for whatever
+ // reason still not know the extension dir (this is just belt and
+ // suspenders).
+ std::list<DeferredInjection> injections = deferred_injections_;
+ std::list<DeferredInjection>::iterator it = injections.begin();
+ for (; it != injections.end(); ++it) {
+ InsertCode(CComBSTR(it->code.c_str()),
+ CComBSTR(it->file.c_str()),
+ it->type);
+ }
+}
+
+void FrameEventHandler::FinalRelease() {
+ if (initialized_debugging_)
+ ScriptHost::default_debug_application()->Terminate();
+}
+
+HRESULT FrameEventHandler::GetDocumentReadyState(READYSTATE* ready_state) {
+ DCHECK(document_ != NULL);
+
+ CComVariant ready_state_var;
+ CComDispatchDriver document(document_);
+ HRESULT hr = document.GetProperty(DISPID_READYSTATE, &ready_state_var);
+ if (FAILED(hr))
+ return hr;
+
+ if (ready_state_var.vt != VT_I4)
+ return E_UNEXPECTED;
+
+ READYSTATE tmp = static_cast<READYSTATE>(ready_state_var.lVal);
+ DCHECK(tmp >= READYSTATE_UNINITIALIZED && tmp <= READYSTATE_COMPLETE);
+ *ready_state = tmp;
+ return S_OK;
+}
+
+void FrameEventHandler::HandleReadyStateChange(READYSTATE old_ready_state,
+ READYSTATE new_ready_state) {
+ // We should always have been notified of our corresponding document's URL
+ DCHECK(browser_url_ != NULL);
+ DCHECK(document_ != NULL);
+
+ if (new_ready_state <= READYSTATE_LOADING &&
+ old_ready_state > READYSTATE_LOADING) {
+ ReInitialize();
+ }
+
+ GURL match_url(com::ToString(browser_url_));
+
+ if (new_ready_state >= READYSTATE_LOADED && !loaded_css_) {
+ loaded_css_ = true;
+ LoadCss(match_url);
+ }
+
+ if (new_ready_state >= READYSTATE_LOADED && !loaded_start_scripts_) {
+ loaded_start_scripts_ = true;
+ LoadStartScripts(match_url);
+ }
+
+ if (new_ready_state == READYSTATE_COMPLETE && !loaded_end_scripts_) {
+ loaded_end_scripts_ = true;
+ LoadEndScripts(match_url);
+ }
+
+ // Let our host know of this change.
+ DCHECK(host_ != NULL);
+ if (host_ != NULL) {
+ HRESULT hr = host_->OnReadyStateChanged(new_ready_state);
+ DCHECK(SUCCEEDED(hr)) << com::LogHr(hr);
+ }
+}
+
+void FrameEventHandler::SetNewReadyState(READYSTATE new_ready_state) {
+ READYSTATE old_ready_state = document_ready_state_;
+ if (old_ready_state != new_ready_state) {
+ document_ready_state_ = new_ready_state;
+ HandleReadyStateChange(old_ready_state, new_ready_state);
+ }
+}
+
+void FrameEventHandler::ReInitialize() {
+ // This function should only be called when the readystate
+ // drops from above LOADING to LOADING (or below).
+ DCHECK(document_ready_state_ <= READYSTATE_LOADING);
+
+ // A readystate drop means our associated document is being
+ // re-navigated or refreshed. We need to tear down all sub
+ // frame handlers, because they'll otherwise receive no
+ // notification of this event.
+ TearDownSubHandlers();
+
+ // Reset our indicators, and manager.
+ // We'll need to re-inject on subsquent up-transitions.
+ loaded_css_ = false;
+ loaded_start_scripts_ = false;
+ loaded_end_scripts_ = false;
+
+ content_script_manager_->TearDown();
+}
+
+STDMETHODIMP FrameEventHandler::OnChanged(DISPID property_disp_id) {
+ if (property_disp_id == DISPID_READYSTATE) {
+ READYSTATE new_ready_state = READYSTATE_UNINITIALIZED;
+ HRESULT hr = GetDocumentReadyState(&new_ready_state);
+ DCHECK(SUCCEEDED(hr));
+ SetNewReadyState(new_ready_state);
+ }
+ return S_OK;
+}
+
+STDMETHODIMP FrameEventHandler::OnRequestEdit(DISPID property_disp_id) {
+ return S_OK;
+}
+
+STDMETHODIMP_(void) FrameEventHandler::OnDataChange(FORMATETC* format,
+ STGMEDIUM* storage) {
+}
+
+STDMETHODIMP_(void) FrameEventHandler::OnViewChange(DWORD aspect, LONG index) {
+}
+
+STDMETHODIMP_(void) FrameEventHandler::OnRename(IMoniker* moniker) {
+}
+
+STDMETHODIMP_(void) FrameEventHandler::OnSave() {
+}
+
+STDMETHODIMP_(void) FrameEventHandler::OnClose() {
+ // TearDown may release all references to ourselves, so we have to
+ // maintain a self-reference over this call.
+ CComPtr<IUnknown> staying_alive_oooh_oooh_oooh_staying_alive(GetUnknown());
+
+ TearDown();
+}
+
+void FrameEventHandler::GetUrl(BSTR* url) {
+ *url = CComBSTR(browser_url_).Detach();
+}
+
+HRESULT FrameEventHandler::SetUrl(BSTR url) {
+ // This method is called by our host on NavigateComplete, at which time
+ // our corresponding browser is either doing first-time navigation, or
+ // it's being re-navigated to a different URL, or possibly to the same URL.
+ // Note that as there's no NavigateComplete event fired for refresh,
+ // we won't hit here in that case, but will rather notice that case on
+ // our readystate dropping on an IPropertyNotifySink change notification.
+ // The readystate for first-time navigation on the top-level browser
+ // will be interactive, whereas for subsequent navigations and for
+ // sub-browsers, the readystate will be loading.
+ // This would be a fine time to probe and act on the readystate, except
+ // for the fact that in some weird cases, GetDocumentReadyState incorrectly
+ // returns READYSTATE_COMPLETE, which means we act too soon. So instead
+ // we patiently wait for a property change notification and act on the
+ // document's ready state then.
+ if (browser_url_ == url)
+ return S_FALSE;
+
+ browser_url_ = url;
+ return S_OK;
+}
+
+void FrameEventHandler::LoadCss(const GURL& match_url) {
+ content_script_manager_->LoadCss(match_url, document_);
+}
+
+void FrameEventHandler::LoadStartScripts(const GURL& match_url) {
+ // Run the document start scripts.
+ content_script_manager_->LoadStartScripts(match_url, document_);
+}
+
+void FrameEventHandler::LoadEndScripts(const GURL& match_url) {
+ // Run the document end scripts.
+ content_script_manager_->LoadEndScripts(match_url, document_);
+}
diff --git a/ceee/ie/plugin/bho/frame_event_handler.h b/ceee/ie/plugin/bho/frame_event_handler.h
new file mode 100644
index 0000000..16bba7d
--- /dev/null
+++ b/ceee/ie/plugin/bho/frame_event_handler.h
@@ -0,0 +1,309 @@
+// 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.
+//
+// @file
+// Frame event handler declaration.
+
+#ifndef CEEE_IE_PLUGIN_BHO_FRAME_EVENT_HANDLER_H_
+#define CEEE_IE_PLUGIN_BHO_FRAME_EVENT_HANDLER_H_
+
+#include <atlbase.h>
+#include <atlcom.h>
+#include <mshtml.h> // Must be before <exdisp.h>
+#include <exdisp.h>
+#include <ocidl.h>
+#include <objidl.h>
+
+#include <list>
+#include <set>
+#include <string>
+
+#include "base/basictypes.h"
+#include "base/scoped_ptr.h"
+#include "ceee/ie/plugin/scripting/content_script_manager.h"
+#include "ceee/ie/plugin/scripting/userscripts_librarian.h"
+#include "ceee/common/initializing_coclass.h"
+
+#include "toolband.h" // NOLINT
+
+// Error code to signal that a browser has a non-MSHTML document attached.
+const HRESULT E_DOCUMENT_NOT_MSHTML =
+ MAKE_HRESULT(SEVERITY_ERROR, FACILITY_ITF, 0x200);
+
+// This is the interface the frame event handler presents to its host.
+// The BHO maintains a mapping of browsers to frame event handlers,
+// and takes care of notifying frame event handlers when they acquire
+// a subframe.
+extern const GUID IID_IFrameEventHandler;
+class IFrameEventHandler: public IUnknown {
+ public:
+ // Get the frame's current URL.
+ virtual void GetUrl(BSTR* url) = 0;
+
+ // Notify the frame event handler of the associated browser's current URL.
+ virtual HRESULT SetUrl(BSTR url) = 0;
+
+ // Returns the current document ready state.
+ virtual READYSTATE GetReadyState() = 0;
+
+ // Notify the frame event handler that |handler| has been attached
+ // to an immediate sub-browser of the browser it's attached to.
+ virtual HRESULT AddSubHandler(IFrameEventHandler* handler) = 0;
+ // Notify the frame event handler that |handler| has detached from
+ // an immediate sub-browser of the browser it's attached to.
+ virtual HRESULT RemoveSubHandler(IFrameEventHandler* handler) = 0;
+
+ // A parent frame handler has seen a readystate drop, indicating
+ // that our associated browser instance has gone out of scope.
+ // @pre this frame event handler is attached to a browser.
+ virtual void TearDown() = 0;
+
+ // Insert code inside a tab whether by execution or injection.
+ // @param code The code to insert.
+ // @param file A file containing the code to insert.
+ // @param type The type of the code to insert.
+ virtual HRESULT InsertCode(BSTR code, BSTR file,
+ CeeeTabCodeType type) = 0;
+
+ // Re-does any injections of code or CSS that should have been already done.
+ // Called by the host when extensions have been loaded, as before then we
+ // don't have details on which scripts to load.
+ virtual void RedoDoneInjections() = 0;
+};
+
+// Fwd.
+class IExtensionPortMessagingProvider;
+
+// The interface presented to a frame event handler by its host.
+extern const GUID IID_IFrameEventHandlerHost;
+class IFrameEventHandlerHost: public IUnknown {
+ public:
+ // Notify the host that |handler| has attached to |browser|,
+ // whose parent browser is |parent_browser|.
+ virtual HRESULT AttachBrowser(IWebBrowser2* browser,
+ IWebBrowser2* parent_browser,
+ IFrameEventHandler* handler) = 0;
+ // Notify the host that |handler| has detached from |browser|.
+ // whose parent browser is |parent_browser|.
+ virtual HRESULT DetachBrowser(IWebBrowser2* browser,
+ IWebBrowser2* parent_browser,
+ IFrameEventHandler* handler) = 0;
+ // Returns the top level browser associated to this frame event handler.
+ virtual HRESULT GetTopLevelBrowser(IWebBrowser2** browser) = 0;
+
+ // Notify the host that our ready state has changed.
+ virtual HRESULT OnReadyStateChanged(READYSTATE ready_state) = 0;
+
+ // Get the current ready state of the host.
+ virtual HRESULT GetReadyState(READYSTATE* ready_state) = 0;
+
+ // Retrieve the CSS content from user scripts that match @p url.
+ // @param url The URL to match.
+ // @param require_all_frames Whether to require the all_frames property of the
+ // user script to be true.
+ // @param css_content The single stream of CSS content.
+ virtual HRESULT GetMatchingUserScriptsCssContent(
+ const GURL& url, bool require_all_frames, std::string* css_content) = 0;
+
+ // Retrieve the JS content from user scripts that match @p url.
+ // @param url The URL to match.
+ // @param location The location where the scripts will be run at.
+ // @param require_all_frames Whether to require the all_frames property of the
+ // user script to be true.
+ // @param js_file_list A vector of file path/content pairs.
+ virtual HRESULT GetMatchingUserScriptsJsContent(
+ const GURL& url, UserScript::RunLocation location,
+ bool require_all_frames,
+ UserScriptsLibrarian::JsFileList* js_file_list) = 0;
+
+ // Retrieve our extension ID.
+ // @param extension_id on success returns the extension id.
+ virtual HRESULT GetExtensionId(std::wstring* extension_id) = 0;
+
+ // Retrieve our extension base dir.
+ // @param extension_path on success returns the extension base dir.
+ virtual HRESULT GetExtensionPath(std::wstring* extension_path) = 0;
+
+ // Retrieve the native API host.
+ // @param host on success returns the native API host.
+ virtual HRESULT GetExtensionPortMessagingProvider(
+ IExtensionPortMessagingProvider** messaging_provider) = 0;
+
+ // Execute the given code or file in the top level frame or all frames.
+ // Note that only one of code or file can be non-empty.
+ // @param code The script to execute.
+ // @param file A file containing the script to execute.
+ // @param all_frames If true, applies to the top level frame as well as
+ // contained iframes. Otherwise, applies only to the
+ // top level frame.
+ // @param type The type of the code to insert.
+ virtual HRESULT InsertCode(BSTR code, BSTR file, BOOL all_frames,
+ CeeeTabCodeType type) = 0;
+};
+
+// The frame event handler is attached to an IWebBrowser2 instance, either
+// a top-level instance or sub instances associated with frames.
+// It is responsible for listening for events from the associated frame in
+// order to e.g. instantiate content scripts that interact with the frame.
+class FrameEventHandler
+ : public CComObjectRootEx<CComSingleThreadModel>,
+ public InitializingCoClass<FrameEventHandler>,
+ public IPropertyNotifySink,
+ public IAdviseSink,
+ public IFrameEventHandler {
+ public:
+ BEGIN_COM_MAP(FrameEventHandler)
+ COM_INTERFACE_ENTRY(IPropertyNotifySink)
+ COM_INTERFACE_ENTRY(IAdviseSink)
+ COM_INTERFACE_ENTRY_IID(IID_IFrameEventHandler, IFrameEventHandler)
+ END_COM_MAP()
+ DECLARE_PROTECT_FINAL_CONSTRUCT();
+
+ FrameEventHandler();
+ virtual ~FrameEventHandler();
+
+ // Initialize the event handler.
+ // @returns S_OK on success, E_DOCUMENT_NOT_MSHTML if the browser
+ // is not attached to an MSTHML document instance.
+ HRESULT Initialize(IWebBrowser2* browser,
+ IWebBrowser2* parent_browser,
+ IFrameEventHandlerHost* host);
+ void FinalRelease();
+
+ // @name IPropertyNotifySink implementation
+ // @{
+ STDMETHOD(OnChanged)(DISPID property_disp_id);
+ STDMETHOD(OnRequestEdit)(DISPID property_disp_id);
+ // @}
+
+ // @name IAdviseSink implementation
+ // @{
+ STDMETHOD_(void, OnDataChange)(FORMATETC* format, STGMEDIUM* storage);
+ STDMETHOD_(void, OnViewChange)(DWORD aspect, LONG index);
+ STDMETHOD_(void, OnRename)(IMoniker* moniker);
+ STDMETHOD_(void, OnSave)();
+ // We use this event to tear down
+ STDMETHOD_(void, OnClose)();
+ // @}
+
+ // @name IFrameEventHandler implementation.
+ // @{
+ virtual void GetUrl(BSTR* url);
+ virtual HRESULT SetUrl(BSTR url);
+ virtual READYSTATE GetReadyState() { return document_ready_state_; }
+ virtual HRESULT AddSubHandler(IFrameEventHandler* handler);
+ virtual HRESULT RemoveSubHandler(IFrameEventHandler* handler);
+ virtual void TearDown();
+ virtual HRESULT InsertCode(BSTR code, BSTR file, CeeeTabCodeType type);
+ virtual void RedoDoneInjections();
+ // @}
+
+ BSTR browser_url() const { return browser_url_; }
+
+ protected:
+ // Reinitialize state on a readystate drop to LOADING, which
+ // signifies that either our associated browser is being refreshed
+ // or is being re-navigated.
+ void ReInitialize();
+
+ // Issues a teardown call to all sub frame handlers.
+ void TearDownSubHandlers();
+
+ // Creates and initializes the content script manager for this handler.
+ // This method is virtual to allow overriding by tests.
+ virtual void InitializeContentScriptManager();
+
+ // Reads the contents of an extension resource. The file path is assumed
+ // to be relative to the root of the extension.
+ virtual HRESULT GetExtensionResourceContents(const FilePath& file,
+ std::string* contents);
+
+ // Validates and returns the code content of either code or file.
+ // Used by ExecuteScript and InsertCss.
+ virtual HRESULT GetCodeOrFileContents(BSTR code, BSTR file,
+ std::wstring* contents);
+
+ // Handle a ready state change from document_ready_state_ to new_ready_state.
+ virtual void HandleReadyStateChange(READYSTATE old_ready_state,
+ READYSTATE new_ready_state);
+
+ // Change the current document ready state to new_ready_state
+ // and invoke HandleReadyStateChange if the ready state changed.
+ void SetNewReadyState(READYSTATE new_ready_state);
+
+ // Retrieves our document's ready state.
+ HRESULT GetDocumentReadyState(READYSTATE* ready_state);
+
+ // Inject CSS for @p match_url.
+ virtual void LoadCss(const GURL& match_url);
+
+ // Inject start scripts for @p match_url.
+ virtual void LoadStartScripts(const GURL& match_url);
+
+ // Inject end scripts for @p match_url.
+ virtual void LoadEndScripts(const GURL& match_url);
+
+ // Subscribes for events etc.
+ // @pre document_ is non-NULL and implements IHTMLDocument2.
+ HRESULT AttachToHtmlDocument(IWebBrowser2* browser,
+ IWebBrowser2* parent_browser,
+ IFrameEventHandlerHost* host);
+
+ // Sentinel value for non-subscribed cookies.
+ static const DWORD kInvalidCookie = -1;
+
+ // Connection cookie for IPropertyNotifySink connection point.
+ DWORD property_notify_sink_cookie_;
+
+ // Connection cookie for IAdviseSink subscription.
+ DWORD advise_sink_cookie_;
+
+ // The browser we're attached to.
+ CComPtr<IWebBrowser2> browser_;
+
+ // Our parent browser, if any.
+ CComPtr<IWebBrowser2> parent_browser_;
+
+ // The current URL browser_ is navigated or navigating to.
+ CComBSTR browser_url_;
+
+ // Our host object.
+ CComPtr<IFrameEventHandlerHost> host_;
+
+ // The document object of browser_, but only if it implements
+ // IHTMLDocument2 - e.g. is an HTML document object.
+ CComPtr<IHTMLDocument2> document_;
+
+ // The last recorded document_ ready state.
+ READYSTATE document_ready_state_;
+
+ // True iff we've initialized debugging.
+ bool initialized_debugging_;
+
+ // Each of these is true iff we've attempted content script
+ // CSS/start/end script injection.
+ bool loaded_css_;
+ bool loaded_start_scripts_;
+ bool loaded_end_scripts_;
+
+ // Our content script manager.
+ scoped_ptr<ContentScriptManager> content_script_manager_;
+
+ typedef std::set<CAdapt<CComPtr<IFrameEventHandler> > > SubHandlerSet;
+ // The sub frames handlers we've been advised of by our host.
+ SubHandlerSet sub_handlers_;
+
+ struct DeferredInjection {
+ std::wstring code;
+ std::wstring file;
+ CeeeTabCodeType type;
+ };
+
+ // Injections we deferred until extension information is available.
+ std::list<DeferredInjection> deferred_injections_;
+
+ DISALLOW_COPY_AND_ASSIGN(FrameEventHandler);
+};
+
+#endif // CEEE_IE_PLUGIN_BHO_FRAME_EVENT_HANDLER_H_
diff --git a/ceee/ie/plugin/bho/frame_event_handler_unittest.cc b/ceee/ie/plugin/bho/frame_event_handler_unittest.cc
new file mode 100644
index 0000000..2094591
--- /dev/null
+++ b/ceee/ie/plugin/bho/frame_event_handler_unittest.cc
@@ -0,0 +1,740 @@
+// 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.
+//
+// @file
+// Frame event handler unittests.
+#include "ceee/ie/plugin/bho/frame_event_handler.h"
+
+#include <atlctl.h>
+#include <map>
+
+#include "base/file_util.h"
+#include "ceee/common/com_utils.h"
+#include "ceee/ie/testing/mock_frame_event_handler_host.h"
+#include "ceee/testing/utils/instance_count_mixin.h"
+#include "ceee/testing/utils/mock_com.h"
+#include "ceee/testing/utils/mshtml_mocks.h"
+#include "ceee/testing/utils/test_utils.h"
+#include "gmock/gmock.h"
+#include "gtest/gtest.h"
+
+namespace {
+
+using testing::IOleObjecMockImpl;
+using testing::IWebBrowser2MockImpl;
+using testing::InstanceCountMixinBase;
+using testing::InstanceCountMixin;
+
+using testing::_;
+using testing::CopyInterfaceToArgument;
+using testing::DoAll;
+using testing::Return;
+using testing::StrEq;
+using testing::StrictMock;
+using testing::SetArgumentPointee;
+
+ScriptHost::DebugApplication debug_app(L"FrameEventHandlerUnittest");
+
+// We need to implement this interface separately, because
+// there are name conflicts with methods in IConnectionPointImpl,
+// and we don't want to override those methods.
+class TestIOleObjectImpl: public StrictMock<IOleObjecMockImpl> {
+ public:
+ // Implement the advise functions.
+ STDMETHOD(Advise)(IAdviseSink* sink, DWORD* advise_cookie) {
+ return advise_holder_->Advise(sink, advise_cookie);
+ }
+ STDMETHOD(Unadvise)(DWORD advise_cookie) {
+ return advise_holder_->Unadvise(advise_cookie);
+ }
+ STDMETHOD(EnumAdvise)(IEnumSTATDATA **enum_advise) {
+ return advise_holder_->EnumAdvise(enum_advise);
+ }
+
+ HRESULT Initialize() {
+ return ::CreateOleAdviseHolder(&advise_holder_);
+ }
+
+ public:
+ CComPtr<IOleAdviseHolder> advise_holder_;
+};
+
+class IPersistMockImpl: public IPersist {
+ public:
+ MOCK_METHOD1_WITH_CALLTYPE(__stdcall, GetClassID, HRESULT(CLSID *clsid));
+};
+
+class MockDocument
+ : public CComObjectRootEx<CComSingleThreadModel>,
+ public InitializingCoClass<MockDocument>,
+ public InstanceCountMixin<MockDocument>,
+ public StrictMock<IHTMLDocument2MockImpl>,
+ public StrictMock<IPersistMockImpl>,
+ public TestIOleObjectImpl,
+ public IConnectionPointContainerImpl<MockDocument>,
+ public IConnectionPointImpl<MockDocument, &IID_IPropertyNotifySink> {
+ public:
+ BEGIN_COM_MAP(MockDocument)
+ COM_INTERFACE_ENTRY(IDispatch)
+ COM_INTERFACE_ENTRY(IHTMLDocument)
+ COM_INTERFACE_ENTRY(IHTMLDocument2)
+ COM_INTERFACE_ENTRY(IOleObject)
+ COM_INTERFACE_ENTRY(IPersist)
+ COM_INTERFACE_ENTRY(IConnectionPointContainer)
+ END_COM_MAP()
+
+ BEGIN_CONNECTION_POINT_MAP(MockDocument)
+ CONNECTION_POINT_ENTRY(IID_IPropertyNotifySink)
+ END_CONNECTION_POINT_MAP()
+
+ MockDocument() : ready_state_(READYSTATE_UNINITIALIZED) {
+ }
+
+ void FireOnClose() {
+ EXPECT_HRESULT_SUCCEEDED(advise_holder_->SendOnClose());
+ }
+
+ // Override to handle DISPID_READYSTATE.
+ STDMETHOD(Invoke)(DISPID member, REFIID iid, LCID locale, WORD flags,
+ DISPPARAMS* params, VARIANT *result, EXCEPINFO* ex_info,
+ unsigned int* arg_error) {
+ if (member == DISPID_READYSTATE && flags == DISPATCH_PROPERTYGET) {
+ result->vt = VT_I4;
+ result->lVal = ready_state_;
+ return S_OK;
+ }
+
+ return StrictMock<IHTMLDocument2MockImpl>::Invoke(member, iid, locale,
+ flags, params, result, ex_info, arg_error);
+ }
+
+ STDMETHOD(get_URL)(BSTR* url) {
+ return url_.CopyTo(url);
+ }
+
+ HRESULT Initialize(MockDocument** self) {
+ *self = this;
+ return TestIOleObjectImpl::Initialize();
+ }
+
+
+ READYSTATE ready_state() const { return ready_state_; }
+ void set_ready_state(READYSTATE ready_state) { ready_state_ = ready_state; }
+
+ void FireReadyStateChange() {
+ CFirePropNotifyEvent::FireOnChanged(GetUnknown(), DISPID_READYSTATE);
+ }
+
+ // Sets our ready state and fires the change event.
+ void SetReadyState(READYSTATE new_ready_state) {
+ if (ready_state_ == new_ready_state)
+ return;
+ ready_state_ = new_ready_state;
+ FireReadyStateChange();
+ }
+
+ const wchar_t *url() const { return com::ToString(url_); }
+ void set_url(const wchar_t* url) { url_ = url; }
+
+ protected:
+ CComBSTR url_;
+ READYSTATE ready_state_;
+};
+
+class MockBrowser
+ : public CComObjectRootEx<CComSingleThreadModel>,
+ public InitializingCoClass<MockBrowser>,
+ public InstanceCountMixin<MockBrowser>,
+ public StrictMock<IWebBrowser2MockImpl> {
+ public:
+ BEGIN_COM_MAP(MockBrowser)
+ COM_INTERFACE_ENTRY(IWebBrowser2)
+ COM_INTERFACE_ENTRY(IWebBrowserApp)
+ COM_INTERFACE_ENTRY(IWebBrowser)
+ END_COM_MAP()
+
+ HRESULT Initialize(MockBrowser** self) {
+ *self = this;
+ return S_OK;
+ }
+
+ STDMETHOD(get_Parent)(IDispatch** parent) {
+ this->GetUnknown()->AddRef();
+ *parent = this;
+ return S_OK;
+ }
+};
+
+class IFrameEventHandlerHostMockImpl : public IFrameEventHandlerHost {
+ public:
+ MOCK_METHOD1(GetReadyState, HRESULT(READYSTATE* readystate));
+ MOCK_METHOD3(GetMatchingUserScriptsCssContent,
+ HRESULT(const GURL& url, bool require_all_frames,
+ std::string* css_content));
+ MOCK_METHOD4(GetMatchingUserScriptsJsContent,
+ HRESULT(const GURL& url,
+ UserScript::RunLocation location,
+ bool require_all_frames,
+ UserScriptsLibrarian::JsFileList* js_file_list));
+ MOCK_METHOD1(GetExtensionId, HRESULT(std::wstring* extension_id));
+ MOCK_METHOD1(GetExtensionPath, HRESULT(std::wstring* extension_path));
+ MOCK_METHOD1(GetExtensionPortMessagingProvider,
+ HRESULT(IExtensionPortMessagingProvider** messaging_provider));
+ MOCK_METHOD4(InsertCode, HRESULT(BSTR, BSTR, BOOL, CeeeTabCodeType));
+};
+
+class TestFrameEventHandlerHost
+ : public testing::MockFrameEventHandlerHostBase<TestFrameEventHandlerHost> {
+ public:
+ HRESULT Initialize(TestFrameEventHandlerHost** self) {
+ *self = this;
+ return S_OK;
+ }
+ virtual HRESULT AttachBrowser(IWebBrowser2* browser,
+ IWebBrowser2* parent_browser,
+ IFrameEventHandler* handler) {
+ // Get the identity unknown.
+ CComPtr<IUnknown> browser_identity_unknown;
+ EXPECT_HRESULT_SUCCEEDED(
+ browser->QueryInterface(&browser_identity_unknown));
+
+ std::pair<HandlerMap::iterator, bool> result =
+ handlers_.insert(std::make_pair(browser_identity_unknown, handler));
+ EXPECT_TRUE(result.second);
+ return S_OK;
+ }
+
+ virtual HRESULT DetachBrowser(IWebBrowser2* browser,
+ IWebBrowser2* parent_browser,
+ IFrameEventHandler* handler) {
+ // Get the identity unknown.
+ CComPtr<IUnknown> browser_identity_unknown;
+ EXPECT_HRESULT_SUCCEEDED(
+
+ browser->QueryInterface(&browser_identity_unknown));
+ EXPECT_EQ(1, handlers_.erase(browser_identity_unknown));
+ return S_OK;
+ }
+
+ virtual HRESULT OnReadyStateChanged(READYSTATE ready_state) {
+ return S_OK;
+ }
+
+ bool has_browser(IUnknown* browser) {
+ CComPtr<IUnknown> browser_identity(browser);
+
+ return handlers_.find(browser_identity) != handlers_.end();
+ }
+
+ FrameEventHandler* GetHandler(IUnknown* browser) {
+ CComPtr<IUnknown> browser_identity(browser);
+
+ HandlerMap::iterator it(handlers_.find(browser_identity));
+ if (it != handlers_.end())
+ return NULL;
+
+ return static_cast<FrameEventHandler*>(it->second);
+ }
+
+ private:
+ typedef std::map<IUnknown*, IFrameEventHandler*> HandlerMap;
+ HandlerMap handlers_;
+};
+
+class MockContentScriptManager : public ContentScriptManager {
+ public:
+ MOCK_METHOD3(ExecuteScript, HRESULT(const wchar_t* code,
+ const wchar_t* file_path,
+ IHTMLDocument2* document));
+ MOCK_METHOD2(InsertCss, HRESULT(const wchar_t* code,
+ IHTMLDocument2* document));
+};
+
+// This testing class is used to test the higher-level event handling
+// behavior of FrameEventHandler by mocking out the implementation
+// functions invoked on readystate transitions.
+class TestingFrameEventHandler
+ : public FrameEventHandler,
+ public InitializingCoClass<TestingFrameEventHandler>,
+ public InstanceCountMixin<TestingFrameEventHandler> {
+ public:
+ TestingFrameEventHandler() {}
+ ~TestingFrameEventHandler() {}
+
+ HRESULT Initialize(TestingFrameEventHandler **self,
+ IWebBrowser2* browser,
+ IWebBrowser2* parent_browser,
+ IFrameEventHandlerHost* host) {
+ *self = this;
+ return FrameEventHandler::Initialize(browser, parent_browser, host);
+ }
+
+ virtual void InitializeContentScriptManager() {
+ content_script_manager_.reset(new MockContentScriptManager);
+ }
+ MockContentScriptManager* GetContentScriptManager() {
+ return reinterpret_cast<MockContentScriptManager*>(
+ content_script_manager_.get());
+ }
+
+ // Mock out or publicize our internal helper methods.
+ MOCK_METHOD2(GetExtensionResourceContents,
+ HRESULT(const FilePath& file, std::string* contents));
+ MOCK_METHOD3(GetCodeOrFileContents,
+ HRESULT(BSTR code, BSTR file, std::wstring* contents));
+ HRESULT CallGetCodeOrFileContents(BSTR code, BSTR file,
+ std::wstring* contents) {
+ return FrameEventHandler::GetCodeOrFileContents(code, file, contents);
+ }
+
+ const std::list<DeferredInjection>& deferred_injections() {
+ return deferred_injections_;
+ }
+
+ void SetupForRedoDoneInjectionsTest(BSTR url) {
+ browser_url_ = url;
+ loaded_css_ = true;
+ loaded_start_scripts_ = true;
+ loaded_end_scripts_ = true;
+ }
+
+ // Disambiguate.
+ using InitializingCoClass<TestingFrameEventHandler>::
+ CreateInitialized;
+
+ // Mock out the state transition implementation functions.
+ MOCK_METHOD1(LoadCss, void(const GURL& match_url));
+ MOCK_METHOD1(LoadStartScripts, void(const GURL& match_url));
+ MOCK_METHOD1(LoadEndScripts, void(const GURL& match_url));
+};
+
+class FrameEventHandlerTestBase: public testing::Test {
+ public:
+ virtual void SetUp() {
+ ASSERT_HRESULT_SUCCEEDED(
+ MockBrowser::CreateInitialized(&browser_, &browser_keeper_));
+ ASSERT_HRESULT_SUCCEEDED(
+ MockDocument::CreateInitialized(&document_, &document_keeper_));
+ ASSERT_HRESULT_SUCCEEDED(
+ TestFrameEventHandlerHost::CreateInitializedIID(
+ &host_, IID_IUnknown, &host_keeper_));
+
+ ExpectGetDocument();
+ }
+
+ virtual void TearDown() {
+ // Fire a close event just in case.
+ if (document_)
+ document_->FireOnClose();
+
+ browser_ = NULL;
+ browser_keeper_.Release();
+ document_ = NULL;
+ document_keeper_.Release();
+ host_ = NULL;
+ host_keeper_.Release();
+
+ handler_keeper_.Release();
+
+ ASSERT_EQ(0, InstanceCountMixinBase::all_instance_count());
+ }
+
+ void ExpectGetDocument() {
+ EXPECT_CALL(*browser_, get_Document(_))
+ .WillRepeatedly(DoAll(
+ CopyInterfaceToArgument<0>(static_cast<IDispatch*>(document_)),
+ Return(S_OK)));
+ }
+
+ protected:
+ MockBrowser* browser_;
+ CComPtr<IWebBrowser2> browser_keeper_;
+
+ MockDocument* document_;
+ CComPtr<IHTMLDocument2> document_keeper_;
+
+ TestFrameEventHandlerHost* host_;
+ CComPtr<IFrameEventHandlerHost> host_keeper_;
+
+ CComPtr<IUnknown> handler_keeper_;
+};
+
+class FrameEventHandlerTest: public FrameEventHandlerTestBase {
+ public:
+ typedef FrameEventHandlerTestBase Base;
+
+ static void SetUpTestCase() {
+ // Never torn down as other threads in the test may need it after
+ // teardown.
+ ScriptHost::set_default_debug_application(&debug_app);
+ }
+
+ void TearDown() {
+ handler_ = NULL;
+
+ Base::TearDown();
+ }
+
+ void CreateHandler() {
+ EXPECT_CALL(*document_, GetClassID(_)).WillOnce(
+ DoAll(SetArgumentPointee<0>(CLSID_HTMLDocument), Return(S_OK)));
+ IWebBrowser2* parent_browser = NULL;
+ ASSERT_HRESULT_SUCCEEDED(
+ TestingFrameEventHandler::CreateInitialized(
+ &handler_, browser_, parent_browser, host_, &handler_keeper_));
+ }
+
+ protected:
+ TestingFrameEventHandler* handler_;
+};
+
+TEST_F(FrameEventHandlerTest, WillNotAttachToNonHTMLDocument) {
+ EXPECT_CALL(*document_, GetClassID(_)).WillOnce(
+ DoAll(SetArgumentPointee<0>(GUID_NULL), Return(S_OK)));
+
+ // If the document is not MSHTML, we should not attach, and
+ // we should return E_DOCUMENT_NOT_MSHTML to our caller to signal this.
+ IWebBrowser2* parent_browser = NULL;
+ HRESULT hr = TestingFrameEventHandler::CreateInitialized(
+ &handler_, browser_, parent_browser, host_, &handler_keeper_);
+
+ EXPECT_EQ(E_DOCUMENT_NOT_MSHTML, hr);
+ EXPECT_FALSE(host_->has_browser(browser_));
+}
+
+TEST_F(FrameEventHandlerTest, CreateAndDetachDoesNotCrash) {
+ ASSERT_EQ(0, TestingFrameEventHandler::instance_count());
+
+ CreateHandler();
+ ASSERT_EQ(1, TestingFrameEventHandler::instance_count());
+
+ // Assert that it registered.
+ ASSERT_TRUE(host_->has_browser(browser_));
+
+ // Release the handler early to ensure its last reference will
+ // be released while handling FireOnClose.
+ handler_keeper_.Release();
+ handler_ = NULL;
+ EXPECT_EQ(1, TestingFrameEventHandler::instance_count());
+
+ // Should tear down and destroy itself on this event.
+ document_->FireOnClose();
+ ASSERT_EQ(0, TestingFrameEventHandler::instance_count());
+}
+
+const wchar_t kGoogleUrl[] =
+ L"http://www.google.com/search?q=Google+Buys+Iceland";
+const wchar_t kSlashdotUrl[] =
+ L"http://hardware.slashdot.org/";
+
+TEST_F(FrameEventHandlerTest, InjectsCSSAndStartScriptsOnLoadedReadystate) {
+ CreateHandler();
+
+ document_->set_url(kGoogleUrl);
+ document_->set_ready_state(READYSTATE_LOADING);
+
+ // Transitioning to loading should not cause any loads.
+ EXPECT_CALL(*handler_, LoadCss(_)).Times(0);
+ EXPECT_CALL(*handler_, LoadStartScripts(_)).Times(0);
+ EXPECT_CALL(*handler_, LoadEndScripts(_)).Times(0);
+
+ // Notify the handler of the URL.
+ handler_->SetUrl(CComBSTR(kGoogleUrl));
+ document_->FireReadyStateChange();
+
+ const GURL google_url(kGoogleUrl);
+ // Transitioning to LOADED should load Css and start scripts.
+ EXPECT_CALL(*handler_, LoadCss(google_url)).Times(1);
+ EXPECT_CALL(*handler_, LoadStartScripts(google_url)).Times(1);
+
+ // But not end scripts.
+ EXPECT_CALL(*handler_, LoadEndScripts(_)).Times(0);
+ document_->SetReadyState(READYSTATE_LOADED);
+
+ // Now make like a re-navigation.
+ document_->SetReadyState(READYSTATE_LOADING);
+
+ // Transitioning back to LOADED should load Css and start scripts again.
+ EXPECT_CALL(*handler_, LoadCss(google_url)).Times(1);
+ EXPECT_CALL(*handler_, LoadStartScripts(google_url)).Times(1);
+
+ // But not end scripts.
+ EXPECT_CALL(*handler_, LoadEndScripts(_)).Times(0);
+ document_->SetReadyState(READYSTATE_LOADED);
+
+ // Now navigate to a different URL.
+ document_->set_url(kSlashdotUrl);
+ document_->set_ready_state(READYSTATE_LOADING);
+
+ // Transitioning to loading should not cause any loads.
+ EXPECT_CALL(*handler_, LoadCss(_)).Times(0);
+ EXPECT_CALL(*handler_, LoadStartScripts(_)).Times(0);
+ EXPECT_CALL(*handler_, LoadEndScripts(_)).Times(0);
+
+ handler_->SetUrl(CComBSTR(kSlashdotUrl));
+ document_->FireReadyStateChange();
+
+ const GURL slashdot_url(kSlashdotUrl);
+
+ // Transitioning back to LOADED on the new URL should load
+ // Css and start scripts again.
+ EXPECT_CALL(*handler_, LoadCss(slashdot_url)).Times(1);
+ EXPECT_CALL(*handler_, LoadStartScripts(slashdot_url)).Times(1);
+
+ // But not end scripts.
+ EXPECT_CALL(*handler_, LoadEndScripts(_)).Times(0);
+ document_->SetReadyState(READYSTATE_LOADED);
+}
+
+TEST_F(FrameEventHandlerTest, InjectsEndScriptsOnCompleteReadystate) {
+ CreateHandler();
+
+ document_->set_url(kGoogleUrl);
+ document_->set_ready_state(READYSTATE_LOADING);
+
+ EXPECT_CALL(*handler_, LoadCss(_)).Times(0);
+ EXPECT_CALL(*handler_, LoadStartScripts(_)).Times(0);
+
+ // Transitioning to loading should not cause any loads.
+ EXPECT_CALL(*handler_, LoadEndScripts(_)).Times(0);
+
+ // Notify the handler of the URL.
+ handler_->SetUrl(CComBSTR(kGoogleUrl));
+ document_->FireReadyStateChange();
+
+ const GURL google_url(kGoogleUrl);
+ // Transitioning to LOADED should load Css and start scripts.
+ EXPECT_CALL(*handler_, LoadCss(google_url)).Times(1);
+ EXPECT_CALL(*handler_, LoadStartScripts(google_url)).Times(1);
+
+ // But not end scripts.
+ EXPECT_CALL(*handler_, LoadEndScripts(_)).Times(0);
+ document_->SetReadyState(READYSTATE_LOADED);
+
+ // Transitioning to INTERACTIVE should be a no-op.
+ EXPECT_CALL(*handler_, LoadCss(_)).Times(0);
+ EXPECT_CALL(*handler_, LoadStartScripts(_)).Times(0);
+ EXPECT_CALL(*handler_, LoadEndScripts(_)).Times(0);
+
+ document_->SetReadyState(READYSTATE_INTERACTIVE);
+
+ // Transitioning to COMPLETE should load end scripts.
+ EXPECT_CALL(*handler_, LoadCss(_)).Times(0);
+ EXPECT_CALL(*handler_, LoadStartScripts(_)).Times(0);
+ EXPECT_CALL(*handler_, LoadEndScripts(google_url)).Times(1);
+
+ document_->SetReadyState(READYSTATE_COMPLETE);
+
+ // Now make like a re-navigation.
+ document_->SetReadyState(READYSTATE_LOADING);
+
+ // Transitioning back to LOADED should load Css and start scripts again.
+ EXPECT_CALL(*handler_, LoadCss(google_url)).Times(1);
+ EXPECT_CALL(*handler_, LoadStartScripts(google_url)).Times(1);
+
+ // But not end scripts.
+ EXPECT_CALL(*handler_, LoadEndScripts(_)).Times(0);
+ document_->SetReadyState(READYSTATE_LOADED);
+
+ // Transitioning back to INTERACTIVE should be a no-op.
+ EXPECT_CALL(*handler_, LoadCss(_)).Times(0);
+ EXPECT_CALL(*handler_, LoadStartScripts(_)).Times(0);
+ EXPECT_CALL(*handler_, LoadEndScripts(_)).Times(0);
+
+ document_->SetReadyState(READYSTATE_INTERACTIVE);
+
+ // Transitioning back to COMPLETE should load end scripts.
+ EXPECT_CALL(*handler_, LoadCss(_)).Times(0);
+ EXPECT_CALL(*handler_, LoadStartScripts(_)).Times(0);
+ EXPECT_CALL(*handler_, LoadEndScripts(google_url)).Times(1);
+
+ document_->SetReadyState(READYSTATE_COMPLETE);
+
+ // Now navigate to a different URL.
+ document_->set_url(kSlashdotUrl);
+ document_->set_ready_state(READYSTATE_LOADING);
+
+ // Transitioning to loading should not cause any loads.
+ EXPECT_CALL(*handler_, LoadCss(_)).Times(0);
+ EXPECT_CALL(*handler_, LoadStartScripts(_)).Times(0);
+ EXPECT_CALL(*handler_, LoadEndScripts(_)).Times(0);
+
+ handler_->SetUrl(CComBSTR(kSlashdotUrl));
+ document_->FireReadyStateChange();
+
+ const GURL slashdot_url(kSlashdotUrl);
+
+ // Transitioning back to LOADED on the new URL should load
+ // Css and start scripts again.
+ EXPECT_CALL(*handler_, LoadCss(slashdot_url)).Times(1);
+ EXPECT_CALL(*handler_, LoadStartScripts(slashdot_url)).Times(1);
+
+ // But not end scripts.
+ EXPECT_CALL(*handler_, LoadEndScripts(_)).Times(0);
+ document_->SetReadyState(READYSTATE_LOADED);
+
+ // Back to INTERACTIVE is still a noop.
+ EXPECT_CALL(*handler_, LoadCss(_)).Times(0);
+ EXPECT_CALL(*handler_, LoadStartScripts(_)).Times(0);
+ EXPECT_CALL(*handler_, LoadEndScripts(_)).Times(0);
+
+ document_->SetReadyState(READYSTATE_INTERACTIVE);
+
+ // And COMPLETE loads end scripts again.
+ EXPECT_CALL(*handler_, LoadCss(_)).Times(0);
+ EXPECT_CALL(*handler_, LoadStartScripts(_)).Times(0);
+ EXPECT_CALL(*handler_, LoadEndScripts(slashdot_url)).Times(1);
+
+ document_->SetReadyState(READYSTATE_COMPLETE);
+}
+
+TEST_F(FrameEventHandlerTest, InsertCodeCss) {
+ CreateHandler();
+ MockContentScriptManager* content_script_manager =
+ handler_->GetContentScriptManager();
+
+ // Css code type
+ EXPECT_CALL(*handler_, GetCodeOrFileContents(_, _, _)).WillOnce(Return(S_OK));
+ // Must respond with non-empty extension path or call will get deferred.
+ EXPECT_CALL(*host_, GetExtensionId(_)).WillOnce(
+ DoAll(SetArgumentPointee<0>(std::wstring(L"hello")), Return(S_OK)));
+ EXPECT_CALL(*content_script_manager, InsertCss(_, _)).WillOnce(Return(S_OK));
+
+ ASSERT_HRESULT_SUCCEEDED(
+ handler_->InsertCode(NULL, NULL, kCeeeTabCodeTypeCss));
+
+ // Js code type with no file
+ EXPECT_CALL(*handler_, GetCodeOrFileContents(_, _, _)).WillOnce(Return(S_OK));
+
+ wchar_t* default_file = L"ExecuteScript.code";
+ EXPECT_CALL(*content_script_manager, ExecuteScript(_, StrEq(default_file), _))
+ .WillOnce(Return(S_OK));
+ EXPECT_CALL(*host_, GetExtensionId(_)).WillOnce(
+ DoAll(SetArgumentPointee<0>(std::wstring(L"hello")), Return(S_OK)));
+
+ ASSERT_HRESULT_SUCCEEDED(
+ handler_->InsertCode(NULL, NULL, kCeeeTabCodeTypeJs));
+
+ // Js code type with a file
+ EXPECT_CALL(*handler_, GetCodeOrFileContents(_, _, _)).WillOnce(Return(S_OK));
+
+ wchar_t* test_file = L"test_file.js";
+ EXPECT_CALL(*content_script_manager, ExecuteScript(_, StrEq(test_file), _))
+ .WillOnce(Return(S_OK));
+ EXPECT_CALL(*host_, GetExtensionId(_)).WillOnce(
+ DoAll(SetArgumentPointee<0>(std::wstring(L"hello")), Return(S_OK)));
+
+ CComBSTR test_file_bstr(test_file);
+ ASSERT_HRESULT_SUCCEEDED(
+ handler_->InsertCode(NULL, test_file_bstr, kCeeeTabCodeTypeJs));
+}
+
+TEST_F(FrameEventHandlerTest, DeferInsertCodeCss) {
+ CreateHandler();
+
+ // Does not set extension path, so it stays empty.
+ EXPECT_CALL(*host_, GetExtensionId(_)).WillRepeatedly(Return(S_OK));
+
+ ASSERT_HRESULT_SUCCEEDED(
+ handler_->InsertCode(L"boo", NULL, kCeeeTabCodeTypeCss));
+ ASSERT_HRESULT_SUCCEEDED(
+ handler_->InsertCode(NULL, L"moo", kCeeeTabCodeTypeJs));
+ ASSERT_EQ(2, handler_->deferred_injections().size());
+
+ ASSERT_EQ(L"boo", handler_->deferred_injections().begin()->code);
+ ASSERT_EQ(L"", handler_->deferred_injections().begin()->file);
+ ASSERT_EQ(kCeeeTabCodeTypeCss,
+ handler_->deferred_injections().begin()->type);
+
+ // The ++ syntax is ugly but it's either this or make DeferredInjection
+ // a public struct.
+ ASSERT_EQ(L"", (++handler_->deferred_injections().begin())->code);
+ ASSERT_EQ(L"moo", (++handler_->deferred_injections().begin())->file);
+ ASSERT_EQ(kCeeeTabCodeTypeJs,
+ (++handler_->deferred_injections().begin())->type);
+}
+
+TEST_F(FrameEventHandlerTest, RedoDoneInjections) {
+ CreateHandler();
+ MockContentScriptManager* content_script_manager =
+ handler_->GetContentScriptManager();
+
+ // Expects no calls since nothing to redo.
+ handler_->RedoDoneInjections();
+
+ CComBSTR url(L"http://www.google.com/");
+ handler_->SetupForRedoDoneInjectionsTest(url);
+ GURL match_url(com::ToString(url));
+
+ // Does not set extension path, so it stays empty.
+ EXPECT_CALL(*host_, GetExtensionId(_)).WillOnce(Return(S_OK));
+ // Will get deferred.
+ ASSERT_HRESULT_SUCCEEDED(handler_->InsertCode(L"boo", NULL,
+ kCeeeTabCodeTypeCss));
+
+ EXPECT_CALL(*handler_, LoadCss(match_url)).Times(1);
+ EXPECT_CALL(*handler_, LoadStartScripts(match_url)).Times(1);
+ EXPECT_CALL(*handler_, LoadEndScripts(match_url)).Times(1);
+
+ // Expect to get this once, as we deferred it before.
+ EXPECT_CALL(*handler_, GetCodeOrFileContents(_, _, _)).WillOnce(Return(S_OK));
+ EXPECT_CALL(*host_, GetExtensionId(_)).WillOnce(
+ DoAll(SetArgumentPointee<0>(std::wstring(L"hello")), Return(S_OK)));
+ EXPECT_CALL(*content_script_manager, InsertCss(_, _)).WillOnce(Return(S_OK));
+ ASSERT_HRESULT_SUCCEEDED(
+ handler_->InsertCode(L"boo", NULL, kCeeeTabCodeTypeCss));
+
+ EXPECT_CALL(*handler_, GetCodeOrFileContents(_, _, _)).WillOnce(Return(S_OK));
+ EXPECT_CALL(*host_, GetExtensionId(_)).WillOnce(
+ DoAll(SetArgumentPointee<0>(std::wstring(L"hello")), Return(S_OK)));
+ EXPECT_CALL(*content_script_manager, InsertCss(_, _)).WillOnce(Return(S_OK));
+ handler_->RedoDoneInjections();
+}
+
+TEST_F(FrameEventHandlerTest, GetCodeOrFileContents) {
+ CreateHandler();
+
+ CComBSTR code(L"test");
+ CComBSTR file(L"test.js");
+ CComBSTR empty;
+ std::wstring contents;
+
+ // Failure cases.
+ EXPECT_CALL(*handler_, GetExtensionResourceContents(_, _)).Times(0);
+
+ ASSERT_HRESULT_FAILED(handler_->CallGetCodeOrFileContents(NULL, NULL,
+ &contents));
+ ASSERT_HRESULT_FAILED(handler_->CallGetCodeOrFileContents(code, file,
+ &contents));
+ ASSERT_HRESULT_FAILED(handler_->CallGetCodeOrFileContents(empty, NULL,
+ &contents));
+ ASSERT_HRESULT_FAILED(handler_->CallGetCodeOrFileContents(NULL, empty,
+ &contents));
+ ASSERT_HRESULT_FAILED(handler_->CallGetCodeOrFileContents(empty, empty,
+ &contents));
+
+ EXPECT_CALL(*handler_, GetExtensionResourceContents(_, _))
+ .WillOnce(Return(E_FAIL));
+
+ ASSERT_HRESULT_FAILED(handler_->CallGetCodeOrFileContents(NULL, file,
+ &contents));
+
+ // Success cases.
+ EXPECT_CALL(*handler_, GetExtensionResourceContents(_, _)).Times(0);
+
+ ASSERT_HRESULT_SUCCEEDED(handler_->CallGetCodeOrFileContents(code, NULL,
+ &contents));
+ ASSERT_HRESULT_SUCCEEDED(handler_->CallGetCodeOrFileContents(code, empty,
+ &contents));
+
+ EXPECT_CALL(*handler_, GetExtensionResourceContents(_, _)).Times(2)
+ .WillRepeatedly(Return(S_OK));
+
+ ASSERT_HRESULT_SUCCEEDED(handler_->CallGetCodeOrFileContents(NULL, file,
+ &contents));
+ ASSERT_HRESULT_SUCCEEDED(handler_->CallGetCodeOrFileContents(empty, file,
+ &contents));
+}
+
+} // namespace
diff --git a/ceee/ie/plugin/bho/http_negotiate.cc b/ceee/ie/plugin/bho/http_negotiate.cc
new file mode 100644
index 0000000..ac39ba3
--- /dev/null
+++ b/ceee/ie/plugin/bho/http_negotiate.cc
@@ -0,0 +1,197 @@
+// 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/bho/http_negotiate.h"
+
+#include <atlbase.h>
+#include <atlctl.h>
+#include <htiframe.h>
+#include <urlmon.h>
+
+#include "base/logging.h"
+#include "base/scoped_ptr.h"
+#include "base/string_util.h"
+#include "base/time.h"
+
+#include "ceee/ie/plugin/bho/cookie_accountant.h"
+#include "chrome_frame/vtable_patch_manager.h"
+#include "chrome_frame/utils.h"
+#include "base/scoped_comptr_win.h"
+
+static const int kHttpNegotiateBeginningTransactionIndex = 3;
+static const int kHttpNegotiateOnResponseIndex = 4;
+
+CComAutoCriticalSection HttpNegotiatePatch::bho_instance_count_crit_;
+int HttpNegotiatePatch::bho_instance_count_ = 0;
+
+
+BEGIN_VTABLE_PATCHES(IHttpNegotiate)
+ VTABLE_PATCH_ENTRY(kHttpNegotiateBeginningTransactionIndex,
+ HttpNegotiatePatch::BeginningTransaction)
+ VTABLE_PATCH_ENTRY(kHttpNegotiateOnResponseIndex,
+ HttpNegotiatePatch::OnResponse)
+END_VTABLE_PATCHES()
+
+namespace {
+
+class SimpleBindStatusCallback : public CComObjectRootEx<CComSingleThreadModel>,
+ public IBindStatusCallback {
+ public:
+ BEGIN_COM_MAP(SimpleBindStatusCallback)
+ COM_INTERFACE_ENTRY(IBindStatusCallback)
+ END_COM_MAP()
+
+ // IBindStatusCallback implementation
+ STDMETHOD(OnStartBinding)(DWORD reserved, IBinding* binding) {
+ return E_NOTIMPL;
+ }
+
+ STDMETHOD(GetPriority)(LONG* priority) {
+ return E_NOTIMPL;
+ }
+ STDMETHOD(OnLowResource)(DWORD reserved) {
+ return E_NOTIMPL;
+ }
+
+ STDMETHOD(OnProgress)(ULONG progress, ULONG max_progress,
+ ULONG status_code, LPCWSTR status_text) {
+ return E_NOTIMPL;
+ }
+ STDMETHOD(OnStopBinding)(HRESULT result, LPCWSTR error) {
+ return E_NOTIMPL;
+ }
+
+ STDMETHOD(GetBindInfo)(DWORD* bind_flags, BINDINFO* bind_info) {
+ return E_NOTIMPL;
+ }
+
+ STDMETHOD(OnDataAvailable)(DWORD flags, DWORD size, FORMATETC* formatetc,
+ STGMEDIUM* storage) {
+ return E_NOTIMPL;
+ }
+ STDMETHOD(OnObjectAvailable)(REFIID iid, IUnknown* object) {
+ return E_NOTIMPL;
+ }
+};
+
+} // end namespace
+
+HttpNegotiatePatch::HttpNegotiatePatch() {
+}
+
+HttpNegotiatePatch::~HttpNegotiatePatch() {
+}
+
+// static
+bool HttpNegotiatePatch::Initialize() {
+ // Patch IHttpNegotiate for user-agent and cookie functionality.
+ {
+ CComCritSecLock<CComAutoCriticalSection> lock(bho_instance_count_crit_);
+ bho_instance_count_++;
+ if (bho_instance_count_ != 1) {
+ return true;
+ }
+ }
+
+ if (IS_PATCHED(IHttpNegotiate)) {
+ LOG(WARNING) << __FUNCTION__ << ": already patched.";
+ return true;
+ }
+
+ // Use our SimpleBindStatusCallback class as we need a temporary object that
+ // implements IBindStatusCallback.
+ CComObjectStackEx<SimpleBindStatusCallback> request;
+ ScopedComPtr<IBindCtx> bind_ctx;
+ HRESULT hr = ::CreateAsyncBindCtx(0, &request, NULL, bind_ctx.Receive());
+
+ DCHECK(SUCCEEDED(hr)) << "CreateAsyncBindCtx";
+ if (bind_ctx) {
+ ScopedComPtr<IUnknown> bscb_holder;
+ bind_ctx->GetObjectParam(L"_BSCB_Holder_", bscb_holder.Receive());
+ if (bscb_holder) {
+ hr = PatchHttpNegotiate(bscb_holder);
+ } else {
+ NOTREACHED() << "Failed to get _BSCB_Holder_";
+ hr = E_UNEXPECTED;
+ }
+ bind_ctx.Release();
+ }
+
+ return SUCCEEDED(hr);
+}
+
+// static
+void HttpNegotiatePatch::Uninitialize() {
+ CComCritSecLock<CComAutoCriticalSection> lock(bho_instance_count_crit_);
+ bho_instance_count_--;
+ DCHECK_GE(bho_instance_count_, 0);
+ if (bho_instance_count_ == 0) {
+ vtable_patch::UnpatchInterfaceMethods(IHttpNegotiate_PatchInfo);
+ }
+}
+
+// static
+HRESULT HttpNegotiatePatch::PatchHttpNegotiate(IUnknown* to_patch) {
+ DCHECK(to_patch);
+ DCHECK_IS_NOT_PATCHED(IHttpNegotiate);
+
+ ScopedComPtr<IHttpNegotiate> http;
+ HRESULT hr = http.QueryFrom(to_patch);
+ if (FAILED(hr)) {
+ hr = DoQueryService(IID_IHttpNegotiate, to_patch, http.Receive());
+ }
+
+ if (http) {
+ hr = vtable_patch::PatchInterfaceMethods(http, IHttpNegotiate_PatchInfo);
+ DLOG_IF(ERROR, FAILED(hr))
+ << StringPrintf("HttpNegotiate patch failed 0x%08X", hr);
+ } else {
+ DLOG(WARNING)
+ << StringPrintf("IHttpNegotiate not supported 0x%08X", hr);
+ }
+
+ return hr;
+}
+
+
+// static
+HRESULT HttpNegotiatePatch::BeginningTransaction(
+ IHttpNegotiate_BeginningTransaction_Fn original, IHttpNegotiate* me,
+ LPCWSTR url, LPCWSTR headers, DWORD reserved, LPWSTR* additional_headers) {
+ DLOG(INFO) << __FUNCTION__ << " " << url << " headers:\n" << headers;
+
+ HRESULT hr = original(me, url, headers, reserved, additional_headers);
+
+ if (FAILED(hr)) {
+ DLOG(WARNING) << __FUNCTION__ << " Delegate returned an error";
+ return hr;
+ }
+
+ // TODO(skare@google.com): Modify User-Agent here.
+
+ return hr;
+}
+
+// static
+HRESULT HttpNegotiatePatch::OnResponse(
+ IHttpNegotiate_OnResponse_Fn original, IHttpNegotiate* me,
+ DWORD response_code, LPCWSTR response_headers, LPCWSTR request_headers,
+ LPWSTR* additional_request_headers) {
+ DLOG(INFO) << __FUNCTION__ << " response headers:\n" << response_headers;
+
+ base::Time current_time = base::Time::Now();
+
+ HRESULT hr = original(me, response_code, response_headers, request_headers,
+ additional_request_headers);
+
+ if (FAILED(hr)) {
+ DLOG(WARNING) << __FUNCTION__ << " Delegate returned an error";
+ return hr;
+ }
+
+ CookieAccountant::GetInstance()->RecordHttpResponseCookies(
+ std::string(CW2A(response_headers)), current_time);
+
+ return hr;
+}
diff --git a/ceee/ie/plugin/bho/http_negotiate.h b/ceee/ie/plugin/bho/http_negotiate.h
new file mode 100644
index 0000000..d2975d3
--- /dev/null
+++ b/ceee/ie/plugin/bho/http_negotiate.h
@@ -0,0 +1,69 @@
+// 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_BHO_HTTP_NEGOTIATE_H_
+#define CEEE_IE_PLUGIN_BHO_HTTP_NEGOTIATE_H_
+
+#include <atlbase.h>
+#include <shdeprecated.h>
+
+#include "base/basictypes.h"
+
+// Typedefs for IHttpNegotiate methods.
+typedef HRESULT (STDMETHODCALLTYPE* IHttpNegotiate_BeginningTransaction_Fn)(
+ IHttpNegotiate* me, LPCWSTR url, LPCWSTR headers, DWORD reserved,
+ LPWSTR* additional_headers);
+typedef HRESULT (STDMETHODCALLTYPE* IHttpNegotiate_OnResponse_Fn)(
+ IHttpNegotiate* me, DWORD response_code, LPCWSTR response_header,
+ LPCWSTR request_header, LPWSTR* additional_request_headers);
+
+// Typedefs for IBindStatusCallback methods.
+typedef HRESULT (STDMETHODCALLTYPE* IBindStatusCallback_StartBinding_Fn)(
+ IBindStatusCallback* me, DWORD reserved, IBinding *binding);
+typedef HRESULT (STDMETHODCALLTYPE* IBindStatusCallback_OnProgress_Fn)(
+ IBindStatusCallback* me, ULONG progress, ULONG progress_max,
+ ULONG status_code, LPCWSTR status_text);
+
+// Typedefs for IInternetProtocolSink methods.
+typedef HRESULT (STDMETHODCALLTYPE* IInternetProtocolSink_ReportProgress_Fn)(
+ IInternetProtocolSink* me, ULONG status_code, LPCWSTR status_text);
+
+// Patches methods of urlmon's IHttpNegotiate implementation for the purposes
+// of adding to the http user agent header.
+
+class HttpNegotiatePatch {
+ private:
+ // Class is not to be instantiated at the moment.
+ HttpNegotiatePatch();
+ ~HttpNegotiatePatch();
+
+ public:
+ static bool Initialize();
+ static void Uninitialize();
+
+ // IHttpNegotiate patch methods
+ static STDMETHODIMP BeginningTransaction(
+ IHttpNegotiate_BeginningTransaction_Fn original, IHttpNegotiate* me,
+ LPCWSTR url, LPCWSTR headers, DWORD reserved, LPWSTR* additional_headers);
+ static STDMETHODIMP OnResponse(IHttpNegotiate_OnResponse_Fn original,
+ IHttpNegotiate* me, DWORD response_code, LPCWSTR response_headers,
+ LPCWSTR request_headers, LPWSTR* additional_request_headers);
+
+ protected:
+ static HRESULT PatchHttpNegotiate(IUnknown* to_patch);
+
+ private:
+ // Count number of BHOs depending on this patch.
+ // Unhook when the last one goes away.
+ static CComAutoCriticalSection bho_instance_count_crit_;
+ static int bho_instance_count_;
+
+ DISALLOW_COPY_AND_ASSIGN(HttpNegotiatePatch);
+};
+
+// Attempts to get to the associated browser service for an active request.
+HRESULT GetBrowserServiceFromProtocolSink(IInternetProtocolSink* sink,
+ IBrowserService** browser_service);
+
+#endif // CEEE_IE_PLUGIN_BHO_HTTP_NEGOTIATE_H_
diff --git a/ceee/ie/plugin/bho/infobar_browser_window.cc b/ceee/ie/plugin/bho/infobar_browser_window.cc
new file mode 100644
index 0000000..fecf5e6
--- /dev/null
+++ b/ceee/ie/plugin/bho/infobar_browser_window.cc
@@ -0,0 +1,205 @@
+// 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.
+//
+// Implementation of the infobar browser window.
+
+#include "ceee/ie/plugin/bho/infobar_browser_window.h"
+
+#include <atlapp.h>
+#include <atlcrack.h>
+#include <atlmisc.h>
+#include <atlsafe.h>
+#include <atlwin.h>
+
+#include "base/logging.h"
+#include "ceee/common/com_utils.h"
+#include "ceee/ie/common/ceee_module_util.h"
+#include "chrome/common/chrome_switches.h"
+#include "chrome_frame/com_message_event.h"
+
+namespace infobar_api {
+
+_ATL_FUNC_INFO InfobarBrowserWindow::handler_type_long_ =
+ { CC_STDCALL, VT_EMPTY, 1, { VT_I4 } };
+_ATL_FUNC_INFO InfobarBrowserWindow::handler_type_bstr_i4_=
+ { CC_STDCALL, VT_EMPTY, 2, { VT_BSTR, VT_I4 } };
+_ATL_FUNC_INFO InfobarBrowserWindow::handler_type_void_=
+ { CC_STDCALL, VT_EMPTY, 0, { } };
+
+InfobarBrowserWindow::InfobarBrowserWindow() : delegate_(NULL) {
+}
+
+InfobarBrowserWindow::~InfobarBrowserWindow() {
+}
+
+STDMETHODIMP InfobarBrowserWindow::GetWantsPrivileged(
+ boolean* wants_privileged) {
+ *wants_privileged = true;
+ return S_OK;
+}
+
+STDMETHODIMP InfobarBrowserWindow::GetChromeExtraArguments(BSTR* args) {
+ DCHECK(args);
+
+ // Must enable experimental extensions because we want to load html pages
+ // from our extension.
+ // Extra arguments are passed on verbatim, so we add the -- prefix.
+ CComBSTR str = "--";
+ str.Append(switches::kEnableExperimentalExtensionApis);
+
+ *args = str.Detach();
+ return S_OK;
+}
+
+STDMETHODIMP InfobarBrowserWindow::GetChromeProfileName(BSTR* profile_name) {
+ *profile_name = ::SysAllocString(
+ ceee_module_util::GetBrokerProfileNameForIe());
+ return S_OK;
+}
+
+STDMETHODIMP InfobarBrowserWindow::GetExtensionApisToAutomate(
+ BSTR* functions_enabled) {
+ *functions_enabled = NULL;
+ return S_FALSE;
+}
+
+STDMETHODIMP_(void) InfobarBrowserWindow::OnCfReadyStateChanged(LONG state) {
+ if (state == READYSTATE_COMPLETE) {
+ // We already loaded the extension, enable them in this CF.
+ chrome_frame_->getEnabledExtensions();
+ // Also we should already have URL, navigate to it.
+ Navigate();
+ infobar_events_funnel().OnDocumentComplete();
+ }
+}
+
+STDMETHODIMP_(void) InfobarBrowserWindow::OnCfExtensionReady(BSTR path,
+ int response) {
+ if (ceee_module_util::IsCrxOrEmpty(extension_path_)) {
+ // If we get here, it's because we just did the first-time
+ // install, so save the installation path+time for future comparison.
+ ceee_module_util::SetInstalledExtensionPath(FilePath(extension_path_));
+ }
+
+ chrome_frame_->getEnabledExtensions();
+}
+
+STDMETHODIMP_(void) InfobarBrowserWindow::OnCfClose() {
+ if (delegate_ != NULL)
+ delegate_->OnWindowClose();
+}
+
+ HRESULT InfobarBrowserWindow::Initialize(HWND parent) {
+ HRESULT hr = InitializeAndShowWindow(parent);
+ if (FAILED(hr)) {
+ LOG(ERROR) << "Infobar browser failed to initialize its site window: " <<
+ com::LogHr(hr);
+ return hr;
+ }
+
+ return S_OK;
+}
+
+HRESULT InfobarBrowserWindow::InitializeAndShowWindow(HWND parent) {
+ if (NULL == Create(parent))
+ return E_FAIL;
+
+ BOOL shown = ShowWindow(SW_SHOW);
+ DCHECK(shown);
+
+ return shown ? S_OK : E_FAIL;
+}
+
+HRESULT InfobarBrowserWindow::Teardown() {
+ if (IsWindow()) {
+ // Teardown the ActiveX host window.
+ CAxWindow host(m_hWnd);
+ CComPtr<IObjectWithSite> host_with_site;
+ HRESULT hr = host.QueryHost(&host_with_site);
+ if (SUCCEEDED(hr))
+ host_with_site->SetSite(NULL);
+
+ DestroyWindow();
+ }
+
+ if (chrome_frame_) {
+ ChromeFrameEvents::DispEventUnadvise(chrome_frame_);
+ }
+
+ return S_OK;
+}
+
+void InfobarBrowserWindow::SetUrl(const std::wstring& url) {
+ // Navigate to the URL if the browser exists, otherwise just store the URL.
+ url_ = url;
+ Navigate();
+}
+
+
+LRESULT InfobarBrowserWindow::OnCreate(LPCREATESTRUCT lpCreateStruct) {
+ // Grab a self-reference.
+ GetUnknown()->AddRef();
+
+ // Create a host window instance.
+ CComPtr<IAxWinHostWindow> host;
+ HRESULT hr = CAxHostWindow::CreateInstance(&host);
+ if (FAILED(hr)) {
+ LOG(ERROR) << "Infobar failed to create ActiveX host window. " <<
+ com::LogHr(hr);
+ return 1;
+ }
+
+ // We're the site for the host window, this needs to be in place
+ // before we attach ChromeFrame to the ActiveX control window, so
+ // as to allow it to probe our service provider.
+ hr = SetChildSite(host);
+ DCHECK(SUCCEEDED(hr));
+
+ // Create the chrome frame instance.
+ hr = chrome_frame_.CoCreateInstance(L"ChromeTab.ChromeFrame");
+ if (FAILED(hr)) {
+ LOG(ERROR) << "Failed to create the Chrome Frame instance. " <<
+ com::LogHr(hr);
+ return 1;
+ }
+
+ // And attach it to our window.
+ hr = host->AttachControl(chrome_frame_, m_hWnd);
+ if (FAILED(hr)) {
+ LOG(ERROR) << "Failed to attach Chrome Frame to the host. " <<
+ com::LogHr(hr);
+ return 1;
+ }
+
+ // Hook up the chrome frame event listener.
+ hr = ChromeFrameEvents::DispEventAdvise(chrome_frame_);
+ if (FAILED(hr)) {
+ LOG(ERROR) << "Failed to hook up event sink. " << com::LogHr(hr);
+ }
+
+ return S_OK;
+}
+
+void InfobarBrowserWindow::OnDestroy() {
+ if (chrome_frame_) {
+ ChromeFrameEvents::DispEventUnadvise(chrome_frame_);
+ chrome_frame_.Release();
+ }
+}
+
+void InfobarBrowserWindow::Navigate() {
+ // If the browser has not been created then just return.
+ if (!chrome_frame_)
+ return;
+
+ if (url_.empty()) {
+ LOG(WARNING) << "Navigating infobar to not specified URL";
+ } else {
+ HRESULT hr = chrome_frame_->put_src(CComBSTR(url_.c_str()));
+ LOG_IF(WARNING, FAILED(hr)) <<
+ "Infobar: ChromeFrame::put_src returned: " << com::LogHr(hr);
+ }
+}
+
+} // namespace infobar_api
diff --git a/ceee/ie/plugin/bho/infobar_browser_window.h b/ceee/ie/plugin/bho/infobar_browser_window.h
new file mode 100644
index 0000000..fd6c975
--- /dev/null
+++ b/ceee/ie/plugin/bho/infobar_browser_window.h
@@ -0,0 +1,156 @@
+// 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.
+//
+// @file
+// Infobar browser window. This is the window that hosts CF and navigates it
+// to the infobar URL.
+
+#ifndef CEEE_IE_PLUGIN_BHO_INFOBAR_BROWSER_WINDOW_H_
+#define CEEE_IE_PLUGIN_BHO_INFOBAR_BROWSER_WINDOW_H_
+
+#include <atlbase.h>
+#include <atlapp.h> // Must be included AFTER base.
+#include <atlcrack.h>
+#include <atlgdi.h>
+#include <atlmisc.h>
+
+#include "base/scoped_ptr.h"
+#include "base/singleton.h"
+#include "ceee/common/initializing_coclass.h"
+#include "ceee/ie/plugin/bho/infobar_events_funnel.h"
+
+#include "chrome_tab.h" // NOLINT
+#include "toolband.h" // NOLINT
+
+namespace infobar_api {
+
+class InfobarBrowserWindow;
+
+typedef IDispEventSimpleImpl<0, InfobarBrowserWindow, &DIID_DIChromeFrameEvents>
+ ChromeFrameEvents;
+
+// The window that hosts CF where infobar URL is loaded. It implements a limited
+// site functionality needed for CF as well as handles sink events from CF.
+// TODO(vadimb@google.com): Refactor this class, ChromeFrameHost and ToolBand
+// to have a common functionality supported in a common base class.
+class ATL_NO_VTABLE InfobarBrowserWindow
+ : public CComObjectRootEx<CComSingleThreadModel>,
+ public IObjectWithSiteImpl<InfobarBrowserWindow>,
+ public IServiceProviderImpl<InfobarBrowserWindow>,
+ public IChromeFramePrivileged,
+ public ChromeFrameEvents,
+ public CWindowImpl<InfobarBrowserWindow> {
+ public:
+ // Class to connect this an instance of InfobarBrowserWindow with a hosting
+ // object who should inherit from InfobarBrowserWindow::Delegate and set it
+ // with set_delegate() functions.
+ class Delegate {
+ public:
+ virtual ~Delegate() {}
+ // Informs about window.close() event.
+ virtual void OnWindowClose() = 0;
+ };
+
+ InfobarBrowserWindow();
+ ~InfobarBrowserWindow();
+
+ BEGIN_COM_MAP(InfobarBrowserWindow)
+ COM_INTERFACE_ENTRY(IServiceProvider)
+ COM_INTERFACE_ENTRY(IChromeFramePrivileged)
+ END_COM_MAP()
+
+ BEGIN_SERVICE_MAP(InfobarBrowserWindow)
+ SERVICE_ENTRY(SID_ChromeFramePrivileged)
+ SERVICE_ENTRY_CHAIN(m_spUnkSite)
+ END_SERVICE_MAP()
+
+ BEGIN_SINK_MAP(InfobarBrowserWindow)
+ SINK_ENTRY_INFO(0, DIID_DIChromeFrameEvents,
+ CF_EVENT_DISPID_ONREADYSTATECHANGED,
+ OnCfReadyStateChanged, &handler_type_long_)
+ SINK_ENTRY_INFO(0, DIID_DIChromeFrameEvents,
+ CF_EVENT_DISPID_ONEXTENSIONREADY,
+ OnCfExtensionReady, &handler_type_bstr_i4_)
+ SINK_ENTRY_INFO(0, DIID_DIChromeFrameEvents,
+ CF_EVENT_DISPID_ONCLOSE,
+ OnCfClose, &handler_type_void_)
+ END_SINK_MAP()
+
+ BEGIN_MSG_MAP(InfobarBrowserWindow)
+ MSG_WM_CREATE(OnCreate)
+ MSG_WM_DESTROY(OnDestroy)
+ END_MSG_MAP()
+
+ // @name IChromeFramePrivileged implementation.
+ // @{
+ STDMETHOD(GetWantsPrivileged)(boolean *wants_privileged);
+ STDMETHOD(GetChromeExtraArguments)(BSTR *args);
+ STDMETHOD(GetChromeProfileName)(BSTR *args);
+ STDMETHOD(GetExtensionApisToAutomate)(BSTR *args);
+ // @}
+
+ // @name ChromeFrame event handlers.
+ // @{
+ STDMETHOD_(void, OnCfReadyStateChanged)(LONG state);
+ STDMETHOD_(void, OnCfExtensionReady)(BSTR path, int response);
+ STDMETHOD_(void, OnCfClose)();
+ // @}
+
+ // Initializes the browser window to the given site.
+ HRESULT Initialize(HWND parent);
+ // Tears down an initialized browser window.
+ HRESULT Teardown();
+
+ // Navigates the browser to the given URL if the browser has already been
+ // created, otherwise stores the URL to navigate later on.
+ void SetUrl(const std::wstring& url);
+
+ // Set the delegate to be informed about window.close() events.
+ void set_delegate(Delegate* delegate) { delegate_ = delegate; }
+
+ // Unit test seam.
+ virtual InfobarEventsFunnel& infobar_events_funnel() {
+ return infobar_events_funnel_;
+ }
+
+ protected:
+ // @name Message handlers.
+ // @{
+ LRESULT OnCreate(LPCREATESTRUCT lpCreateStruct);
+ void OnDestroy();
+ // @}
+
+ private:
+ // The funnel for sending infobar events to the broker.
+ InfobarEventsFunnel infobar_events_funnel_;
+
+ // Our Chrome frame instance.
+ CComPtr<IChromeFrame> chrome_frame_;
+
+ // Url to navigate infobar to.
+ std::wstring url_;
+ // Filesystem path to the .crx we will install, or the empty string, or
+ // (if not ending in .crx) the path to an exploded extension directory to
+ // load.
+ std::wstring extension_path_;
+
+ // Delegate. Not owned by the instance of this object.
+ Delegate* delegate_;
+
+ static _ATL_FUNC_INFO handler_type_long_;
+ static _ATL_FUNC_INFO handler_type_bstr_i4_;
+ static _ATL_FUNC_INFO handler_type_void_;
+
+ // Subroutine of general initialization. Extracted to make testable.
+ virtual HRESULT InitializeAndShowWindow(HWND parent);
+
+ // Navigate the browser to url_ if the browser has been created.
+ void Navigate();
+
+ DISALLOW_COPY_AND_ASSIGN(InfobarBrowserWindow);
+};
+
+} // namespace infobar_api
+
+#endif // CEEE_IE_PLUGIN_BHO_INFOBAR_BROWSER_WINDOW_H_
diff --git a/ceee/ie/plugin/bho/infobar_events_funnel.cc b/ceee/ie/plugin/bho/infobar_events_funnel.cc
new file mode 100644
index 0000000..d1a0b67
--- /dev/null
+++ b/ceee/ie/plugin/bho/infobar_events_funnel.cc
@@ -0,0 +1,21 @@
+// 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.
+//
+// Funnel of Chrome Extension Infobar Events to the Broker.
+
+#include "ceee/ie/plugin/bho/infobar_events_funnel.h"
+
+#include "base/logging.h"
+#include "base/scoped_ptr.h"
+#include "base/values.h"
+#include "base/json/json_writer.h"
+
+namespace {
+const char kOnDocumentCompleteEventName[] = "infobar.onDocumentComplete";
+}
+
+HRESULT InfobarEventsFunnel::OnDocumentComplete() {
+ DictionaryValue info;
+ return SendEvent(kOnDocumentCompleteEventName, info);
+}
diff --git a/ceee/ie/plugin/bho/infobar_events_funnel.h b/ceee/ie/plugin/bho/infobar_events_funnel.h
new file mode 100644
index 0000000..fcfa274d7
--- /dev/null
+++ b/ceee/ie/plugin/bho/infobar_events_funnel.h
@@ -0,0 +1,24 @@
+// 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.
+//
+// Funnel of Chrome Extension Infobar Events.
+
+#ifndef CEEE_IE_PLUGIN_BHO_INFOBAR_EVENTS_FUNNEL_H_
+#define CEEE_IE_PLUGIN_BHO_INFOBAR_EVENTS_FUNNEL_H_
+
+#include "ceee/ie/plugin/bho/events_funnel.h"
+
+// Implements a set of methods to send infobar related events to the Broker.
+class InfobarEventsFunnel : public EventsFunnel {
+ public:
+ InfobarEventsFunnel() : EventsFunnel(false) {}
+
+ // Sends the infobar.onDocumentComplete event to the Broker.
+ virtual HRESULT OnDocumentComplete();
+
+ private:
+ DISALLOW_COPY_AND_ASSIGN(InfobarEventsFunnel);
+};
+
+#endif // CEEE_IE_PLUGIN_BHO_INFOBAR_EVENTS_FUNNEL_H_
diff --git a/ceee/ie/plugin/bho/infobar_events_funnel_unittest.cc b/ceee/ie/plugin/bho/infobar_events_funnel_unittest.cc
new file mode 100644
index 0000000..9fe15ea
--- /dev/null
+++ b/ceee/ie/plugin/bho/infobar_events_funnel_unittest.cc
@@ -0,0 +1,38 @@
+// 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.
+//
+// Unit tests for InfobarEventsFunnel.
+
+#include "ceee/ie/plugin/bho/infobar_events_funnel.h"
+#include "base/values.h"
+#include "gmock/gmock.h"
+#include "gtest/gtest.h"
+
+namespace {
+
+using testing::Return;
+using testing::StrEq;
+
+MATCHER_P(ValuesEqual, value, "") {
+ return arg.Equals(value);
+}
+
+const char kOnDocumentCompleteEventName[] = "infobar.onDocumentComplete";
+
+class TestInfobarEventsFunnel : public InfobarEventsFunnel {
+ public:
+ MOCK_METHOD2(SendEvent, HRESULT(const char*, const Value&));
+};
+
+TEST(InfobarEventsFunnelTest, OnDocumentComplete) {
+ TestInfobarEventsFunnel infobar_events_funnel;
+ DictionaryValue dict;
+
+ EXPECT_CALL(infobar_events_funnel, SendEvent(
+ StrEq(kOnDocumentCompleteEventName), ValuesEqual(&dict))).
+ WillOnce(Return(S_OK));
+ EXPECT_HRESULT_SUCCEEDED(infobar_events_funnel.OnDocumentComplete());
+}
+
+} // namespace
diff --git a/ceee/ie/plugin/bho/infobar_manager.cc b/ceee/ie/plugin/bho/infobar_manager.cc
new file mode 100644
index 0000000..6b969f6
--- /dev/null
+++ b/ceee/ie/plugin/bho/infobar_manager.cc
@@ -0,0 +1,270 @@
+// 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.
+//
+// Implementation of the manager for infobar windows.
+
+#include "ceee/ie/plugin/bho/infobar_manager.h"
+
+#include <atlbase.h>
+#include <atlapp.h> // Must be included AFTER base.
+#include <atlcrack.h>
+#include <atlmisc.h>
+#include <atlwin.h>
+
+#include "base/logging.h"
+#include "ceee/common/windows_constants.h"
+#include "chrome_frame/utils.h"
+
+namespace {
+
+enum ContainerWindowUserMessages {
+ TM_NOTIFY_UPDATE_POSITION = WM_USER + 600,
+ TM_DELAYED_CLOSE_INFOBAR = (TM_NOTIFY_UPDATE_POSITION + 1),
+};
+
+} // namespace
+
+namespace infobar_api {
+
+// ContainerWindow subclasses IE content window's container window, handles
+// WM_NCCALCSIZE to resize its client area. It also handles WM_SIZE and WM_MOVE
+// messages to make infobars consistent with IE content window's size and
+// position.
+class InfobarManager::ContainerWindow : public CWindowImpl<ContainerWindow> {
+ public:
+ ContainerWindow(HWND container, InfobarManager* manager)
+ : infobar_manager_(manager) {
+ PinModule();
+ destroyed_ = !::IsWindow(container) || !SubclassWindow(container);
+ }
+
+ virtual ~ContainerWindow() {
+ if (!destroyed_)
+ UnsubclassWindow();
+ }
+
+ bool destroyed() const {
+ return destroyed_;
+ }
+
+ BEGIN_MSG_MAP_EX(ContainerWindow)
+ MSG_WM_NCCALCSIZE(OnNcCalcSize)
+ MSG_WM_SIZE(OnSize)
+ MSG_WM_MOVE(OnMove)
+ MSG_WM_DESTROY(OnDestroy)
+ MESSAGE_HANDLER(TM_NOTIFY_UPDATE_POSITION, OnNotifyUpdatePosition)
+ MESSAGE_HANDLER(TM_DELAYED_CLOSE_INFOBAR, OnDelayedCloseInfobar)
+ END_MSG_MAP()
+
+ private:
+ // Handles WM_NCCALCSIZE message.
+ LRESULT OnNcCalcSize(BOOL calc_valid_rects, LPARAM lparam) {
+ // Adjust client area for infobar.
+ LRESULT ret = DefWindowProc(WM_NCCALCSIZE,
+ static_cast<WPARAM>(calc_valid_rects), lparam);
+ // Whether calc_valid_rects is true or false, we could treat beginning of
+ // lparam as a RECT object.
+ RECT* rect = reinterpret_cast<RECT*>(lparam);
+ if (infobar_manager_ != NULL)
+ infobar_manager_->OnContainerWindowNcCalcSize(rect);
+
+ // If infobars reserve all the space and rect becomes empty, the container
+ // window won't receive subsequent WM_SIZE and WM_MOVE messages.
+ // In this case, we have to explicitly notify infobars to update their
+ // position.
+ if (rect->right - rect->left <= 0 || rect->bottom - rect->top <= 0)
+ PostMessage(TM_NOTIFY_UPDATE_POSITION, 0, 0);
+ return ret;
+ }
+
+ // Handles WM_SIZE message.
+ void OnSize(UINT type, CSize size) {
+ DefWindowProc(WM_SIZE, static_cast<WPARAM>(type),
+ MAKELPARAM(size.cx, size.cy));
+ if (infobar_manager_ != NULL)
+ infobar_manager_->OnContainerWindowUpdatePosition();
+ }
+
+ // Handles WM_MOVE message.
+ void OnMove(CPoint point) {
+ if (infobar_manager_ != NULL)
+ infobar_manager_->OnContainerWindowUpdatePosition();
+ }
+
+ // Handles WM_DESTROY message.
+ void OnDestroy() {
+ // When refreshing IE window, this window may be destroyed.
+ if (infobar_manager_ != NULL)
+ infobar_manager_->OnContainerWindowDestroy();
+ if (m_hWnd && IsWindow())
+ UnsubclassWindow();
+ destroyed_ = true;
+ }
+
+ // Handles TM_NOTIFY_UPDATE_POSITION message - delayed window resize message.
+ LRESULT OnNotifyUpdatePosition(UINT message, WPARAM wparam, LPARAM lparam,
+ BOOL& handled) {
+ if (infobar_manager_ != NULL)
+ infobar_manager_->OnContainerWindowUpdatePosition();
+
+ handled = TRUE;
+ return 0;
+ }
+
+ // Handles TM_DELAYED_CLOSE_INFOBAR - delayed infobar window close request.
+ LRESULT OnDelayedCloseInfobar(UINT message, WPARAM wparam, LPARAM lparam,
+ BOOL& handled) {
+ if (infobar_manager_ != NULL) {
+ InfobarType type = static_cast<InfobarType>(wparam);
+ infobar_manager_->OnContainerWindowDelayedCloseInfobar(type);
+ }
+
+ handled = TRUE;
+ return 0;
+ }
+
+ // Pointer to infobar manager. This object is not owned by the class instance.
+ InfobarManager* infobar_manager_;
+
+ // True is this window was destroyed or not subclassed.
+ bool destroyed_;
+
+ DISALLOW_COPY_AND_ASSIGN(ContainerWindow);
+};
+
+InfobarManager::InfobarManager(HWND tab_window)
+ : tab_window_(tab_window) {
+ for (int index = 0; index < END_OF_INFOBAR_TYPE; ++index) {
+ // Note that when InfobarManager is being initialized the IE has not created
+ // the tab. Therefore we cannot find the container window here and have to
+ // pass interface for a function that finds windows to be called later.
+ infobars_[index].reset(
+ InfobarWindow::CreateInfobar(static_cast<InfobarType>(index), this));
+ }
+}
+
+HRESULT InfobarManager::Show(InfobarType type, int max_height,
+ const std::wstring& url, bool slide) {
+ if (type < FIRST_INFOBAR_TYPE || type >= END_OF_INFOBAR_TYPE ||
+ infobars_[type] == NULL) {
+ return E_INVALIDARG;
+ }
+ // Set the URL. If the window is not created it will navigate there as soon as
+ // it is created.
+ infobars_[type]->Navigate(url);
+ // Create the window if not created.
+ if (!infobars_[type]->IsWindow()) {
+ infobars_[type]->Create(tab_window_, NULL, NULL,
+ WS_CHILD | WS_CLIPCHILDREN);
+ }
+ if (!infobars_[type]->IsWindow())
+ return E_UNEXPECTED;
+
+ HRESULT hr = infobars_[type]->Show(max_height, slide);
+ return hr;
+}
+
+HRESULT InfobarManager::Hide(InfobarType type) {
+ if (type < FIRST_INFOBAR_TYPE || type >= END_OF_INFOBAR_TYPE ||
+ infobars_[type] == NULL) {
+ return E_INVALIDARG;
+ }
+ // There is a choice either to hide or to destroy the infobar window.
+ // This implementation destroys the infobar to save resources and stop all
+ // scripts that possibly still run in the window. If we want to just hide the
+ // infobar window instead then we should change Reset to Hide here, possibly
+ // navigate the infobar window to "about:blank" and make sure that the code
+ // in Show() does not try to create the chrome frame window again.
+ infobars_[type]->Reset();
+ return S_OK;
+}
+
+void InfobarManager::HideAll() {
+ for (int index = 0; index < END_OF_INFOBAR_TYPE; ++index)
+ Hide(static_cast<InfobarType>(index));
+}
+
+// Callback function for EnumChildWindows. lParam should be the pointer to
+// HWND variable where the handle of the window which class is
+// kIeTabContentParentWindowClass will be written.
+static BOOL CALLBACK FindContentParentWindowsProc(HWND hwnd, LPARAM lparam) {
+ HWND* window_handle = reinterpret_cast<HWND*>(lparam);
+ if (NULL == window_handle) {
+ // It makes no sense to continue enumeration.
+ return FALSE;
+ }
+
+ // Variable to hold the class name. The size does not matter as long as it
+ // is at least can hold kIeTabContentParentWindowClass.
+ wchar_t class_name[100];
+ if (::GetClassName(hwnd, class_name, arraysize(class_name)) &&
+ lstrcmpi(windows::kIeTabContentParentWindowClass, class_name) == 0) {
+ // We found the window. Return its handle and stop enumeration.
+ *window_handle = hwnd;
+ return FALSE;
+ }
+ return TRUE;
+}
+
+HWND InfobarManager::GetContainerWindow() {
+ if (container_window_ != NULL && container_window_->destroyed())
+ container_window_.reset(NULL);
+
+ if (container_window_ == NULL) {
+ if (tab_window_ != NULL && ::IsWindow(tab_window_)) {
+ // Find the window which is the container for the HTML view (parent of
+ // the content).
+ HWND content_parent_window = NULL;
+ ::EnumChildWindows(tab_window_, FindContentParentWindowsProc,
+ reinterpret_cast<LPARAM>(&content_parent_window));
+ DCHECK(content_parent_window);
+ if (content_parent_window != NULL) {
+ container_window_.reset(
+ new ContainerWindow(content_parent_window, this));
+ }
+ }
+ }
+ DCHECK(container_window_ != NULL && container_window_->IsWindow());
+ return container_window_->m_hWnd;
+}
+
+void InfobarManager::OnWindowClose(InfobarType type) {
+ // This callback is called from CF callback so we should not destroy the
+ // infobar window right away as it may result on deleting the object that
+ // started this callback. So instead we post ourtselves the message.
+ if (container_window_ != NULL)
+ container_window_->PostMessage(TM_DELAYED_CLOSE_INFOBAR,
+ static_cast<WPARAM>(type), 0);
+}
+
+void InfobarManager::OnContainerWindowNcCalcSize(RECT* rect) {
+ if (rect == NULL)
+ return;
+
+ for (int index = 0; index < END_OF_INFOBAR_TYPE; ++index) {
+ if (infobars_[index] != NULL)
+ infobars_[index]->ReserveSpace(rect);
+ }
+}
+
+void InfobarManager::OnContainerWindowUpdatePosition() {
+ for (int index = 0; index < END_OF_INFOBAR_TYPE; ++index) {
+ if (infobars_[index] != NULL)
+ infobars_[index]->UpdatePosition();
+ }
+}
+
+void InfobarManager::OnContainerWindowDelayedCloseInfobar(InfobarType type) {
+ // Hide the infobar window. Parameter validation is handled in Hide().
+ Hide(type);
+}
+
+void InfobarManager::OnContainerWindowDestroy() {
+ for (int index = 0; index < END_OF_INFOBAR_TYPE; ++index) {
+ if (infobars_[index] != NULL)
+ infobars_[index]->Reset();
+ }
+}
+
+} // namespace infobar_api
diff --git a/ceee/ie/plugin/bho/infobar_manager.h b/ceee/ie/plugin/bho/infobar_manager.h
new file mode 100644
index 0000000..dead924
--- /dev/null
+++ b/ceee/ie/plugin/bho/infobar_manager.h
@@ -0,0 +1,67 @@
+// 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.
+//
+// @file
+// Manager for infobar windows.
+
+#ifndef CEEE_IE_PLUGIN_BHO_INFOBAR_MANAGER_H_
+#define CEEE_IE_PLUGIN_BHO_INFOBAR_MANAGER_H_
+
+#include "base/scoped_ptr.h"
+#include "base/singleton.h"
+#include "ceee/ie/plugin/bho/infobar_window.h"
+#include "ceee/ie/plugin/bho/web_browser_events_source.h"
+
+namespace infobar_api {
+
+// InfobarManager creates and manages infobars, which are displayed at the top
+// or bottom of IE content window.
+class InfobarManager : public InfobarWindow::Delegate,
+ public WebBrowserEventsSource::Sink {
+ public:
+ explicit InfobarManager(HWND tab_window);
+
+ // Shows the infobar of the specified type and navigates it to the specified
+ // URL.
+ HRESULT Show(InfobarType type, int max_height, const std::wstring& url,
+ bool slide);
+ // Hides the infobar of the specified type.
+ HRESULT Hide(InfobarType type);
+ // Hides all infobars.
+ void HideAll();
+
+ // Implementation of InfobarWindow::Delegate.
+ // Finds the handle of the container window.
+ virtual HWND GetContainerWindow();
+ // Informs about window.close() event.
+ virtual void OnWindowClose(InfobarType type);
+
+ private:
+ class ContainerWindow;
+
+ // The HWND of the tab window the infobars are associated with.
+ HWND tab_window_;
+
+ // Parent window for IE content window.
+ scoped_ptr<ContainerWindow> container_window_;
+
+ // Infobar windows.
+ scoped_ptr<InfobarWindow> infobars_[END_OF_INFOBAR_TYPE];
+
+ // ContainerWindow callbacks.
+ // Callback for WM_NCCALCSIZE.
+ void OnContainerWindowNcCalcSize(RECT* rect);
+ // Callback for messages on size or position change.
+ void OnContainerWindowUpdatePosition();
+ // Callback for message requesting closing the infobar.
+ void OnContainerWindowDelayedCloseInfobar(InfobarType type);
+ // Callback for WM_DESTROY.
+ void OnContainerWindowDestroy();
+
+ DISALLOW_COPY_AND_ASSIGN(InfobarManager);
+};
+
+} // namespace infobar_api
+
+#endif // CEEE_IE_PLUGIN_BHO_INFOBAR_MANAGER_H_
diff --git a/ceee/ie/plugin/bho/infobar_window.cc b/ceee/ie/plugin/bho/infobar_window.cc
new file mode 100644
index 0000000..62e9301
--- /dev/null
+++ b/ceee/ie/plugin/bho/infobar_window.cc
@@ -0,0 +1,314 @@
+// 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.
+//
+// Implementation of the manager for infobar windows.
+
+#include "ceee/ie/plugin/bho/infobar_window.h"
+
+#include <atlapp.h>
+#include <atlcrack.h>
+#include <atlmisc.h>
+
+#include "base/logging.h"
+#include "base/scoped_ptr.h"
+
+namespace {
+
+const UINT_PTR kInfobarSlidingTimerId = 1U;
+// Interval for sliding the infobar in milliseconds.
+const UINT kInfobarSlidingTimerIntervalMs = 50U;
+// The step when the infobar is sliding, in pixels.
+const int kInfobarSlidingStep = 10;
+// The default height of the infobar. See also similar constant in
+// ceee/ie/plugin/bho/executor.cc which overrides this one.
+const int kInfobarDefaultHeight = 39;
+
+} // namespace
+
+
+namespace infobar_api {
+
+InfobarWindow* InfobarWindow::CreateInfobar(InfobarType type,
+ Delegate* delegate) {
+ DCHECK(delegate);
+ return NULL == delegate ? NULL : new InfobarWindow(type, delegate);
+}
+
+InfobarWindow::InfobarWindow(InfobarType type, Delegate* delegate)
+ : type_(type),
+ delegate_(delegate),
+ show_(false),
+ target_height_(1),
+ current_height_(1),
+ sliding_infobar_(false) {
+ DCHECK(delegate);
+}
+
+InfobarWindow::~InfobarWindow() {
+ Reset();
+
+ if (IsWindow()) {
+ DestroyWindow();
+ } else {
+ NOTREACHED() << "Infobar window was not successfully created.";
+ }
+}
+
+void InfobarWindow::OnWindowClose() {
+ // Propagate the event to the manager.
+ if (delegate_ != NULL)
+ delegate_->OnWindowClose(type_);
+}
+
+HRESULT InfobarWindow::Show(int max_height, bool slide) {
+ if (url_.empty())
+ return E_UNEXPECTED;
+
+ StartUpdatingLayout(true, max_height, slide);
+ return S_OK;
+}
+
+HRESULT InfobarWindow::Hide() {
+ StartUpdatingLayout(false, 0, false);
+
+ return S_OK;
+}
+
+HRESULT InfobarWindow::Navigate(const std::wstring& url) {
+ // If the CF exists (which means the infobar has already been created) then
+ // navigate it. Otherwise just store the URL, it will be passed to the CF when
+ // it will be created.
+ url_ = url;
+ if (chrome_frame_host_)
+ chrome_frame_host_->SetUrl(url_);
+ return S_OK;
+}
+
+void InfobarWindow::ReserveSpace(RECT* rect) {
+ DCHECK(rect);
+ if (rect == NULL || !show_)
+ return;
+
+ switch (type_) {
+ case TOP_INFOBAR:
+ rect->top += current_height_;
+ if (rect->top > rect->bottom)
+ rect->top = rect->bottom;
+ break;
+ case BOTTOM_INFOBAR:
+ rect->bottom -= current_height_;
+ if (rect->bottom < rect->top)
+ rect->bottom = rect->top;
+ break;
+ default:
+ NOTREACHED() << "Unknown InfobarType value.";
+ break;
+ }
+}
+
+void InfobarWindow::UpdatePosition() {
+ // Make infobar be consistent with IE window's size.
+ // NOTE: Even if currently it is not visible, we still need to update its
+ // position, since the contents may need to decide its layout based on the
+ // width of the infobar.
+
+ CRect rect = CalculatePosition();
+ if (IsWindow())
+ MoveWindow(&rect, TRUE);
+}
+
+void InfobarWindow::Reset() {
+ Hide();
+
+ if (chrome_frame_host_)
+ chrome_frame_host_->set_delegate(NULL);
+
+ DCHECK(!show_ && !sliding_infobar_);
+ if (m_hWnd != NULL) {
+ DestroyWindow();
+ m_hWnd = NULL;
+ }
+ url_.clear();
+ chrome_frame_host_.Release();
+}
+
+void InfobarWindow::StartUpdatingLayout(bool show, int max_height, bool slide) {
+ if (!IsWindow()) {
+ LOG(ERROR) << "Updating infobar layout when window has not been created";
+ return;
+ }
+
+ show_ = show;
+ if (show) {
+ int html_content_height = kInfobarDefaultHeight;
+ CSize html_content_size(0, 0);
+ if (SUCCEEDED(GetContentSize(&html_content_size)) &&
+ html_content_size.cy > 0) {
+ html_content_height = html_content_size.cy;
+ }
+ target_height_ = (max_height == 0 || html_content_height < max_height) ?
+ html_content_height : max_height;
+ if (target_height_ <= 0) {
+ target_height_ = 1;
+ }
+ } else {
+ target_height_ = 1;
+ }
+
+ if (!slide || !show) {
+ current_height_ = target_height_;
+
+ if (sliding_infobar_) {
+ KillTimer(kInfobarSlidingTimerId);
+ sliding_infobar_ = false;
+ }
+ } else {
+ // If the infobar is visible and sliding effect is requested, we need to
+ // start expanding/shrinking the infobar according to its current height.
+ current_height_ = CalculateNextHeight();
+
+ if (!sliding_infobar_) {
+ SetTimer(kInfobarSlidingTimerId, kInfobarSlidingTimerIntervalMs, NULL);
+ sliding_infobar_ = true;
+ }
+ }
+
+ UpdateLayout();
+}
+
+int InfobarWindow::CalculateNextHeight() {
+ if (current_height_ < target_height_) {
+ return std::min(current_height_ + kInfobarSlidingStep, target_height_);
+ } else if (current_height_ > target_height_) {
+ return std::max(current_height_ - kInfobarSlidingStep, target_height_);
+ } else {
+ return current_height_;
+ }
+}
+
+RECT InfobarWindow::CalculatePosition() {
+ CRect rect(0, 0, 0, 0);
+
+ if (NULL == delegate_)
+ return rect;
+ HWND container_window = delegate_->GetContainerWindow();
+ if (container_window == NULL || !::IsWindow(container_window))
+ return rect;
+ HWND container_parent_window = ::GetParent(container_window);
+ if (!::IsWindow(container_parent_window))
+ return rect;
+
+ ::GetWindowRect(container_window, &rect);
+ ::MapWindowPoints(NULL, container_parent_window,
+ reinterpret_cast<POINT*>(&rect), 2);
+
+ switch (type_) {
+ case TOP_INFOBAR:
+ if (rect.top + current_height_ < rect.bottom)
+ rect.bottom = rect.top + current_height_;
+ break;
+ case BOTTOM_INFOBAR:
+ if (rect.bottom - current_height_ > rect.top)
+ rect.top = rect.bottom - current_height_;
+ break;
+ default:
+ NOTREACHED() << "Unknown InfobarType value.";
+ break;
+ }
+ return rect;
+}
+
+void InfobarWindow::UpdateLayout() {
+ CRect rect = CalculatePosition();
+ if (IsWindow()) {
+ // Set infobar's z-order, place it at the top, so that it won't be hidden by
+ // IE window.
+ SetWindowPos(HWND_TOP, &rect, show_ ? SWP_SHOWWINDOW : SWP_HIDEWINDOW);
+ }
+
+ HWND container_window = NULL;
+ if (delegate_ != NULL)
+ container_window = delegate_->GetContainerWindow();
+ if (container_window != NULL && ::IsWindow(container_window)) {
+ // Call SetWindowPos with SWP_FRAMECHANGED for IE window, then IE
+ // window would receive WM_NCCALCSIZE to recalculate its client size.
+ ::SetWindowPos(container_window,
+ NULL, 0, 0, 0, 0,
+ SWP_NOACTIVATE | SWP_NOMOVE | SWP_NOSIZE | SWP_NOZORDER |
+ SWP_FRAMECHANGED);
+ }
+}
+
+HRESULT InfobarWindow::GetContentSize(SIZE* size) {
+ DCHECK(size);
+ if (NULL == size)
+ return E_POINTER;
+
+ // Set the size to 0 that means we do not know it.
+ // TODO(vadimb@google.com): Find how to get the content size from the CF.
+ size->cx = 0;
+ size->cy = 0;
+ return S_OK;
+}
+
+LRESULT InfobarWindow::OnTimer(UINT_PTR nIDEvent) {
+ DCHECK(nIDEvent == kInfobarSlidingTimerId);
+ if (show_ && sliding_infobar_ && current_height_ != target_height_) {
+ current_height_ = CalculateNextHeight();
+ UpdateLayout();
+ } else if (sliding_infobar_) {
+ KillTimer(kInfobarSlidingTimerId);
+ sliding_infobar_ = false;
+ }
+
+ return S_OK;
+}
+
+LRESULT InfobarWindow::OnCreate(LPCREATESTRUCT lpCreateStruct) {
+ // TODO(vadimb@google.com): Better way to do this is to derive
+ // InfobarBrowserWindow from InitializingCoClass and give it an
+ // InitializeMethod, then create it with
+ // InfobarBrowserWindow::CreateInitialized(...).
+ CComObject<InfobarBrowserWindow>* chrome_frame_host = NULL;
+ CComObject<InfobarBrowserWindow>::CreateInstance(&chrome_frame_host);
+ if (chrome_frame_host) {
+ chrome_frame_host_.Attach(chrome_frame_host);
+ chrome_frame_host_->SetUrl(url_);
+ chrome_frame_host_->Initialize(m_hWnd);
+ chrome_frame_host_->set_delegate(this);
+ AdjustSize();
+ }
+ return S_OK;
+}
+
+void InfobarWindow::OnPaint(CDCHandle dc) {
+ RECT rc;
+ if (GetUpdateRect(&rc, FALSE)) {
+ PAINTSTRUCT ps = {};
+ BeginPaint(&ps);
+
+ BOOL ret = GetClientRect(&rc);
+ DCHECK(ret);
+ FillRect(ps.hdc, &rc, (HBRUSH)GetStockObject(GRAY_BRUSH));
+ ::DrawText(ps.hdc, L"Google CEEE. No Chrome Frame found!", -1,
+ &rc, DT_SINGLELINE | DT_CENTER | DT_VCENTER);
+
+ EndPaint(&ps);
+ }
+}
+
+void InfobarWindow::OnSize(UINT type, CSize size) {
+ AdjustSize();
+}
+
+void InfobarWindow::AdjustSize() {
+ if (NULL != chrome_frame_host_) {
+ CRect rect;
+ GetClientRect(&rect);
+ chrome_frame_host_->SetWindowPos(NULL, 0, 0, rect.Width(), rect.Height(),
+ SWP_NOACTIVATE | SWP_NOZORDER);
+ }
+}
+
+} // namespace infobar_api
diff --git a/ceee/ie/plugin/bho/infobar_window.h b/ceee/ie/plugin/bho/infobar_window.h
new file mode 100644
index 0000000..e745cef
--- /dev/null
+++ b/ceee/ie/plugin/bho/infobar_window.h
@@ -0,0 +1,143 @@
+// 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.
+//
+// @file
+// Infobar window.
+
+#ifndef CEEE_IE_PLUGIN_BHO_INFOBAR_WINDOW_H_
+#define CEEE_IE_PLUGIN_BHO_INFOBAR_WINDOW_H_
+
+#include <atlbase.h>
+#include <atlapp.h> // Must be included AFTER base.
+#include <atlcrack.h>
+#include <atlgdi.h>
+#include <atlmisc.h>
+#include <atlwin.h>
+
+#include "base/singleton.h"
+#include "base/scoped_ptr.h"
+#include "ceee/ie/plugin/bho/infobar_browser_window.h"
+
+namespace infobar_api {
+
+enum InfobarType {
+ FIRST_INFOBAR_TYPE = 0,
+ TOP_INFOBAR = 0, // Infobar at the top.
+ BOTTOM_INFOBAR = 1, // Infobar at the bottom.
+ END_OF_INFOBAR_TYPE = 2
+};
+
+// InfobarWindow is the window created on the top or bottom of the browser tab
+// window that contains the web browser window.
+class InfobarWindow : public InfobarBrowserWindow::Delegate,
+ public CWindowImpl<InfobarWindow, CWindow> {
+ public:
+ class Delegate {
+ public:
+ virtual ~Delegate() {}
+ // Returns the window handle for the HTML container window.
+ virtual HWND GetContainerWindow() = 0;
+ // Informs about window.close() event.
+ virtual void OnWindowClose(InfobarType type) = 0;
+ };
+
+ static InfobarWindow* CreateInfobar(InfobarType type, Delegate* delegate);
+ ~InfobarWindow();
+
+ // Implementation of InfobarBrowserWindow::Delegate.
+ // Informs about window.close() event.
+ virtual void OnWindowClose();
+
+ // Shows the infobar.
+ // NOTE: Navigate should be called before Show.
+ // The height of the infobar is calculated to fit the content (limited to
+ // max_height if the content is too high; no limit if max_height is set to
+ // 0).
+ // slide indicates whether to show sliding effect.
+ HRESULT Show(int max_height, bool slide);
+
+ // Hides the infobar.
+ HRESULT Hide();
+
+ // Navigates the HTML view of the infobar.
+ HRESULT Navigate(const std::wstring& url);
+
+ // Reserves space for the infobar when IE window recalculates its size.
+ void ReserveSpace(RECT* rect);
+
+ // Updates the infobar size and position when IE content window size or
+ // position is changed.
+ void UpdatePosition();
+
+ // Destroys the browser window.
+ void Reset();
+
+ private:
+ BEGIN_MSG_MAP(InfobarWindow)
+ MSG_WM_TIMER(OnTimer);
+ MSG_WM_CREATE(OnCreate)
+ MSG_WM_PAINT(OnPaint)
+ MSG_WM_SIZE(OnSize)
+ END_MSG_MAP()
+
+ // Type of the infobar - whether it is displayed at the top or at the bottom
+ // of the IE content window.
+ InfobarType type_;
+
+ // Delegate, connection to the infobar manager.
+ Delegate* delegate_;
+
+ // URL to navigate to.
+ std::wstring url_;
+
+ // Whether the infobar is shown or not.
+ bool show_;
+
+ // The target height of the infobar.
+ int target_height_;
+
+ // The current height of the infobar.
+ int current_height_;
+
+ // Indicates whether the infobar is sliding.
+ bool sliding_infobar_;
+
+ // The Chrome Frame host handling a Chrome Frame instance for us.
+ CComPtr<InfobarBrowserWindow> chrome_frame_host_;
+
+ // Constructor.
+ InfobarWindow(InfobarType type, Delegate* delegate);
+
+ // If show is true, shrinks IE content window and shows the infobar
+ // either at the top or at the bottom. Otherwise, hides the infobar and
+ // restores IE content window.
+ void StartUpdatingLayout(bool show, int max_height, bool slide);
+
+ // Based on the current height and the target height, decides the height of
+ // the next step. This is used when showing sliding effect.
+ int CalculateNextHeight();
+
+ // Calculates the position of the infobar based on its current height.
+ RECT CalculatePosition();
+
+ // Updates the layout (sizes and positions of infobar and IE content window)
+ // based on the current height.
+ void UpdateLayout();
+
+ HRESULT GetContentSize(SIZE* size);
+
+ // Event handlers.
+ LRESULT OnTimer(UINT_PTR nIDEvent);
+ LRESULT OnCreate(LPCREATESTRUCT lpCreateStruct);
+ void OnPaint(CDCHandle dc);
+ void OnSize(UINT type, CSize size);
+
+ void AdjustSize();
+
+ DISALLOW_COPY_AND_ASSIGN(InfobarWindow);
+};
+
+} // namespace infobar_api
+
+#endif // CEEE_IE_PLUGIN_BHO_INFOBAR_WINDOW_H_
diff --git a/ceee/ie/plugin/bho/mediumtest_browser_event.cc b/ceee/ie/plugin/bho/mediumtest_browser_event.cc
new file mode 100644
index 0000000..3288e9d
--- /dev/null
+++ b/ceee/ie/plugin/bho/mediumtest_browser_event.cc
@@ -0,0 +1,495 @@
+// 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.
+//
+// A test that hosts and excercises the webbrowser control to test
+// its event firing behavior.
+#include <atlcrack.h>
+#include <atlsync.h>
+#include <atlwin.h>
+#include <set>
+
+#include "base/logging.h"
+#include "base/file_path.h"
+#include "base/path_service.h"
+#include "base/base_paths_win.h"
+#include "ceee/ie/testing/mediumtest_ie_common.h"
+#include "gmock/gmock.h"
+#include "gtest/gtest.h"
+#include "ceee/common/com_utils.h"
+#include "ceee/common/initializing_coclass.h"
+#include "ceee/testing/utils/mock_com.h"
+#include "ceee/testing/utils/test_utils.h"
+#include "ceee/testing/utils/instance_count_mixin.h"
+
+
+namespace {
+
+using testing::InstanceCountMixin;
+using testing::InstanceCountMixinBase;
+
+using testing::BrowserEventSinkBase;
+using testing::GetTestUrl;
+using testing::GetTempPath;
+using testing::ShellBrowserTestImpl;
+
+// {AFF1D082-6B03-4b29-9521-E52240F6333B}
+const GUID IID_Dummy =
+ { 0xaff1d082, 0x6b03, 0x4b29,
+ { 0x95, 0x21, 0xe5, 0x22, 0x40, 0xf6, 0x33, 0x3b } };
+
+class TestBrowserEventSink;
+
+class TestFrameEventHandler
+ : public CComObjectRootEx<CComSingleThreadModel>,
+ public InitializingCoClass<TestFrameEventHandler>,
+ public InstanceCountMixin<TestFrameEventHandler>,
+ public IPropertyNotifySink,
+ public IAdviseSink {
+ public:
+ BEGIN_COM_MAP(TestFrameEventHandler)
+ COM_INTERFACE_ENTRY(IPropertyNotifySink)
+ COM_INTERFACE_ENTRY(IAdviseSink)
+ END_COM_MAP()
+
+ DECLARE_PROTECT_FINAL_CONSTRUCT();
+
+ TestFrameEventHandler() : event_sink_(NULL),
+ document_property_notify_sink_cookie_(-1),
+ advise_sink_cookie_(-1) {
+ }
+
+ virtual ~TestFrameEventHandler() {
+ ATLTRACE("~TestFrameEventHandler[%ws]\n", url_ ? url_ : L"");
+ }
+
+ void DetachFromSink();
+
+ STDMETHOD(OnChanged)(DISPID changed_property);
+ STDMETHOD(OnRequestEdit)(DISPID changed_property) {
+ ATLTRACE("OnRequestEdit(%d)\n", changed_property);
+ return S_OK;
+ }
+
+ // IAdviseSink
+ STDMETHOD_(void, OnDataChange)(FORMATETC *pFormatetc, STGMEDIUM *pStgmed) {
+ ATLTRACE("%s\n", __FUNCTION__);
+ }
+ STDMETHOD_(void, OnViewChange)(DWORD dwAspect, LONG lindex) {
+ ATLTRACE("%s\n", __FUNCTION__);
+ }
+ STDMETHOD_(void, OnRename)(IMoniker *pmk) {
+ ATLTRACE("%s\n", __FUNCTION__);
+ }
+ STDMETHOD_(void, OnSave)() {
+ ATLTRACE("%s\n", __FUNCTION__);
+ }
+ STDMETHOD_(void, OnClose)();
+
+ virtual void GetDescription(std::string* description) const {
+ description->clear();
+ description->append("TestFrameEventHandler");
+ // TODO(siggi@chromium.org): append URL
+ }
+
+ HRESULT Initialize(TestBrowserEventSink* event_sink,
+ IWebBrowser2* browser,
+ BSTR url);
+ void FinalRelease();
+
+ void set_url(const wchar_t* url) { url_ = url; }
+ const wchar_t* url() const { return url_ ? url_ : L""; }
+
+ template <class Interface>
+ HRESULT GetBrowser(Interface** browser) {
+ return browser_.QueryInterface(browser);
+ }
+
+ private:
+ CComBSTR url_;
+ CComDispatchDriver document_;
+ CComPtr<IWebBrowser2> browser_;
+ TestBrowserEventSink* event_sink_;
+
+ DWORD advise_sink_cookie_;
+ DWORD document_property_notify_sink_cookie_;
+};
+
+class TestBrowserEventSink
+ : public BrowserEventSinkBase,
+ public InitializingCoClass<TestBrowserEventSink> {
+ public:
+ // Disambiguate.
+ using InitializingCoClass<TestBrowserEventSink>::CreateInitialized;
+
+ HRESULT Initialize(TestBrowserEventSink** self, IWebBrowser2* browser) {
+ *self = this;
+ return BrowserEventSinkBase::Initialize(browser);
+ }
+
+ // We have seen cases where we get destroyed while the ATL::CAxHostWindow
+ // may still hold references to frame handlers.
+ void FinalRelease() {
+ FrameHandlerMap::iterator it(frame_handlers_.begin());
+ FrameHandlerMap::iterator end(frame_handlers_.end());
+
+ // Since they will detach from us as we clear them, we must not do it
+ // while we loop.
+ std::vector<TestFrameEventHandler*> to_be_detached;
+ for (; it != end; ++it) {
+ to_be_detached.push_back(it->second);
+ }
+ for (size_t index = 0; index < to_be_detached.size(); ++index) {
+ to_be_detached[index]->DetachFromSink();
+ }
+ ASSERT_EQ(0, frame_handlers_.size());
+ }
+
+ virtual void GetDescription(std::string* description) const {
+ description->clear();
+ description->append("TestBrowserEventSink");
+ }
+
+ void AttachHandler(IWebBrowser2* browser, TestFrameEventHandler* handler) {
+ ASSERT_TRUE(browser != NULL && handler != NULL);
+ CComPtr<IUnknown> browser_unk;
+ ASSERT_HRESULT_SUCCEEDED(browser->QueryInterface(&browser_unk));
+
+ // We shouldn't already have one.
+ ASSERT_TRUE(NULL == FindHandlerForBrowser(browser));
+
+ frame_handlers_.insert(std::make_pair(browser_unk, handler));
+ }
+
+ void DetachHandler(IWebBrowser2* browser, TestFrameEventHandler* handler) {
+ ASSERT_TRUE(browser != NULL && handler != NULL);
+
+ // It should already be registered.
+ ASSERT_TRUE(NULL != FindHandlerForBrowser(browser));
+
+ CComPtr<IUnknown> browser_unk;
+ ASSERT_HRESULT_SUCCEEDED(browser->QueryInterface(&browser_unk));
+ ASSERT_EQ(1, frame_handlers_.erase(browser_unk));
+ }
+
+ TestFrameEventHandler* FindHandlerForBrowser(IDispatch* browser) {
+ CComPtr<IUnknown> browser_unk;
+ EXPECT_HRESULT_SUCCEEDED(browser->QueryInterface(&browser_unk));
+
+ FrameHandlerMap::iterator it(frame_handlers_.find(browser_unk));
+ if (it == frame_handlers_.end())
+ return NULL;
+
+ return it->second;
+ }
+
+ TestFrameEventHandler* FindHandlerForUrl(const std::wstring& url) {
+ FrameHandlerMap::iterator it(frame_handlers_.begin());
+ FrameHandlerMap::iterator end(frame_handlers_.end());
+
+ for (; it != end; ++it) {
+ CComQIPtr<IWebBrowser2> browser(it->first);
+ EXPECT_TRUE(browser != NULL);
+ CComBSTR location_url;
+ EXPECT_HRESULT_SUCCEEDED(browser->get_LocationURL(&location_url));
+
+ if (0 == ::UrlCompare(url.c_str(), location_url, TRUE))
+ return it->second;
+ }
+
+ return NULL;
+ }
+
+ // Override.
+ STDMETHOD_(void, OnNavigateComplete)(IDispatch* browser_disp,
+ VARIANT* url_var) {
+ CComBSTR url;
+ if (V_VT(url_var) == VT_BSTR)
+ url = V_BSTR(url_var);
+
+ TestFrameEventHandler* frame_handler = FindHandlerForBrowser(browser_disp);
+ if (!frame_handler) {
+ CComQIPtr<IWebBrowser2> browser(browser_disp);
+ ASSERT_TRUE(browser != NULL);
+
+ CComPtr<IUnknown> frame_handler_keeper;
+ ASSERT_HRESULT_SUCCEEDED(
+ TestFrameEventHandler::CreateInitialized(this,
+ browser,
+ url,
+ &frame_handler_keeper));
+ } else {
+ ATLTRACE("FrameHandler[%ws] -> %ws\n", frame_handler->url(), url);
+ frame_handler->set_url(url);
+ }
+ }
+
+ private:
+ typedef std::map<IUnknown*, TestFrameEventHandler*> FrameHandlerMap;
+ // Keeps a map from a frame or top-level browser's identifying
+ // IUnknown to the frame event handler instance attached.
+ FrameHandlerMap frame_handlers_;
+};
+
+
+STDMETHODIMP TestFrameEventHandler::OnChanged(DISPID changed_property) {
+ ATLTRACE("OnChanged(%d)\n", changed_property);
+
+ if (changed_property == DISPID_READYSTATE) {
+ CComVariant ready_state;
+ CComDispatchDriver document(document_);
+ EXPECT_TRUE(document != NULL);
+ EXPECT_HRESULT_SUCCEEDED(document.GetProperty(DISPID_READYSTATE,
+ &ready_state));
+ EXPECT_EQ(V_VT(&ready_state), VT_I4);
+ ATLTRACE("READYSTATE Frame[%ws]: %d\n", url_, ready_state.lVal);
+
+ TestBrowserEventSink::add_state(static_cast<READYSTATE>(ready_state.lVal));
+ }
+
+ return S_OK;
+}
+
+HRESULT TestFrameEventHandler::Initialize(TestBrowserEventSink* event_sink,
+ IWebBrowser2* browser,
+ BSTR url) {
+ EXPECT_HRESULT_SUCCEEDED(browser->get_Document(&document_));
+
+ CComQIPtr<IHTMLDocument2> html_document2(document_);
+ if (html_document2 != NULL) {
+ event_sink_ = event_sink;
+ browser_ = browser;
+ url_ = url;
+ EXPECT_HRESULT_SUCCEEDED(AtlAdvise(document_,
+ GetUnknown(),
+ IID_IPropertyNotifySink,
+ &document_property_notify_sink_cookie_));
+
+ ATLTRACE("TestFrameEventHandler::Initialize[%ws]\n", url_ ? url_ : L"");
+
+ CComQIPtr<IOleObject> document_ole_object(document_);
+ EXPECT_TRUE(document_ole_object != NULL);
+ EXPECT_HRESULT_SUCCEEDED(
+ document_ole_object->Advise(this, &advise_sink_cookie_));
+
+ event_sink_->AttachHandler(browser_, this);
+ } else {
+ // This happens when we're navigated to e.g. a PDF doc or a folder.
+ }
+
+ return S_OK;
+}
+
+
+void TestFrameEventHandler::DetachFromSink() {
+ ASSERT_TRUE(event_sink_ != NULL);
+ event_sink_->DetachHandler(browser_, this);
+ event_sink_ = NULL;
+}
+
+void TestFrameEventHandler::FinalRelease() {
+ if (event_sink_ && browser_)
+ event_sink_->DetachHandler(browser_, this);
+ browser_.Release();
+ document_.Release();
+}
+
+STDMETHODIMP_(void) TestFrameEventHandler::OnClose() {
+ EXPECT_HRESULT_SUCCEEDED(AtlUnadvise(document_,
+ IID_IPropertyNotifySink,
+ document_property_notify_sink_cookie_));
+
+ CComQIPtr<IOleObject> document_ole_object(document_);
+ EXPECT_TRUE(document_ole_object != NULL);
+ EXPECT_HRESULT_SUCCEEDED(
+ document_ole_object->Unadvise(advise_sink_cookie_));
+}
+
+class BrowserEventTest: public ShellBrowserTestImpl<TestBrowserEventSink> {
+};
+
+const wchar_t* kSimplePage = L"simple_page.html";
+
+TEST_F(BrowserEventTest, RefreshTopLevelBrowserRetainsFrameHandler) {
+ EXPECT_TRUE(NavigateBrowser(GetTestUrl(kSimplePage)));
+
+ // We should have only one frame at this point.
+ EXPECT_EQ(1, TestFrameEventHandler::instance_count());
+
+ // Refreshing the top-level browser retains it.
+ EXPECT_HRESULT_SUCCEEDED(browser_->Refresh());
+ EXPECT_TRUE(WaitForReadystateLoading());
+ EXPECT_TRUE(WaitForReadystateComplete());
+
+ // Still there after refresh.
+ EXPECT_EQ(1, TestFrameEventHandler::instance_count());
+}
+
+const wchar_t* kTwoFramesPage = L"two_frames.html";
+const wchar_t* kFrameOne = L"frame_one.html";
+const wchar_t* kFrameTwo = L"frame_two.html";
+
+TEST_F(BrowserEventTest, NavigateToFrames) {
+ EXPECT_TRUE(NavigateBrowser(GetTestUrl(kTwoFramesPage)));
+
+ // We should have three frame handlers at this point.
+ EXPECT_EQ(3, TestFrameEventHandler::instance_count());
+
+ // We should have a handler for each of these.
+ TestFrameEventHandler* two_frames =
+ event_sink()->FindHandlerForUrl(GetTestUrl(kTwoFramesPage));
+ TestFrameEventHandler* frame_one =
+ event_sink()->FindHandlerForUrl(GetTestUrl(kFrameOne));
+ TestFrameEventHandler* frame_two =
+ event_sink()->FindHandlerForUrl(GetTestUrl(kFrameTwo));
+ ASSERT_TRUE(two_frames != NULL);
+ ASSERT_TRUE(frame_one != NULL);
+ ASSERT_TRUE(frame_two != NULL);
+
+ // Noteworthy fact: the top level browser implements an
+ // IPropertyNotifySink connection point, but the sub-browsers
+ // for the frames do not.
+ {
+ CComQIPtr<IConnectionPointContainer> cpc;
+ ASSERT_HRESULT_SUCCEEDED(two_frames->GetBrowser(&cpc));
+ ASSERT_TRUE(cpc != NULL);
+ CComPtr<IConnectionPoint> cp;
+ EXPECT_HRESULT_SUCCEEDED(
+ cpc->FindConnectionPoint(IID_IPropertyNotifySink, &cp));
+ }
+
+ {
+ CComQIPtr<IConnectionPointContainer> cpc;
+ ASSERT_HRESULT_SUCCEEDED(frame_one->GetBrowser(&cpc));
+ ASSERT_TRUE(cpc != NULL);
+ CComPtr<IConnectionPoint> cp;
+ EXPECT_HRESULT_FAILED(
+ cpc->FindConnectionPoint(IID_IPropertyNotifySink, &cp));
+ }
+
+ {
+ CComQIPtr<IConnectionPointContainer> cpc;
+ ASSERT_HRESULT_SUCCEEDED(frame_two->GetBrowser(&cpc));
+ ASSERT_TRUE(cpc != NULL);
+ CComPtr<IConnectionPoint> cp;
+ EXPECT_HRESULT_FAILED(
+ cpc->FindConnectionPoint(IID_IPropertyNotifySink, &cp));
+ }
+
+ // Test sub-frame document IPropertyNotifySink.
+ {
+ CComPtr<IWebBrowser2> browser;
+ ASSERT_HRESULT_SUCCEEDED(frame_two->GetBrowser(&browser));
+
+ CComPtr<IDispatch> document_disp;
+ ASSERT_HRESULT_SUCCEEDED(browser->get_Document(&document_disp));
+
+ CComPtr<IConnectionPointContainer> cpc;
+ ASSERT_HRESULT_SUCCEEDED(document_disp->QueryInterface(&cpc));
+ CComPtr<IConnectionPoint> cp;
+ ASSERT_HRESULT_SUCCEEDED(
+ cpc->FindConnectionPoint(IID_IPropertyNotifySink, &cp));
+ }
+}
+
+TEST_F(BrowserEventTest, ReNavigateToSamePageRetainsEventHandler) {
+ const std::wstring url(GetTestUrl(kSimplePage));
+ EXPECT_TRUE(NavigateBrowser(url));
+
+ // We should have a frame handler attached now.
+ EXPECT_EQ(1, TestFrameEventHandler::instance_count());
+
+ // Retrieve it and make sure it doesn't die.
+ TestFrameEventHandler* handler_before =
+ event_sink()->FindHandlerForUrl(url);
+
+ ASSERT_TRUE(handler_before != NULL);
+ CComPtr<IUnknown> handler_before_keeper(handler_before->GetUnknown());
+
+ // Re-navigate the browser to the same page.
+ EXPECT_TRUE(NavigateBrowser(GetTestUrl(kSimplePage)));
+ // Note: on re-navigation we don't see the top-level
+ // browser's readystate drop, I guess that only happens
+ // on transitions between content types. E.g. when a
+ // navigation requires the shell browser to instantiate
+ // a new type of document, such as going from a
+ // HTML doc to a PDF doc or the like.
+ EXPECT_FALSE(WaitForReadystateLoading());
+ EXPECT_TRUE(WaitForReadystateComplete());
+
+ // We should only have the one frame handler in existence now.
+ EXPECT_EQ(1, TestFrameEventHandler::instance_count());
+
+ // Retrieve the new one.
+ TestFrameEventHandler* handler_after =
+ event_sink()->FindHandlerForUrl(GetTestUrl(kSimplePage));
+
+ ASSERT_EQ(handler_before, handler_after);
+
+ // Release the old one, it should still stay around
+ handler_before_keeper.Release();
+ EXPECT_EQ(1, TestFrameEventHandler::instance_count());
+}
+
+TEST_F(BrowserEventTest, NavigateToDifferentPageRetainsEventHandler) {
+ const std::wstring first_url(GetTestUrl(kSimplePage));
+ EXPECT_TRUE(NavigateBrowser(first_url));
+
+ // We should have a frame handler attached now.
+ EXPECT_EQ(1, TestFrameEventHandler::instance_count());
+
+ // Retrieve it and make sure it doesn't die.
+ TestFrameEventHandler* handler_before =
+ event_sink()->FindHandlerForUrl(first_url);
+
+ ASSERT_TRUE(handler_before != NULL);
+ CComPtr<IUnknown> handler_before_keeper(handler_before->GetUnknown());
+
+ // Navigate the browser to another page.
+ const std::wstring second_url(GetTestUrl(kTwoFramesPage));
+ EXPECT_TRUE(NavigateBrowser(second_url));
+ EXPECT_FALSE(WaitForReadystateLoading());
+ EXPECT_TRUE(WaitForReadystateComplete());
+
+ // We should have the three frame handlers in existence now.
+ EXPECT_EQ(3, TestFrameEventHandler::instance_count());
+
+ // Retrieve the new one for the top-level browser.
+ TestFrameEventHandler* handler_after =
+ event_sink()->FindHandlerForUrl(second_url);
+
+ ASSERT_EQ(handler_before, handler_after);
+
+ // Release the old one, it should still stay around
+ handler_before_keeper.Release();
+ EXPECT_EQ(3, TestFrameEventHandler::instance_count());
+}
+
+TEST_F(BrowserEventTest, RefreshFrameBrowserRetainsHandler) {
+ EXPECT_TRUE(NavigateBrowser(GetTestUrl(kTwoFramesPage)));
+
+ // We should have three frame handlers at this point.
+ EXPECT_EQ(3, TestFrameEventHandler::instance_count());
+
+ // Get one of the frames.
+ TestFrameEventHandler* frame_two =
+ event_sink()->FindHandlerForUrl(GetTestUrl(kFrameTwo));
+ ASSERT_TRUE(frame_two != NULL);
+
+ // Now refresh a sub-browser instance, let it settle,
+ // observe its frame event handler is still around and
+ // has signalled a readystate transition.
+ CComPtr<IWebBrowser2> browser;
+ ASSERT_HRESULT_SUCCEEDED(frame_two->GetBrowser(&browser));
+ ASSERT_HRESULT_SUCCEEDED(browser->Refresh());
+
+ EXPECT_TRUE(WaitForReadystateLoading());
+ EXPECT_TRUE(WaitForReadystateComplete());
+
+ // We should have all three frame handlers at this point.
+ EXPECT_EQ(3, TestFrameEventHandler::instance_count());
+ frame_two = event_sink()->FindHandlerForUrl(GetTestUrl(kFrameTwo));
+ EXPECT_TRUE(frame_two != NULL);
+}
+
+} // namespace
diff --git a/ceee/ie/plugin/bho/mediumtest_browser_helper_object.cc b/ceee/ie/plugin/bho/mediumtest_browser_helper_object.cc
new file mode 100644
index 0000000..fd433508
--- /dev/null
+++ b/ceee/ie/plugin/bho/mediumtest_browser_helper_object.cc
@@ -0,0 +1,531 @@
+// 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.
+//
+// A test that hosts and excercises the webbrowser control to test
+// its event firing behavior.
+#include <guiddef.h>
+#include <mshtml.h>
+#include <shlguid.h>
+#include "base/utf_string_conversions.h"
+#include "ceee/ie/common/ceee_module_util.h"
+#include "ceee/ie/plugin/bho/browser_helper_object.h"
+#include "ceee/ie/testing/mediumtest_ie_common.h"
+#include "ceee/ie/testing/mock_broker_and_friends.h"
+#include "ceee/ie/testing/mock_chrome_frame_host.h"
+#include "gmock/gmock.h"
+#include "gtest/gtest.h"
+#include "ceee/common/com_utils.h"
+#include "ceee/common/initializing_coclass.h"
+#include "ceee/testing/utils/mock_com.h"
+#include "ceee/testing/utils/test_utils.h"
+#include "ceee/testing/utils/instance_count_mixin.h"
+#include "net/base/net_util.h"
+
+namespace {
+
+using testing::_;
+using testing::AnyNumber;
+using testing::BrowserEventSinkBase;
+using testing::GetTestUrl;
+using testing::DoAll;
+using testing::InstanceCountMixin;
+using testing::kAnotherFrameOne;
+using testing::kAnotherFrameTwo;
+using testing::kAnotherTwoFramesPage;
+using testing::kDeepFramesPage;
+using testing::kFrameOne;
+using testing::kFrameTwo;
+using testing::kLevelOneFrame;
+using testing::kLevelTwoFrame;
+using testing::kOrphansPage;
+using testing::kSimplePage;
+using testing::kTwoFramesPage;
+using testing::MockChromeFrameHost;
+using testing::NotNull;
+using testing::Return;
+using testing::SetArgumentPointee;
+using testing::ShellBrowserTestImpl;
+using testing::StrictMock;
+
+ScriptHost::DebugApplication debug_app(L"FrameEventHandlerUnittest");
+
+class TestingFrameEventHandler
+ : public FrameEventHandler,
+ public InstanceCountMixin<TestingFrameEventHandler>,
+ public InitializingCoClass<TestingFrameEventHandler> {
+ public:
+ // Disambiguate.
+ using InitializingCoClass<TestingFrameEventHandler>::CreateInitializedIID;
+
+ IWebBrowser2* browser() const { return browser_; }
+ IHTMLDocument2* document() const { return document_; }
+};
+
+class TestingBrowserHelperObject
+ : public BrowserHelperObject,
+ public InitializingCoClass<TestingBrowserHelperObject>,
+ public InstanceCountMixin<TestingBrowserHelperObject> {
+ public:
+ TestingBrowserHelperObject() : mock_chrome_frame_host_(NULL) {
+ }
+
+ HRESULT Initialize(TestingBrowserHelperObject** self) {
+ *self = this;
+ return S_OK;
+ }
+
+ HRESULT CreateFrameEventHandler(IWebBrowser2* browser,
+ IWebBrowser2* parent_browser,
+ IFrameEventHandler** handler) {
+ return TestingFrameEventHandler::CreateInitializedIID(
+ browser, parent_browser, this, IID_IFrameEventHandler, handler);
+ }
+
+ virtual TabEventsFunnel& tab_events_funnel() {
+ return mock_tab_events_funnel_;
+ }
+
+ virtual HRESULT GetBrokerRegistrar(ICeeeBrokerRegistrar** broker) {
+ broker_keeper_.CopyTo(broker);
+ return S_OK;
+ }
+
+ virtual HRESULT CreateExecutor(IUnknown** executor) {
+ CComPtr<IUnknown> unknown(executor_keeper_);
+ unknown.CopyTo(executor);
+ return S_OK;
+ }
+
+ HRESULT CreateChromeFrameHost() {
+ HRESULT hr = MockChromeFrameHost::CreateInitializedIID(
+ &mock_chrome_frame_host_, IID_IChromeFrameHost, &chrome_frame_host_);
+
+ // Neuter the functions we know are going to be called.
+ if (SUCCEEDED(hr)) {
+ EXPECT_CALL(*mock_chrome_frame_host_, SetChromeProfileName(_))
+ .Times(1);
+ EXPECT_CALL(*mock_chrome_frame_host_, StartChromeFrame())
+ .WillOnce(Return(S_OK));
+
+ EXPECT_CALL(*mock_chrome_frame_host_, SetEventSink(NotNull()))
+ .Times(1);
+ EXPECT_CALL(*mock_chrome_frame_host_, SetEventSink(NULL))
+ .Times(1);
+
+ EXPECT_CALL(*mock_chrome_frame_host_, PostMessage(_, _))
+ .WillRepeatedly(Return(S_OK));
+
+ EXPECT_CALL(*mock_chrome_frame_host_, TearDown())
+ .WillOnce(Return(S_OK));
+
+ EXPECT_CALL(*mock_chrome_frame_host_, GetSessionId(NotNull()))
+ .WillOnce(DoAll(SetArgumentPointee<0>(44), Return(S_OK)));
+ EXPECT_CALL(*broker_, SetTabIdForHandle(44, _))
+ .WillOnce(Return(S_OK));
+ }
+
+ return hr;
+ }
+
+ // Stub content script manifest loading.
+ void LoadManifestFile() {}
+
+ // Make type public in this class.
+ typedef BrowserHelperObject::BrowserHandlerMap BrowserHandlerMap;
+
+ BrowserHandlerMap::const_iterator browsers_begin() const {
+ return browsers_.begin();
+ };
+ BrowserHandlerMap::const_iterator browsers_end() const {
+ return browsers_.end();
+ };
+
+ TestingFrameEventHandler* FindHandlerForUrl(const std::wstring& url) {
+ BrowserHandlerMap::iterator it(browsers_.begin());
+ BrowserHandlerMap::iterator end(browsers_.end());
+
+ for (; it != end; ++it) {
+ CComQIPtr<IWebBrowser2> browser(it->first.m_T);
+ EXPECT_TRUE(browser != NULL);
+ CComBSTR location_url;
+ EXPECT_HRESULT_SUCCEEDED(browser->get_LocationURL(&location_url));
+
+ if (0 == ::UrlCompare(url.c_str(), location_url, TRUE))
+ return static_cast<TestingFrameEventHandler*>(it->second.m_T.p);
+ }
+
+ return NULL;
+ }
+
+ // Returns true iff we have exactly |num_frames| registering
+ // the urls |resources[0..num_frames)|.
+ bool ExpectHasFrames(size_t num_frames, const std::wstring* resources) {
+ typedef BrowserHandlerMap::const_iterator iterator;
+ iterator it(browsers_.begin());
+
+ size_t count = 0;
+ for (; it != browsers_.end(); ++it) {
+ ++count;
+
+ FrameEventHandler* handler =
+ static_cast<FrameEventHandler*>(it->second.m_T.p);
+ std::wstring url(handler->browser_url());
+ const std::wstring* resources_end = resources + num_frames;
+ const std::wstring* found = std::find(resources, resources_end, url);
+
+ if (resources_end == found) {
+ // A browser navigated to a file: URL reports the
+ // raw file path as its URL, convert the file path
+ // to a URL and search again.
+ FilePath path(handler->browser_url());
+
+ url = UTF8ToWide(net::FilePathToFileURL(path).spec());
+ found = std::find(resources, resources_end, url);
+ }
+
+ EXPECT_TRUE(resources_end != found)
+ << " unexpected frame URL " << url;
+ }
+
+ EXPECT_EQ(num_frames, count);
+ return num_frames == count;
+ }
+
+ template <size_t N>
+ bool ExpectHasFrames(const std::wstring (&resources)[N]) {
+ return ExpectHasFrames(N, resources);
+ }
+
+ MockChromeFrameHost* mock_chrome_frame_host() const {
+ return mock_chrome_frame_host_;
+ }
+
+ testing::MockTabEventsFunnel* mock_tab_events_funnel() {
+ return &mock_tab_events_funnel_;
+ }
+
+ // We should use the executor mock that supports infobar in this test because
+ // OnBeforeNavigate2 queries the executor for infobar interface.
+ testing::MockTabInfobarExecutor* executor_;
+ CComPtr<ICeeeTabExecutor> executor_keeper_;
+
+ testing::MockBroker* broker_;
+ CComPtr<ICeeeBrokerRegistrar> broker_keeper_;
+
+ private:
+ MockChromeFrameHost* mock_chrome_frame_host_;
+ StrictMock<testing::MockTabEventsFunnel> mock_tab_events_funnel_;
+};
+
+class TestBrowserSite
+ : public CComObjectRootEx<CComSingleThreadModel>,
+ public InstanceCountMixin<TestBrowserSite>,
+ public InitializingCoClass<TestBrowserSite>,
+ public IServiceProviderImpl<TestBrowserSite> {
+ public:
+ BEGIN_COM_MAP(TestBrowserSite)
+ COM_INTERFACE_ENTRY(IServiceProvider)
+ END_COM_MAP()
+
+ BEGIN_SERVICE_MAP(TestBrowserSite)
+ SERVICE_ENTRY_CHAIN(browser_)
+ END_SERVICE_MAP()
+
+ HRESULT Initialize(TestBrowserSite **self, IWebBrowser2* browser) {
+ *self = this;
+ browser_ = browser;
+ return S_OK;
+ }
+
+ public:
+ CComPtr<IWebBrowser> browser_;
+};
+
+class BrowserEventSink
+ : public BrowserEventSinkBase,
+ public InitializingCoClass<BrowserEventSink> {
+ public:
+ // Disambiguate.
+ using InitializingCoClass<BrowserEventSink>::CreateInitialized;
+
+ HRESULT Initialize(BrowserEventSink** self, IWebBrowser2* browser) {
+ *self = this;
+ return BrowserEventSinkBase::Initialize(browser);
+ }
+};
+
+class BrowerHelperObjectTest: public ShellBrowserTestImpl<BrowserEventSink> {
+ public:
+ typedef ShellBrowserTestImpl<BrowserEventSink> Super;
+
+ BrowerHelperObjectTest() : bho_(NULL), site_(NULL) {
+ }
+
+ virtual void SetUp() {
+ Super::SetUp();
+
+ // Never torn down as other threads in the test may need it after
+ // teardown.
+ ScriptHost::set_default_debug_application(&debug_app);
+
+ ASSERT_HRESULT_SUCCEEDED(
+ TestingBrowserHelperObject::CreateInitialized(&bho_, &bho_keeper_));
+
+ ASSERT_HRESULT_SUCCEEDED(
+ TestBrowserSite::CreateInitialized(&site_, browser_, &site_keeper_));
+
+ // Create and set expectations for the broker registrar related objects.
+ ASSERT_HRESULT_SUCCEEDED(testing::MockTabInfobarExecutor::CreateInitialized(
+ &bho_->executor_, &bho_->executor_keeper_));
+ ASSERT_HRESULT_SUCCEEDED(
+ testing::MockBroker::CreateInitialized(&bho_->broker_,
+ &bho_->broker_keeper_));
+ EXPECT_CALL(*bho_->broker_, RegisterTabExecutor(_,
+ bho_->executor_keeper_.p)).WillRepeatedly(Return(S_OK));
+ EXPECT_CALL(*bho_->broker_, UnregisterExecutor(_)).
+ WillRepeatedly(Return(S_OK));
+ EXPECT_CALL(*bho_->mock_tab_events_funnel(), OnCreated(_, _, _)).
+ Times(AnyNumber());
+ EXPECT_CALL(*bho_->mock_tab_events_funnel(), OnUpdated(_, _, _)).
+ Times(AnyNumber());
+ EXPECT_CALL(*bho_->executor_, Initialize(_)).WillOnce(Return(S_OK));
+ EXPECT_CALL(*bho_->executor_, OnTopFrameBeforeNavigate(_)).
+ WillRepeatedly(Return(S_OK));
+
+ ASSERT_HRESULT_SUCCEEDED(bho_keeper_->SetSite(site_->GetUnknown()));
+ }
+
+ virtual void TearDown() {
+ EXPECT_CALL(*bho_->mock_tab_events_funnel(), OnRemoved(_));
+ EXPECT_CALL(*bho_->mock_tab_events_funnel(), OnTabUnmapped(_, _));
+ ASSERT_HRESULT_SUCCEEDED(bho_keeper_->SetSite(NULL));
+
+ site_ = NULL;
+ site_keeper_.Release();
+
+ bho_->executor_ = NULL;
+ bho_->executor_keeper_.Release();
+
+ bho_->broker_ = NULL;
+ bho_->broker_keeper_.Release();
+
+ bho_ = NULL;
+ bho_keeper_.Release();
+
+ Super::TearDown();
+ }
+
+ protected:
+ TestingBrowserHelperObject* bho_;
+ CComPtr<IObjectWithSite> bho_keeper_;
+
+ TestBrowserSite* site_;
+ CComPtr<IServiceProvider> site_keeper_;
+};
+
+// This test navigates a webbrowser control instance back and forth
+// between a set of resources with our BHO attached. On every navigation
+// we're asserting on the urls and number of frame event handlers the BHO
+// has recorded, as well as the instance count of the testing frame
+// event handler class.
+// This is to ensure that:
+// 1. Our frame event handlers get attached to all created frames.
+// 2. That any "recycled" frame event handlers track their associated
+// document's URL changes.
+// 3. That we don't leak discarded frame event handlers.
+// 4. That we don't crash during any of this.
+TEST_F(BrowerHelperObjectTest, FrameHandlerCreationAndDestructionOnNavigation) {
+ const std::wstring two_frames_resources[] = {
+ GetTestUrl(kTwoFramesPage),
+ GetTestUrl(kFrameOne),
+ GetTestUrl(kFrameTwo)};
+
+ const std::wstring another_two_frames_resources[] = {
+ GetTestUrl(kAnotherTwoFramesPage),
+ GetTestUrl(kAnotherFrameOne),
+ GetTestUrl(kAnotherFrameTwo)};
+
+ const std::wstring simple_page_resources[] = { GetTestUrl(kSimplePage) };
+
+ EXPECT_TRUE(NavigateBrowser(two_frames_resources[0]));
+ EXPECT_TRUE(bho_->ExpectHasFrames(two_frames_resources));
+
+ // One handler per resource.
+ EXPECT_EQ(arraysize(two_frames_resources),
+ TestingFrameEventHandler::instance_count());
+
+ EXPECT_TRUE(NavigateBrowser(another_two_frames_resources[0]));
+ EXPECT_TRUE(bho_->ExpectHasFrames(another_two_frames_resources));
+
+ // One handler per resource.
+ EXPECT_EQ(arraysize(another_two_frames_resources),
+ TestingFrameEventHandler::instance_count());
+
+ EXPECT_TRUE(NavigateBrowser(simple_page_resources[0]));
+ EXPECT_TRUE(bho_->ExpectHasFrames(simple_page_resources));
+
+ // One handler per resource.
+ EXPECT_EQ(arraysize(simple_page_resources),
+ TestingFrameEventHandler::instance_count());
+}
+
+// What motivated this test is the fact that at teardown time,
+// sometimes the child->parent relationship between the browser
+// instances we've encountered has been disrupted.
+// So if you start with a frame hierarchy like
+// A <- B <- C
+// if you query the parent for C at the timepoint when B is
+// reporting a COMPLETE->LOADING readystate change you'll find this:
+// A <- B
+// A <- C
+// e.g. the C frame has been re-parented to the topmost webbrowser.
+//
+// Strangely I can't get this to repro under programmatic control
+// against the webbrowser control. I suspect conditions are simply
+// different in IE proper, or else there's something special to
+// navigating by user event. I'm still leaving this code in as
+// I hope to find a viable repro for this later, and because this
+// code exercises some modes of navigation that the above test does
+// not.
+TEST_F(BrowerHelperObjectTest, DeepFramesAreCorrectlyHandled) {
+ const std::wstring simple_page_resources[] = { GetTestUrl(kSimplePage) };
+ const std::wstring deep_frames_resources[] = {
+ GetTestUrl(kDeepFramesPage),
+ GetTestUrl(kLevelOneFrame),
+ GetTestUrl(kLevelTwoFrame),
+ GetTestUrl(kFrameOne)
+ };
+
+ // Navigate to a deep frame hierarchy.
+ EXPECT_TRUE(NavigateBrowser(deep_frames_resources[0]));
+ EXPECT_TRUE(bho_->ExpectHasFrames(deep_frames_resources));
+
+ // One handler per resource.
+ EXPECT_EQ(arraysize(deep_frames_resources),
+ TestingFrameEventHandler::instance_count());
+
+ // Navigate to a simple page with only a top-level frame.
+ EXPECT_TRUE(NavigateBrowser(simple_page_resources[0]));
+ EXPECT_TRUE(bho_->ExpectHasFrames(simple_page_resources));
+
+ // One handler per resource.
+ EXPECT_EQ(arraysize(simple_page_resources),
+ TestingFrameEventHandler::instance_count());
+
+ // And back to a deep frame hierarchy.
+ EXPECT_TRUE(NavigateBrowser(deep_frames_resources[0]));
+ EXPECT_TRUE(bho_->ExpectHasFrames(deep_frames_resources));
+
+ // One handler per resource.
+ EXPECT_EQ(arraysize(deep_frames_resources),
+ TestingFrameEventHandler::instance_count());
+
+
+ // Refresh a mid-way frame.
+ TestingFrameEventHandler* handler =
+ bho_->FindHandlerForUrl(GetTestUrl(kLevelOneFrame));
+ ASSERT_TRUE(handler);
+ EXPECT_HRESULT_SUCCEEDED(handler->browser()->Refresh());
+ ASSERT_FALSE(WaitForReadystateLoading());
+ ASSERT_TRUE(WaitForReadystateComplete());
+
+ // We should still have the same set of frames.
+ EXPECT_TRUE(bho_->ExpectHasFrames(deep_frames_resources));
+
+ // One handler per resource.
+ EXPECT_EQ(arraysize(deep_frames_resources),
+ TestingFrameEventHandler::instance_count());
+
+ // Navigate a mid-way frame to a new resource.
+ CComVariant empty;
+ EXPECT_HRESULT_SUCCEEDED(
+ browser_->Navigate2(&CComVariant(GetTestUrl(kTwoFramesPage).c_str()),
+ &empty,
+ &CComVariant(L"level_one"),
+ &empty, &empty));
+
+ ASSERT_FALSE(WaitForReadystateLoading());
+ ASSERT_TRUE(WaitForReadystateComplete());
+
+ // This should now be our resource set.
+ const std::wstring mixed_frames_resources[] = {
+ GetTestUrl(kDeepFramesPage),
+ GetTestUrl(kLevelOneFrame),
+ GetTestUrl(kTwoFramesPage),
+ GetTestUrl(kFrameOne),
+ GetTestUrl(kFrameTwo),
+ };
+ EXPECT_TRUE(bho_->ExpectHasFrames(mixed_frames_resources));
+
+ // One handler per resource.
+ EXPECT_EQ(arraysize(mixed_frames_resources),
+ TestingFrameEventHandler::instance_count());
+}
+
+// What motivated this test is the fact that some webpage can dynamically
+// create frames that don't get navigated. We first saw this on the
+// http://www.zooborns.com site and found that it has some javascript that
+// creates and iframe and manually fill its innerHTML which contains an
+// iframe with a src that is navigated to. Our code used to assume that
+// when a frame is navigated, its parent was previously navigated so we could
+// attach the new frame to its parent that we had seen before. So we needed to
+// add code to create a handler for the ancestors of such orphans.
+//
+TEST_F(BrowerHelperObjectTest, OrphanFrame) {
+ const std::wstring simple_page_resources[] = { GetTestUrl(kSimplePage) };
+ const std::wstring orphan_page_resources[] = {
+ GetTestUrl(kOrphansPage),
+ GetTestUrl(kOrphansPage),
+ GetTestUrl(kFrameOne)
+ };
+
+ // Navigate to an orphanage.
+ EXPECT_TRUE(NavigateBrowser(orphan_page_resources[0]));
+ EXPECT_TRUE(bho_->ExpectHasFrames(orphan_page_resources));
+
+ // One handler per resource.
+ EXPECT_EQ(arraysize(orphan_page_resources),
+ TestingFrameEventHandler::instance_count());
+
+ // Navigate to a simple page with only a top-level frame.
+ EXPECT_TRUE(NavigateBrowser(simple_page_resources[0]));
+ EXPECT_TRUE(bho_->ExpectHasFrames(simple_page_resources));
+
+ // One handler per resource.
+ EXPECT_EQ(arraysize(simple_page_resources),
+ TestingFrameEventHandler::instance_count());
+
+ // And back to the orphanage.
+ EXPECT_TRUE(NavigateBrowser(orphan_page_resources[0]));
+ // On fast machines, we don't wait long enough for everything to be completed.
+ // So we may already be OK, or we may need to wait for an extra COMPLETE.
+ // When we re-navigate to the ophans page like this, for some reason, we first
+ // get one COMPLETE ready state, and then a LOADING and then another COMPLETE.
+ if (arraysize(orphan_page_resources) !=
+ TestingFrameEventHandler::instance_count()) {
+ ASSERT_TRUE(WaitForReadystateComplete());
+ }
+ EXPECT_TRUE(bho_->ExpectHasFrames(orphan_page_resources));
+
+ // One handler per resource.
+ EXPECT_EQ(arraysize(orphan_page_resources),
+ TestingFrameEventHandler::instance_count());
+
+ // Refresh a deep frame.
+ TestingFrameEventHandler* handler =
+ bho_->FindHandlerForUrl(GetTestUrl(kFrameOne));
+ ASSERT_TRUE(handler);
+ EXPECT_HRESULT_SUCCEEDED(handler->browser()->Refresh());
+ ASSERT_FALSE(WaitForReadystateLoading());
+ ASSERT_TRUE(WaitForReadystateComplete());
+
+ // We should still have the same set of frames.
+ EXPECT_TRUE(bho_->ExpectHasFrames(orphan_page_resources));
+
+ // One handler per resource.
+ EXPECT_EQ(arraysize(orphan_page_resources),
+ TestingFrameEventHandler::instance_count());
+}
+
+} // namespace
diff --git a/ceee/ie/plugin/bho/tab_events_funnel.cc b/ceee/ie/plugin/bho/tab_events_funnel.cc
new file mode 100644
index 0000000..275da40
--- /dev/null
+++ b/ceee/ie/plugin/bho/tab_events_funnel.cc
@@ -0,0 +1,85 @@
+// 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.
+//
+// Funnel of Chrome Extension Events from whereever through the Broker.
+
+#include "ceee/ie/plugin/bho/tab_events_funnel.h"
+
+#include "base/logging.h"
+#include "base/scoped_ptr.h"
+#include "base/values.h"
+#include "ceee/ie/common/constants.h"
+#include "chrome/browser/extensions/extension_event_names.h"
+#include "chrome/browser/extensions/extension_tabs_module_constants.h"
+
+namespace ext_event_names = extension_event_names;
+namespace keys = extension_tabs_module_constants;
+
+HRESULT TabEventsFunnel::OnCreated(HWND tab_handle, BSTR url, bool complete) {
+ DictionaryValue tab_values;
+ tab_values.SetInteger(keys::kIdKey, reinterpret_cast<int>(tab_handle));
+ tab_values.SetString(keys::kUrlKey, url);
+ tab_values.SetString(keys::kStatusKey, complete ? keys::kStatusValueComplete :
+ keys::kStatusValueLoading);
+ return SendEvent(ext_event_names::kOnTabCreated, tab_values);
+}
+
+HRESULT TabEventsFunnel::OnMoved(HWND tab_handle, int window_id, int from_index,
+ int to_index) {
+ // For tab moves, the args are an array of two values, the tab id as an int
+ // and then a dictionary with window id, from and to indexes.
+ ListValue tab_moved_args;
+ tab_moved_args.Append(Value::CreateIntegerValue(
+ reinterpret_cast<int>(tab_handle)));
+ DictionaryValue* dict = new DictionaryValue;
+ dict->SetInteger(keys::kWindowIdKey, window_id);
+ dict->SetInteger(keys::kFromIndexKey, from_index);
+ dict->SetInteger(keys::kFromIndexKey, to_index);
+ tab_moved_args.Append(dict);
+ return SendEvent(ext_event_names::kOnTabMoved, tab_moved_args);
+}
+
+HRESULT TabEventsFunnel::OnRemoved(HWND tab_handle) {
+ scoped_ptr<Value> args(Value::CreateIntegerValue(
+ reinterpret_cast<int>(tab_handle)));
+ return SendEvent(ext_event_names::kOnTabRemoved, *args.get());
+}
+
+HRESULT TabEventsFunnel::OnSelectionChanged(HWND tab_handle, int window_id) {
+ // For tab selection changes, the args are an array of two values, the tab id
+ // as an int and then a dictionary with only the window id in it.
+ ListValue tab_selection_changed_args;
+ tab_selection_changed_args.Append(Value::CreateIntegerValue(
+ reinterpret_cast<int>(tab_handle)));
+ DictionaryValue* dict = new DictionaryValue;
+ dict->SetInteger(keys::kWindowIdKey, window_id);
+ tab_selection_changed_args.Append(dict);
+ return SendEvent(ext_event_names::kOnTabSelectionChanged,
+ tab_selection_changed_args);
+}
+
+HRESULT TabEventsFunnel::OnUpdated(HWND tab_handle, BSTR url,
+ READYSTATE ready_state) {
+ // For tab updates, the args are an array of two values, the tab id as an int
+ // and then a dictionary with an optional url field as well as a mandatory
+ // status string value.
+ ListValue tab_update_args;
+ tab_update_args.Append(Value::CreateIntegerValue(
+ reinterpret_cast<int>(tab_handle)));
+ DictionaryValue* dict = new DictionaryValue;
+ if (url != NULL)
+ dict->SetString(keys::kUrlKey, url);
+ dict->SetString(keys::kStatusKey, (ready_state == READYSTATE_COMPLETE) ?
+ keys::kStatusValueComplete : keys::kStatusValueLoading);
+ tab_update_args.Append(dict);
+ return SendEvent(ext_event_names::kOnTabUpdated, tab_update_args);
+}
+
+HRESULT TabEventsFunnel::OnTabUnmapped(HWND tab_handle, int tab_id) {
+ ListValue tab_unmapped_args;
+ tab_unmapped_args.Append(Value::CreateIntegerValue(
+ reinterpret_cast<int>(tab_handle)));
+ tab_unmapped_args.Append(Value::CreateIntegerValue(tab_id));
+ return SendEvent(ceee_event_names::kCeeeOnTabUnmapped, tab_unmapped_args);
+}
diff --git a/ceee/ie/plugin/bho/tab_events_funnel.h b/ceee/ie/plugin/bho/tab_events_funnel.h
new file mode 100644
index 0000000..7a9a2c4
--- /dev/null
+++ b/ceee/ie/plugin/bho/tab_events_funnel.h
@@ -0,0 +1,61 @@
+// 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.
+//
+// Funnel of Chrome Extension Tab Events.
+
+#ifndef CEEE_IE_PLUGIN_BHO_TAB_EVENTS_FUNNEL_H_
+#define CEEE_IE_PLUGIN_BHO_TAB_EVENTS_FUNNEL_H_
+
+#include <ocidl.h> // for READYSTATE
+
+#include "ceee/ie/plugin/bho/events_funnel.h"
+
+// Implements a set of methods to send tab related events to the Broker.
+class TabEventsFunnel : public EventsFunnel {
+ public:
+ TabEventsFunnel() : EventsFunnel(true) {}
+
+ // Sends the tabs.onMoved event to the Broker.
+ // @param tab_handle The HWND of the tab that moved.
+ // @param window_id The identifier of the window containing the moving tab.
+ // @param from_index The index from which the tab moved away.
+ // @param to_index The index where the tab moved to.
+ virtual HRESULT OnMoved(HWND tab_handle, int window_id,
+ int from_index, int to_index);
+
+ // Sends the tabs.onRemoved event to the Broker.
+ // @param tab_handle The identifier of the tab that was removed.
+ virtual HRESULT OnRemoved(HWND tab_handle);
+
+ // Sends the tabs.onSelectionChanged event to the Broker.
+ // @param tab_handle The HWND of the tab was selected.
+ // @param window_id The identifier of the window containing the selected tab.
+ virtual HRESULT OnSelectionChanged(HWND tab_handle, int window_id);
+
+ // Sends the tabs.onCreated :b+event to the Broker.
+ // @param tab_handle The HWND of the tab that was created.
+ // @param url The current URL of the page.
+ // @param completed If true, the status of the page is completed, otherwise,
+ // it is any othe other status values.
+ virtual HRESULT OnCreated(HWND tab_handle, BSTR url, bool completed);
+
+ // Sends the tabs.onUpdated event to the Broker.
+ // @param tab_handle The HWND of the tab that was updated.
+ // @param url The [optional] url where the tab was navigated to.
+ // @param ready_state The ready state of the tab.
+ virtual HRESULT OnUpdated(HWND tab_handle, BSTR url,
+ READYSTATE ready_state);
+
+ // Sends the private message to unmap a tab to its BHO. This is the last
+ // message a BHO should send, as its tab_id will no longer be mapped afterward
+ // and will assert if used.
+ // @param tab_handle The HWND of the tab to unmap.
+ // @param tab_id The id of the tab to unmap.
+ virtual HRESULT OnTabUnmapped(HWND tab_handle, int tab_id);
+
+ private:
+ DISALLOW_COPY_AND_ASSIGN(TabEventsFunnel);
+};
+
+#endif // CEEE_IE_PLUGIN_BHO_TAB_EVENTS_FUNNEL_H_
diff --git a/ceee/ie/plugin/bho/tab_events_funnel_unittest.cc b/ceee/ie/plugin/bho/tab_events_funnel_unittest.cc
new file mode 100644
index 0000000..dc2726d
--- /dev/null
+++ b/ceee/ie/plugin/bho/tab_events_funnel_unittest.cc
@@ -0,0 +1,141 @@
+// 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.
+//
+// Unit tests for TabEventsFunnel.
+
+#include <atlcomcli.h>
+
+#include "base/scoped_ptr.h"
+#include "base/values.h"
+#include "ceee/ie/plugin/bho/tab_events_funnel.h"
+#include "chrome/browser/extensions/extension_event_names.h"
+#include "chrome/browser/extensions/extension_tabs_module_constants.h"
+#include "gmock/gmock.h"
+#include "gtest/gtest.h"
+
+
+namespace ext_event_names = extension_event_names;
+namespace keys = extension_tabs_module_constants;
+
+namespace {
+
+using testing::Return;
+using testing::StrEq;
+
+MATCHER_P(ValuesEqual, value, "") {
+ return arg.Equals(value);
+}
+
+class TestTabEventsFunnel : public TabEventsFunnel {
+ public:
+ MOCK_METHOD2(SendEvent, HRESULT(const char*, const Value&));
+};
+
+TEST(TabEventsFunnelTest, OnTabCreated) {
+ TestTabEventsFunnel tab_events_funnel;
+ int tab_id = 42;
+ HWND tab_handle = reinterpret_cast<HWND>(tab_id);
+ std::string url("http://www.google.com");
+ std::string status(keys::kStatusValueComplete);
+
+ DictionaryValue dict;
+ dict.SetInteger(keys::kIdKey, tab_id);
+ dict.SetString(keys::kUrlKey, url);
+ dict.SetString(keys::kStatusKey, status);
+
+ EXPECT_CALL(tab_events_funnel, SendEvent(
+ StrEq(ext_event_names::kOnTabCreated), ValuesEqual(&dict))).
+ WillOnce(Return(S_OK));
+ EXPECT_HRESULT_SUCCEEDED(tab_events_funnel.OnCreated(tab_handle,
+ CComBSTR(url.c_str()),
+ true));
+}
+
+TEST(TabEventsFunnelTest, OnTabMoved) {
+ TestTabEventsFunnel tab_events_funnel;
+
+ int tab_id = 42;
+ HWND tab_handle = reinterpret_cast<HWND>(tab_id);
+ int window_id = 24;
+ int from_index = 12;
+ int to_index = 21;
+
+ ListValue tab_moved_args;
+ tab_moved_args.Append(Value::CreateIntegerValue(tab_id));
+
+ DictionaryValue* dict = new DictionaryValue;
+ dict->SetInteger(keys::kWindowIdKey, window_id);
+ dict->SetInteger(keys::kFromIndexKey, from_index);
+ dict->SetInteger(keys::kFromIndexKey, to_index);
+ tab_moved_args.Append(dict);
+
+ EXPECT_CALL(tab_events_funnel, SendEvent(StrEq(ext_event_names::kOnTabMoved),
+ ValuesEqual(&tab_moved_args))).WillOnce(Return(S_OK));
+ EXPECT_HRESULT_SUCCEEDED(tab_events_funnel.OnMoved(tab_handle, window_id,
+ from_index, to_index));
+}
+
+TEST(TabEventsFunnelTest, OnTabRemoved) {
+ TestTabEventsFunnel tab_events_funnel;
+
+ int tab_id = 42;
+ HWND tab_handle = reinterpret_cast<HWND>(tab_id);
+ scoped_ptr<Value> args(Value::CreateIntegerValue(tab_id));
+
+ EXPECT_CALL(tab_events_funnel, SendEvent(
+ StrEq(ext_event_names::kOnTabRemoved), ValuesEqual(args.get()))).
+ WillOnce(Return(S_OK));
+ EXPECT_HRESULT_SUCCEEDED(tab_events_funnel.OnRemoved(tab_handle));
+}
+
+TEST(TabEventsFunnelTest, OnTabSelectionChanged) {
+ TestTabEventsFunnel tab_events_funnel;
+
+ int tab_id = 42;
+ HWND tab_handle = reinterpret_cast<HWND>(tab_id);
+ int window_id = 24;
+
+ ListValue args;
+ args.Append(Value::CreateIntegerValue(tab_id));
+ DictionaryValue* dict = new DictionaryValue;
+ dict->SetInteger(keys::kWindowIdKey, window_id);
+ args.Append(dict);
+
+ EXPECT_CALL(tab_events_funnel, SendEvent(
+ StrEq(ext_event_names::kOnTabSelectionChanged), ValuesEqual(&args))).
+ WillOnce(Return(S_OK));
+ EXPECT_HRESULT_SUCCEEDED(tab_events_funnel.OnSelectionChanged(tab_handle,
+ window_id));
+}
+
+TEST(TabEventsFunnelTest, OnTabUpdated) {
+ TestTabEventsFunnel tab_events_funnel;
+
+ int tab_id = 24;
+ HWND tab_handle = reinterpret_cast<HWND>(tab_id);
+ READYSTATE ready_state = READYSTATE_INTERACTIVE;
+
+ ListValue args;
+ args.Append(Value::CreateIntegerValue(tab_id));
+ DictionaryValue* dict = new DictionaryValue;
+ dict->SetString(keys::kStatusKey, keys::kStatusValueLoading);
+ args.Append(dict);
+
+ // Without a URL.
+ EXPECT_CALL(tab_events_funnel, SendEvent(
+ StrEq(ext_event_names::kOnTabUpdated), ValuesEqual(&args))).
+ WillOnce(Return(S_OK));
+ EXPECT_HRESULT_SUCCEEDED(tab_events_funnel.OnUpdated(tab_handle, NULL,
+ ready_state));
+ // With a URL.
+ CComBSTR url(L"http://imbored.com");
+ dict->SetString(keys::kUrlKey, url.m_str);
+ EXPECT_CALL(tab_events_funnel, SendEvent(
+ StrEq(ext_event_names::kOnTabUpdated), ValuesEqual(&args))).
+ WillOnce(Return(S_OK));
+ EXPECT_HRESULT_SUCCEEDED(tab_events_funnel.OnUpdated(tab_handle, url,
+ ready_state));
+}
+
+} // namespace
diff --git a/ceee/ie/plugin/bho/tab_window_manager.cc b/ceee/ie/plugin/bho/tab_window_manager.cc
new file mode 100644
index 0000000..c557436
--- /dev/null
+++ b/ceee/ie/plugin/bho/tab_window_manager.cc
@@ -0,0 +1,244 @@
+// 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/bho/tab_window_manager.h"
+
+#include "base/logging.h"
+#include "ceee/common/com_utils.h"
+#include "ceee/common/windows_constants.h"
+#include "ceee/common/window_utils.h"
+#include "ceee/ie/common/ie_tab_interfaces.h"
+
+namespace {
+
+// Adapter for tab-window interfaces on IE7/IE8.
+template <class IeTabWindow>
+class TabWindowAdapter : public TabWindow {
+ public:
+ explicit TabWindowAdapter(IeTabWindow* tab_window)
+ : tab_window_(tab_window) {
+ }
+
+ STDMETHOD(GetBrowser)(IDispatch** browser) {
+ DCHECK(tab_window_ != NULL);
+ DCHECK(browser != NULL);
+ DCHECK(*browser == NULL);
+ return tab_window_->GetBrowser(browser);
+ }
+
+ STDMETHOD(GetID)(long* id) {
+ DCHECK(tab_window_ != NULL);
+ DCHECK(id != NULL);
+ return tab_window_->GetID(id);
+ }
+
+ STDMETHOD(Close)() {
+ DCHECK(tab_window_ != NULL);
+ return tab_window_->Close();
+ }
+
+ private:
+ CComPtr<IeTabWindow> tab_window_;
+ DISALLOW_COPY_AND_ASSIGN(TabWindowAdapter);
+};
+
+// Adapter for tab-window-manager interfaces on IE7/IE8.
+template <class IeTabWindowManager, class IeTabWindow>
+class TabWindowManagerAdapter : public TabWindowManager {
+ public:
+ explicit TabWindowManagerAdapter(IeTabWindowManager* manager)
+ : manager_(manager) {
+ }
+
+ STDMETHOD(IndexFromHWND)(HWND window, long* index) {
+ DCHECK(manager_ != NULL);
+ DCHECK(index != NULL);
+ return manager_->IndexFromHWND(window, index);
+ }
+
+ STDMETHOD(SelectTab)(long index) {
+ DCHECK(manager_ != NULL);
+ return manager_->SelectTab(index);
+ }
+
+ STDMETHOD(GetCount)(long* count) {
+ DCHECK(manager_ != NULL);
+ DCHECK(count != NULL);
+ return manager_->GetCount(count);
+ }
+
+ STDMETHOD(GetItemWrapper)(long index, scoped_ptr<TabWindow>* tab_window) {
+ DCHECK(manager_ != NULL);
+ DCHECK(tab_window != NULL);
+
+ CComPtr<IUnknown> tab_unk;
+ HRESULT hr = E_FAIL;
+ hr = manager_->GetItem(index, &tab_unk);
+ DCHECK(SUCCEEDED(hr)) << "GetItem failed for index: " << index << ". " <<
+ com::LogHr(hr);
+ if (FAILED(hr))
+ return hr;
+
+ CComPtr<IeTabWindow> ie_tab_window;
+ hr = tab_unk.QueryInterface(&ie_tab_window);
+ DCHECK(SUCCEEDED(hr)) << "QI failed IeTabWindow. " << com::LogHr(hr);
+ if (FAILED(hr))
+ return hr;
+
+ tab_window->reset(new TabWindowAdapter<IeTabWindow>(ie_tab_window));
+ return S_OK;
+ }
+
+ STDMETHOD(RepositionTab)(long moving_id, long dest_id, int unused) {
+ DCHECK(manager_ != NULL);
+ return manager_->RepositionTab(moving_id, dest_id, unused);
+ }
+
+ STDMETHOD(CloseAllTabs)() {
+ DCHECK(manager_ != NULL);
+ return manager_->CloseAllTabs();
+ }
+
+ private:
+ CComPtr<IeTabWindowManager> manager_;
+ DISALLOW_COPY_AND_ASSIGN(TabWindowManagerAdapter);
+};
+
+typedef TabWindowManagerAdapter<ITabWindowManagerIe9, ITabWindowIe9>
+ TabWindowManagerAdapter9;
+typedef TabWindowManagerAdapter<ITabWindowManagerIe8, ITabWindowIe8_1>
+ TabWindowManagerAdapter8_1;
+typedef TabWindowManagerAdapter<ITabWindowManagerIe8, ITabWindowIe8>
+ TabWindowManagerAdapter8;
+typedef TabWindowManagerAdapter<ITabWindowManagerIe7, ITabWindowIe7>
+ TabWindowManagerAdapter7;
+
+// Faked tab-window class for IE6.
+class TabWindow6 : public TabWindow {
+ public:
+ explicit TabWindow6(TabWindowManager *manager) : manager_(manager) {}
+
+ STDMETHOD(GetBrowser)(IDispatch** browser) {
+ // Currently nobody calls this method.
+ NOTREACHED();
+ return E_NOTIMPL;
+ }
+
+ STDMETHOD(GetID)(long* id) {
+ DCHECK(id != NULL);
+ *id = 0;
+ return S_OK;
+ }
+
+ STDMETHOD(Close)() {
+ DCHECK(manager_ != NULL);
+ // IE6 has only one tab for each frame window.
+ // So closing one tab means closing all the tabs.
+ return manager_->CloseAllTabs();
+ }
+
+ private:
+ TabWindowManager* manager_;
+ DISALLOW_COPY_AND_ASSIGN(TabWindow6);
+};
+
+// Faked tab-window-manager class for IE6.
+class TabWindowManager6 : public TabWindowManager {
+ public:
+ explicit TabWindowManager6(HWND frame_window) : frame_window_(frame_window) {}
+
+ STDMETHOD(IndexFromHWND)(HWND window, long* index) {
+ DCHECK(window != NULL);
+ DCHECK(index != NULL);
+ *index = 0;
+ return S_OK;
+ }
+
+ STDMETHOD(SelectTab)(long index) {
+ DCHECK_EQ(0, index);
+ return S_OK;
+ }
+
+ STDMETHOD(GetCount)(long* count) {
+ DCHECK(count != NULL);
+ *count = 1;
+ return S_OK;
+ }
+
+ STDMETHOD(GetItemWrapper)(long index, scoped_ptr<TabWindow>* tab_window) {
+ DCHECK(tab_window != NULL);
+ DCHECK_EQ(0, index);
+ tab_window->reset(new TabWindow6(this));
+ return S_OK;
+ }
+
+ STDMETHOD(RepositionTab)(long moving_id, long dest_id, int unused) {
+ DCHECK_EQ(0, moving_id);
+ DCHECK_EQ(0, dest_id);
+ return S_OK;
+ }
+
+ STDMETHOD(CloseAllTabs)() {
+ DCHECK(IsWindow(frame_window_));
+ ::PostMessage(frame_window_, WM_CLOSE, 0, 0);
+ return S_OK;
+ }
+ private:
+ HWND frame_window_;
+};
+
+} // anonymous namespace
+
+HRESULT CreateTabWindowManager(HWND frame_window,
+ scoped_ptr<TabWindowManager>* manager) {
+ CComPtr<IUnknown> manager_unknown;
+ HRESULT hr = ie_tab_interfaces::TabWindowManagerFromFrame(
+ frame_window,
+ __uuidof(IUnknown),
+ reinterpret_cast<void**>(&manager_unknown));
+ if (SUCCEEDED(hr)) {
+ DCHECK(manager_unknown != NULL);
+ CComQIPtr<ITabWindowManagerIe9> manager_ie9(manager_unknown);
+ if (manager_ie9 != NULL) {
+ manager->reset(new TabWindowManagerAdapter9(manager_ie9));
+ return S_OK;
+ }
+
+ CComQIPtr<ITabWindowManagerIe8> manager_ie8(manager_unknown);
+ if (manager_ie8 != NULL) {
+ // On IE8, there was a version change that introduced a new version
+ // of the ITabWindow interface even though the ITabWindowManager didn't
+ // change. So we must find which one before we make up our mind.
+ CComPtr<IUnknown> tab_window_punk;
+ hr = manager_ie8->GetItem(0, &tab_window_punk);
+ DCHECK(SUCCEEDED(hr) && tab_window_punk != NULL) << com::LogHr(hr);
+ CComQIPtr<ITabWindowIe8> tab_window8(tab_window_punk);
+ if (tab_window8 != NULL) {
+ manager->reset(new TabWindowManagerAdapter8(manager_ie8));
+ return S_OK;
+ }
+ CComQIPtr<ITabWindowIe8_1> tab_window8_1(tab_window_punk);
+ if (tab_window8_1 != NULL) {
+ manager->reset(new TabWindowManagerAdapter8_1(manager_ie8));
+ return S_OK;
+ }
+ NOTREACHED() << "Found an ITabWindow Punk that is not known by us!!!";
+ return E_UNEXPECTED;
+ }
+
+ CComQIPtr<ITabWindowManagerIe7> manager_ie7(manager_unknown);
+ if (manager_ie7 != NULL) {
+ manager->reset(new TabWindowManagerAdapter7(manager_ie7));
+ return S_OK;
+ }
+
+ // Maybe future IE would have another interface. Consider it as IE6 anyway.
+ NOTREACHED();
+ }
+
+ LOG(WARNING) <<
+ "Could not create a sensible brower interface, defaulting to IE6";
+ manager->reset(new TabWindowManager6(frame_window));
+ return S_OK;
+}
diff --git a/ceee/ie/plugin/bho/tab_window_manager.h b/ceee/ie/plugin/bho/tab_window_manager.h
new file mode 100644
index 0000000..4f1ef2c
--- /dev/null
+++ b/ceee/ie/plugin/bho/tab_window_manager.h
@@ -0,0 +1,39 @@
+// 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_BHO_TAB_WINDOW_MANAGER_H_
+#define CEEE_IE_PLUGIN_BHO_TAB_WINDOW_MANAGER_H_
+
+#include <atlbase.h>
+
+#include "base/scoped_ptr.h"
+
+// A unified tab-window interface for IE6/IE7/IE8.
+class TabWindow {
+ public:
+ STDMETHOD(GetBrowser)(IDispatch** browser) = 0;
+ STDMETHOD(GetID)(long* id) = 0;
+ STDMETHOD(Close)() = 0;
+ virtual ~TabWindow() {}
+};
+
+// A unified tab-window-manager interface for IE6/IE7/IE8.
+class TabWindowManager {
+ public:
+ STDMETHOD(IndexFromHWND)(HWND window, long* index) = 0;
+ STDMETHOD(SelectTab)(long index) = 0;
+ STDMETHOD(GetCount)(long* count) = 0;
+ STDMETHOD(GetItemWrapper)(long index, scoped_ptr<TabWindow>* tab_window) = 0;
+ STDMETHOD(RepositionTab)(long moving_id, long dest_id, int unused) = 0;
+ STDMETHOD(CloseAllTabs)() = 0;
+ virtual ~TabWindowManager() {}
+};
+
+// Creates a TabWindowManager object for the specified IEFrame window.
+// @param frame_window The top-level frame window you wish to manage.
+// @param manager The created TabWindowManager object.
+HRESULT CreateTabWindowManager(HWND frame_window,
+ scoped_ptr<TabWindowManager>* manager);
+
+#endif // CEEE_IE_PLUGIN_BHO_TAB_WINDOW_MANAGER_H_
diff --git a/ceee/ie/plugin/bho/tool_band_visibility.cc b/ceee/ie/plugin/bho/tool_band_visibility.cc
new file mode 100644
index 0000000..8952457
--- /dev/null
+++ b/ceee/ie/plugin/bho/tool_band_visibility.cc
@@ -0,0 +1,179 @@
+// 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/bho/tool_band_visibility.h"
+
+#include "base/logging.h"
+#include "ceee/ie/common/ie_tab_interfaces.h"
+#include "ceee/ie/common/ceee_module_util.h"
+#include "ceee/ie/plugin/toolband/tool_band.h"
+
+// The ToolBandVisibility class allows us to recover from a number of
+// features of IE that can cause our toolband to become invisible
+// unexpectedly.
+
+// A brief discussion of toolband visibility in IE.
+//
+// See MS knowledge base article Q219427 for more detail.
+//
+// IE does some interesting tricks to cache toolband layout information. One
+// side effect of this is that sometimes it can get confused about whether a
+// toolband should be shown or not.
+//
+// In theory all that is needed to recover from this is a call to
+// IWebBrowser2::ShowBrowserBar. Unfortunately, there are some
+// gotchas.
+// It's not easy to tell when IE has refused to display your
+// toolband. The only way to be sure is to either call
+// ShowBrowserBar on every startup or wait for some reasonable
+// time to see if IE showed the toolband and then kick it if it
+// didn't.
+// In IE6, a single call to ShowBrowserBar is often not enough to
+// unhide a hidden toolband. It's sometimes necessary to call
+// ShowBrowserBar THREE times (show, then hide, then show) to get
+// things into a sane state.
+// TODO(cindylau@chromium.org): In IE6, just calling ShowBrowserBar
+// will usually cause IE to scrunch our toolband up at the end of
+// a line instead of giving it its own line. We can combat this
+// by requesting to be shown on our own line, but if we do that
+// in all cases we'll anger users who WANT our toolband to share
+// a line with others.
+// Some other toolbands (notably SnagIt versions 6 and 7) will
+// cause toolbands to get hidden when opening a new tab in IE7.
+// Visibility must be checked on every new tab and window to be
+// sure.
+// Calls to ShowBrowserBar are slow and we should avoid them
+// whenever possible.
+// Calls to ShowBrowserBar should be made from the same UI thread
+// responsible for the browser object we use to unhide the
+// toolband. Failure to do this can cause the toolband to
+// believe it belongs to a different thread than it does.
+// TODO(cindylau@chromium.org): IE tracks layout information in the
+// registry. When toolbands are added or removed the installing
+// toolband MUST clear the registry (badness, including possible
+// crashes in IE will result if not). When this layout
+// information is cleared all third party toolbands are hidden.
+
+// This code attempts to address all of these issues.
+// The core is the VisibilityUtil class.
+// When VisibilityUtil::CheckVisibility is called it checks for common
+// indicators that a toolband is hidden. If it sees a smoking gun
+// it unhides the toolband. Otherwise it creates a notification window and
+// a timer. If a toolband doesn't report itself as active for a particular
+// browser window before the timer fires it unhides the toolband.
+
+namespace {
+const int kVisibilityTimerId = 1;
+const DWORD kVisibilityCheckDelay = 2000; // 2 seconds.
+} // anonymous namespace
+
+std::set<IUnknown*> ToolBandVisibility::visibility_set_;
+CComAutoCriticalSection ToolBandVisibility::visibility_set_crit_;
+
+ToolBandVisibility::ToolBandVisibility()
+ : web_browser_(NULL) {
+}
+
+ToolBandVisibility::~ToolBandVisibility() {
+ DCHECK(m_hWnd == NULL);
+}
+
+void ToolBandVisibility::ReportToolBandVisible(IWebBrowser2* web_browser) {
+ DCHECK(web_browser);
+ CComQIPtr<IUnknown, &IID_IUnknown> browser_identity(web_browser);
+ DCHECK(browser_identity != NULL);
+ if (browser_identity == NULL)
+ return;
+ CComCritSecLock<CComAutoCriticalSection> lock(visibility_set_crit_);
+ visibility_set_.insert(browser_identity);
+}
+
+bool ToolBandVisibility::IsToolBandVisible(IWebBrowser2* web_browser) {
+ DCHECK(web_browser);
+ CComQIPtr<IUnknown, &IID_IUnknown> browser_identity(web_browser);
+ DCHECK(browser_identity != NULL);
+ if (browser_identity == NULL)
+ return false;
+ CComCritSecLock<CComAutoCriticalSection> lock(visibility_set_crit_);
+ return visibility_set_.count(browser_identity) != 0;
+}
+
+void ToolBandVisibility::ClearCachedVisibility(IWebBrowser2* web_browser) {
+ CComCritSecLock<CComAutoCriticalSection> lock(visibility_set_crit_);
+ if (web_browser) {
+ CComQIPtr<IUnknown, &IID_IUnknown> browser_identity(web_browser);
+ DCHECK(browser_identity != NULL);
+ if (browser_identity == NULL)
+ return;
+ visibility_set_.erase(browser_identity);
+ } else {
+ visibility_set_.clear();
+ }
+}
+
+void ToolBandVisibility::CheckToolBandVisibility(IWebBrowser2* web_browser) {
+ DCHECK(web_browser);
+ web_browser_ = web_browser;
+
+ if (!ceee_module_util::GetOptionToolbandIsHidden() &&
+ CreateNotificationWindow()) {
+ SetWindowTimer(kVisibilityTimerId, kVisibilityCheckDelay);
+ }
+}
+
+void ToolBandVisibility::TearDown() {
+ if (web_browser_ != NULL) {
+ ClearCachedVisibility(web_browser_);
+ }
+ if (m_hWnd != NULL) {
+ CloseNotificationWindow();
+ }
+}
+
+bool ToolBandVisibility::CreateNotificationWindow() {
+ return Create(HWND_MESSAGE) != NULL;
+}
+
+void ToolBandVisibility::CloseNotificationWindow() {
+ DestroyWindow();
+}
+
+void ToolBandVisibility::SetWindowTimer(UINT timer_id, UINT delay) {
+ SetTimer(timer_id, delay, NULL);
+}
+
+void ToolBandVisibility::KillWindowTimer(UINT timer_id) {
+ KillTimer(timer_id);
+}
+
+void ToolBandVisibility::OnTimer(UINT_PTR nIDEvent) {
+ DCHECK(nIDEvent == kVisibilityTimerId);
+ KillWindowTimer(nIDEvent);
+
+ if (!IsToolBandVisible(web_browser_)) {
+ UnhideToolBand();
+ }
+ ClearCachedVisibility(web_browser_);
+ CloseNotificationWindow();
+}
+
+void ToolBandVisibility::UnhideToolBand() {
+ // Ignore ShowDW calls that are triggered by our calls here to
+ // ShowBrowserBar.
+ ceee_module_util::SetIgnoreShowDWChanges(true);
+ CComVariant toolband_class(CLSID_ToolBand);
+ CComVariant show(false);
+ CComVariant empty;
+ show = true;
+ web_browser_->ShowBrowserBar(&toolband_class, &show, &empty);
+
+ // Force IE to ignore bad caching. This is a problem generally before IE7,
+ // and at least sometimes even in IE7 (bb1291042).
+ show = false;
+ web_browser_->ShowBrowserBar(&toolband_class, &show, &empty);
+ show = true;
+ web_browser_->ShowBrowserBar(&toolband_class, &show, &empty);
+
+ ceee_module_util::SetIgnoreShowDWChanges(false);
+}
diff --git a/ceee/ie/plugin/bho/tool_band_visibility.h b/ceee/ie/plugin/bho/tool_band_visibility.h
new file mode 100644
index 0000000..f6e5767
--- /dev/null
+++ b/ceee/ie/plugin/bho/tool_band_visibility.h
@@ -0,0 +1,98 @@
+// 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_BHO_TOOL_BAND_VISIBILITY_H_
+#define CEEE_IE_PLUGIN_BHO_TOOL_BAND_VISIBILITY_H_
+
+#include <atlbase.h>
+#include <atlcrack.h>
+#include <atlwin.h>
+#include <mshtml.h> // Needed for exdisp.h
+#include <exdisp.h>
+#include <set>
+
+#include "base/basictypes.h"
+
+// The ToolBandVisibility class allows us to recover from a number of
+// features of IE that can cause our toolband to become invisible
+// unexpectedly. See the .cc file for more details.
+class ATL_NO_VTABLE ToolBandVisibility
+ : public CWindowImpl<ToolBandVisibility> {
+ public:
+ // Inform ToolBandVisibility that the toolband has been created for this
+ // browser instance.
+ static void ReportToolBandVisible(IWebBrowser2* web_browser);
+
+ BEGIN_MSG_MAP(ToolBandVisibility)
+ MSG_WM_CREATE(OnCreate)
+ MSG_WM_TIMER(OnTimer)
+ END_MSG_MAP()
+
+ protected:
+ // Returns true iff ReportToolBandVisible has been called for the given web
+ // browser.
+ static bool IsToolBandVisible(IWebBrowser2* web_browser);
+
+ // Cleans up the visibility set entry for the given browser that was stored
+ // when the browser called ReportToolBandVisible. If the pointer passed in is
+ // NULL, all items in the entire visibility set are deleted (this is useful
+ // for testing).
+ static void ClearCachedVisibility(IWebBrowser2* web_browser);
+
+ ToolBandVisibility();
+ virtual ~ToolBandVisibility();
+
+ // Checks toolband visibility, and forces the toolband to be shown if it
+ // isn't, and the user hasn't explicitly hidden the toolband.
+ void CheckToolBandVisibility(IWebBrowser2* web_browser);
+
+ // Cleans up toolband visibility data when the BHO is being torn down.
+ void TearDown();
+
+ // Set up the notification window used for processing ToolBandVisibilityWindow
+ // messages.
+ // Unfortunately, we need to create a notification window to handle a delayed
+ // check for the toolband. Other methods (like creating a new thread and
+ // sleeping) will not work.
+ // Returns true on success.
+ // Also serves as a unit testing seam.
+ virtual bool CreateNotificationWindow();
+
+ // Unit testing seam for destroying the window.
+ virtual void CloseNotificationWindow();
+
+ // Unit testing seam for setting the timer for the visibility window.
+ virtual void SetWindowTimer(UINT timer_id, UINT delay);
+
+ // Unit testing seam for killing the timer for the visibility window.
+ virtual void KillWindowTimer(UINT timer_id);
+
+ // @name Message handlers.
+ // @{
+ // The OnCreate handler is empty; subclasses can override it for more
+ // functionality.
+ virtual LRESULT OnCreate(LPCREATESTRUCT lpCreateStruct) {
+ return 0;
+ }
+ void OnTimer(UINT_PTR nIDEvent);
+ // @}
+
+ // Forces the toolband to be shown.
+ void UnhideToolBand();
+
+ // The web browser instance for which we are tracking toolband visibility.
+ CComPtr<IWebBrowser2> web_browser_;
+
+ private:
+ // The set of browser windows that have visible toolbands.
+ // The BHO for each browser window is responsible for cleaning up its
+ // own entry in this set when it's torn down; see ClearToolBandVisibility.
+ // This collection does not hold references to the objects it stores.
+ static std::set<IUnknown*> visibility_set_;
+ static CComAutoCriticalSection visibility_set_crit_;
+
+ DISALLOW_COPY_AND_ASSIGN(ToolBandVisibility);
+};
+
+#endif // CEEE_IE_PLUGIN_BHO_TOOL_BAND_VISIBILITY_H_
diff --git a/ceee/ie/plugin/bho/tool_band_visibility_unittest.cc b/ceee/ie/plugin/bho/tool_band_visibility_unittest.cc
new file mode 100644
index 0000000..0876c9d
--- /dev/null
+++ b/ceee/ie/plugin/bho/tool_band_visibility_unittest.cc
@@ -0,0 +1,179 @@
+// 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.
+//
+// Tests for ToolBandVisibility.
+
+#include "ceee/common/initializing_coclass.h"
+#include "ceee/ie/common/mock_ceee_module_util.h"
+#include "ceee/ie/plugin/bho/tool_band_visibility.h"
+#include "ceee/testing/utils/mock_com.h"
+#include "gtest/gtest.h"
+
+namespace {
+using testing::_;
+using testing::Return;
+using testing::StrictMock;
+
+class TestingToolBandVisibility : public ToolBandVisibility {
+ public:
+ TestingToolBandVisibility() {}
+ virtual ~TestingToolBandVisibility() {}
+
+ IWebBrowser2* GetWindowBrowser() const {
+ return web_browser_;
+ }
+
+ using ToolBandVisibility::IsToolBandVisible;
+ using ToolBandVisibility::ClearCachedVisibility;
+ using ToolBandVisibility::CheckToolBandVisibility;
+ using ToolBandVisibility::OnTimer;
+
+ MOCK_METHOD0(CreateNotificationWindow, bool());
+ MOCK_METHOD0(CloseNotificationWindow, void());
+ MOCK_METHOD2(SetWindowTimer, void(UINT, UINT));
+ MOCK_METHOD1(KillWindowTimer, void(UINT));
+};
+
+class MockBrowser
+ : public testing::MockIWebBrowser2,
+ public InitializingCoClass<MockBrowser> {
+ public:
+ HRESULT Initialize(MockBrowser** browser) {
+ *browser = this;
+ return S_OK;
+ }
+};
+
+class ToolBandVisibilityTest : public testing::Test {
+ public:
+ virtual void TearDown() {
+ TestingToolBandVisibility::ClearCachedVisibility(NULL);
+ }
+
+ void CreateMockBrowser(MockBrowser** browser, IWebBrowser2** browser_keeper) {
+ ASSERT_TRUE(browser && browser_keeper);
+ ASSERT_HRESULT_SUCCEEDED(
+ MockBrowser::CreateInitialized(browser, browser_keeper));
+ }
+
+ void ExpectCheckToolBandVisibilitySucceeded(
+ TestingToolBandVisibility* visibility) {
+ EXPECT_CALL(ceee_module_utils_, GetOptionToolbandIsHidden())
+ .WillOnce(Return(false));
+ EXPECT_CALL(*visibility, CreateNotificationWindow())
+ .WillOnce(Return(true));
+ EXPECT_CALL(*visibility, SetWindowTimer(1, 2000)).Times(1);
+ }
+
+ StrictMock<testing::MockCeeeModuleUtils> ceee_module_utils_;
+ TestingToolBandVisibility visibility;
+};
+
+TEST_F(ToolBandVisibilityTest, ReportToolBandVisibleSucceeds) {
+ MockBrowser* browser1;
+ MockBrowser* browser2;
+ MockBrowser* browser3;
+ CComPtr<IWebBrowser2> browser1_keeper, browser2_keeper, browser3_keeper;
+ CreateMockBrowser(&browser1, &browser1_keeper);
+ CreateMockBrowser(&browser2, &browser2_keeper);
+ CreateMockBrowser(&browser3, &browser3_keeper);
+
+ ASSERT_FALSE(TestingToolBandVisibility::IsToolBandVisible(browser1_keeper));
+ ASSERT_FALSE(TestingToolBandVisibility::IsToolBandVisible(browser2_keeper));
+ ASSERT_FALSE(TestingToolBandVisibility::IsToolBandVisible(browser3_keeper));
+
+ TestingToolBandVisibility::ReportToolBandVisible(browser2_keeper);
+ EXPECT_TRUE(TestingToolBandVisibility::IsToolBandVisible(browser2_keeper));
+ ASSERT_FALSE(TestingToolBandVisibility::IsToolBandVisible(browser1_keeper));
+ ASSERT_FALSE(TestingToolBandVisibility::IsToolBandVisible(browser3_keeper));
+
+ TestingToolBandVisibility::ReportToolBandVisible(browser3_keeper);
+ TestingToolBandVisibility::ClearCachedVisibility(browser2_keeper);
+
+ EXPECT_FALSE(TestingToolBandVisibility::IsToolBandVisible(browser1_keeper));
+ EXPECT_FALSE(TestingToolBandVisibility::IsToolBandVisible(browser2_keeper));
+ EXPECT_TRUE(TestingToolBandVisibility::IsToolBandVisible(browser3_keeper));
+
+ // Clearing visibility for a browser that isn't visible is essentially a
+ // no-op.
+ TestingToolBandVisibility::ClearCachedVisibility(browser1_keeper);
+ EXPECT_FALSE(TestingToolBandVisibility::IsToolBandVisible(browser1_keeper));
+ ASSERT_FALSE(TestingToolBandVisibility::IsToolBandVisible(browser2_keeper));
+ ASSERT_TRUE(TestingToolBandVisibility::IsToolBandVisible(browser3_keeper));
+}
+
+TEST_F(ToolBandVisibilityTest, CheckToolBandVisibilityHiddenToolband) {
+ MockBrowser* browser;
+ CComPtr<IWebBrowser2> browser_keeper;
+ CreateMockBrowser(&browser, &browser_keeper);
+ TestingToolBandVisibility visibility;
+
+ EXPECT_CALL(ceee_module_utils_, GetOptionToolbandIsHidden())
+ .WillOnce(Return(true));
+ EXPECT_CALL(visibility, CreateNotificationWindow()).Times(0);
+ visibility.CheckToolBandVisibility(browser_keeper);
+ EXPECT_EQ(browser_keeper, visibility.GetWindowBrowser());
+}
+
+TEST_F(ToolBandVisibilityTest, CheckToolBandVisibilityCreateFailed) {
+ MockBrowser* browser;
+ CComPtr<IWebBrowser2> browser_keeper;
+ CreateMockBrowser(&browser, &browser_keeper);
+ TestingToolBandVisibility visibility;
+
+ EXPECT_CALL(ceee_module_utils_, GetOptionToolbandIsHidden())
+ .WillOnce(Return(false));
+ EXPECT_CALL(visibility, CreateNotificationWindow())
+ .WillOnce(Return(false));
+ EXPECT_CALL(visibility, SetWindowTimer(_, _)).Times(0);
+ visibility.CheckToolBandVisibility(browser_keeper);
+}
+
+TEST_F(ToolBandVisibilityTest, CheckToolBandVisibilitySucceeded) {
+ MockBrowser* browser;
+ CComPtr<IWebBrowser2> browser_keeper;
+ CreateMockBrowser(&browser, &browser_keeper);
+ TestingToolBandVisibility visibility;
+
+ ExpectCheckToolBandVisibilitySucceeded(&visibility);
+ visibility.CheckToolBandVisibility(browser_keeper);
+}
+
+TEST_F(ToolBandVisibilityTest, OnTimerVisibleToolBand) {
+ MockBrowser* browser;
+ CComPtr<IWebBrowser2> browser_keeper;
+ CreateMockBrowser(&browser, &browser_keeper);
+ TestingToolBandVisibility visibility;
+
+ TestingToolBandVisibility::ReportToolBandVisible(browser_keeper);
+ EXPECT_TRUE(TestingToolBandVisibility::IsToolBandVisible(browser_keeper));
+
+ ExpectCheckToolBandVisibilitySucceeded(&visibility);
+ visibility.CheckToolBandVisibility(browser_keeper);
+
+ EXPECT_CALL(visibility, KillWindowTimer(1)).Times(1);
+ EXPECT_CALL(visibility, CloseNotificationWindow()).Times(1);
+ visibility.OnTimer(1);
+
+ EXPECT_FALSE(TestingToolBandVisibility::IsToolBandVisible(browser_keeper));
+}
+
+TEST_F(ToolBandVisibilityTest, OnTimerInvisibleToolBand) {
+ MockBrowser* browser;
+ CComPtr<IWebBrowser2> browser_keeper;
+ CreateMockBrowser(&browser, &browser_keeper);
+ TestingToolBandVisibility visibility;
+
+ ExpectCheckToolBandVisibilitySucceeded(&visibility);
+ visibility.CheckToolBandVisibility(browser_keeper);
+
+ EXPECT_CALL(visibility, KillWindowTimer(1)).Times(1);
+ EXPECT_CALL(ceee_module_utils_, SetIgnoreShowDWChanges(true)).Times(1);
+ EXPECT_CALL(*browser, ShowBrowserBar(_, _, _)).Times(3);
+ EXPECT_CALL(ceee_module_utils_, SetIgnoreShowDWChanges(false)).Times(1);
+ EXPECT_CALL(visibility, CloseNotificationWindow()).Times(1);
+ visibility.OnTimer(1);
+}
+
+} // namespace
diff --git a/ceee/ie/plugin/bho/web_browser_events_source.h b/ceee/ie/plugin/bho/web_browser_events_source.h
new file mode 100644
index 0000000..8cdec9c
--- /dev/null
+++ b/ceee/ie/plugin/bho/web_browser_events_source.h
@@ -0,0 +1,34 @@
+// 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.
+//
+// Interface of WebBrowser events source.
+#ifndef CEEE_IE_PLUGIN_BHO_WEB_BROWSER_EVENTS_SOURCE_H_
+#define CEEE_IE_PLUGIN_BHO_WEB_BROWSER_EVENTS_SOURCE_H_
+
+#include <exdisp.h>
+
+// WebBrowserEventsSource defines the interface of a WebBrowser event publisher,
+// which is used to register/unregister event consumers and fire WebBrowser
+// events to them.
+class WebBrowserEventsSource {
+ public:
+ // The interface of WebBrowser event consumers.
+ class Sink {
+ public:
+ virtual ~Sink() {}
+ virtual void OnBeforeNavigate(IWebBrowser2* browser, BSTR url) {}
+ virtual void OnDocumentComplete(IWebBrowser2* browser, BSTR url) {}
+ virtual void OnNavigateComplete(IWebBrowser2* browser, BSTR url) {}
+ virtual void OnNavigateError(IWebBrowser2* browser, BSTR url,
+ long status_code) {}
+ virtual void OnNewWindow(BSTR url_context, BSTR url) {}
+ };
+
+ virtual ~WebBrowserEventsSource() {}
+
+ virtual void RegisterSink(Sink* sink) = 0;
+ virtual void UnregisterSink(Sink* sink) = 0;
+};
+
+#endif // CEEE_IE_PLUGIN_BHO_WEB_BROWSER_EVENTS_SOURCE_H_
diff --git a/ceee/ie/plugin/bho/web_progress_notifier.cc b/ceee/ie/plugin/bho/web_progress_notifier.cc
new file mode 100644
index 0000000..c32ac70
--- /dev/null
+++ b/ceee/ie/plugin/bho/web_progress_notifier.cc
@@ -0,0 +1,653 @@
+// 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.
+//
+// Web progress notifier implementation.
+#include "ceee/ie/plugin/bho/web_progress_notifier.h"
+
+#include "base/logging.h"
+#include "base/string_util.h"
+#include "ceee/common/com_utils.h"
+#include "ceee/ie/plugin/bho/dom_utils.h"
+
+namespace {
+
+// In milliseconds. It defines the "effective period" of user action. A user
+// action is considered as a possible cause of the next navigation if the
+// navigation happens in this period.
+// This is a number we feel confident of based on past experience.
+const int kUserActionTimeThresholdMs = 500;
+
+// String constants for the values of TransitionQualifier.
+const char kClientRedirect[] = "client_redirect";
+const char kServerRedirect[] = "server_redirect";
+const char kForwardBack[] = "forward_back";
+const char kRedirectMetaRefresh[] = "redirect_meta_refresh";
+const char kRedirectOnLoad[] = "redirect_onload";
+const char kRedirectJavaScript[] = "redirect_javascript";
+
+} // namespace
+
+WebProgressNotifier::WebProgressNotifier()
+ : web_browser_events_source_(NULL),
+ main_frame_info_(PageTransition::LINK, 0, kMainFrameId),
+ tab_handle_(NULL),
+ next_subframe_id_(1),
+ main_frame_document_complete_(true),
+ tracking_content_window_action_(false),
+ tracking_browser_ui_action_(false),
+ has_potential_javascript_redirect_(false),
+ cached_webrequest_notifier_(NULL),
+ webrequest_notifier_initialized_(false),
+ create_thread_id_(::GetCurrentThreadId()) {
+}
+
+WebProgressNotifier::~WebProgressNotifier() {
+ DCHECK(!webrequest_notifier_initialized_);
+ DCHECK(web_browser_events_source_ == NULL);
+ DCHECK(window_message_source_ == NULL);
+ DCHECK(main_browser_ == NULL);
+ DCHECK(travel_log_ == NULL);
+ DCHECK(tab_handle_ == NULL);
+}
+
+HRESULT WebProgressNotifier::Initialize(
+ WebBrowserEventsSource* web_browser_events_source,
+ HWND tab_window,
+ IWebBrowser2* main_browser) {
+ if (web_browser_events_source == NULL || tab_window == NULL ||
+ main_browser == NULL) {
+ return E_INVALIDARG;
+ }
+
+ tab_handle_ = reinterpret_cast<CeeeWindowHandle>(tab_window);
+ main_browser_ = main_browser;
+
+ CComQIPtr<IServiceProvider> service_provider(main_browser);
+ if (service_provider == NULL ||
+ FAILED(service_provider->QueryService(
+ SID_STravelLogCursor, IID_ITravelLogStg,
+ reinterpret_cast<void**>(&travel_log_))) ||
+ travel_log_ == NULL) {
+ TearDown();
+ return E_FAIL;
+ }
+
+ web_browser_events_source_ = web_browser_events_source;
+ web_browser_events_source_->RegisterSink(this);
+
+ window_message_source_.reset(CreateWindowMessageSource());
+ if (window_message_source_ == NULL) {
+ TearDown();
+ return E_FAIL;
+ }
+ window_message_source_->RegisterSink(this);
+
+ if (!webrequest_notifier()->RequestToStart())
+ NOTREACHED() << "Failed to start the WebRequestNotifier service.";
+ webrequest_notifier_initialized_ = true;
+ return S_OK;
+}
+
+void WebProgressNotifier::TearDown() {
+ if (webrequest_notifier_initialized_) {
+ webrequest_notifier()->RequestToStop();
+ webrequest_notifier_initialized_ = false;
+ }
+ if (web_browser_events_source_ != NULL) {
+ web_browser_events_source_->UnregisterSink(this);
+ web_browser_events_source_ = NULL;
+ }
+ if (window_message_source_ != NULL) {
+ window_message_source_->UnregisterSink(this);
+ window_message_source_->TearDown();
+ window_message_source_.reset(NULL);
+ }
+
+ main_browser_.Release();
+ travel_log_.Release();
+ tab_handle_ = NULL;
+}
+
+void WebProgressNotifier::OnBeforeNavigate(IWebBrowser2* browser, BSTR url) {
+ if (browser == NULL || url == NULL)
+ return;
+
+ if (FilterOutWebBrowserEvent(browser, FilteringInfo::BEFORE_NAVIGATE))
+ return;
+
+ FrameInfo* frame_info = NULL;
+ if (!GetFrameInfo(browser, &frame_info))
+ return;
+
+ // TODO(yzshen@google.com): add support for requestId.
+ HRESULT hr = webnavigation_events_funnel().OnBeforeNavigate(
+ tab_handle_, url, frame_info->frame_id, -1, base::Time::Now());
+ DCHECK(SUCCEEDED(hr))
+ << "Failed to fire the webNavigation.onBeforeNavigate event "
+ << com::LogHr(hr);
+
+ if (frame_info->IsMainFrame()) {
+ frame_info->ClearTransition();
+
+ // The order in which we set these transitions is **very important.**
+ // If there was no DocumentComplete, then there are two likely options:
+ // the transition was a JavaScript redirect, or the user navigated to a
+ // second page before the first was done loading. We initialize the
+ // transition to JavaScript redirect first. If there are other signals such
+ // as the user clicked/typed, we'll overwrite this value with the
+ // appropriate value.
+ if (!main_frame_document_complete_ ||
+ wcsncmp(url, L"javascript:", wcslen(L"javascript:")) == 0 ||
+ has_potential_javascript_redirect_) {
+ frame_info->SetTransition(PageTransition::LINK,
+ CLIENT_REDIRECT | REDIRECT_JAVASCRIPT);
+ }
+
+ // Override the transition if there is user action in the tab content window
+ // or browser UI.
+ if (IsPossibleUserActionInContentWindow()) {
+ frame_info->SetTransition(PageTransition::LINK, 0);
+ } else if (IsPossibleUserActionInBrowserUI()) {
+ frame_info->SetTransition(PageTransition::TYPED, 0);
+ }
+
+ // Override the transition if we find some signals that we are more
+ // confident about.
+ if (InOnLoadEvent(browser)) {
+ frame_info->SetTransition(PageTransition::LINK,
+ CLIENT_REDIRECT | REDIRECT_ONLOAD);
+ } else if (IsMetaRefresh(browser, url)) {
+ frame_info->SetTransition(PageTransition::LINK,
+ CLIENT_REDIRECT | REDIRECT_META_REFRESH);
+ }
+
+ // Assume that user actions don't have long-lasting effect: user actions
+ // before the current navigation may be the cause of this navigation; but
+ // they can not affect any subsequent navigation.
+ //
+ // Under this assumption, we don't need to remember previous user actions.
+ tracking_content_window_action_ = false;
+ tracking_browser_ui_action_ = false;
+ }
+}
+
+void WebProgressNotifier::OnDocumentComplete(IWebBrowser2* browser, BSTR url) {
+ if (browser == NULL || url == NULL)
+ return;
+
+ if (FilterOutWebBrowserEvent(browser, FilteringInfo::DOCUMENT_COMPLETE))
+ return;
+
+ FrameInfo* frame_info = NULL;
+ if (!GetFrameInfo(browser, &frame_info))
+ return;
+
+ if (frame_info->IsMainFrame()) {
+ main_frame_document_complete_ = true;
+
+ has_potential_javascript_redirect_ =
+ HasPotentialJavaScriptRedirect(browser);
+ }
+
+ HRESULT hr = webnavigation_events_funnel().OnCompleted(
+ tab_handle_, url, frame_info->frame_id, base::Time::Now());
+ DCHECK(SUCCEEDED(hr)) << "Failed to fire the webNavigation.onCompleted event "
+ << com::LogHr(hr);
+}
+
+void WebProgressNotifier::OnNavigateComplete(IWebBrowser2* browser, BSTR url) {
+ if (browser == NULL || url == NULL)
+ return;
+
+ if (FilterOutWebBrowserEvent(browser, FilteringInfo::NAVIGATE_COMPLETE)) {
+ filtering_info_.pending_navigate_complete_browser = browser;
+ filtering_info_.pending_navigate_complete_url = url;
+ filtering_info_.pending_navigate_complete_timestamp = base::Time::Now();
+ } else {
+ HandleNavigateComplete(browser, url, base::Time::Now());
+ }
+}
+
+void WebProgressNotifier::HandleNavigateComplete(
+ IWebBrowser2* browser,
+ BSTR url,
+ const base::Time& timestamp) {
+ // NOTE: For the first OnNavigateComplete event in a tab/window, this method
+ // may not be called at the moment when IE fires the event.
+ // As a result, be careful if you need to query the browser state in this
+ // method, because the state may have changed after IE fired the event.
+
+ FrameInfo* frame_info = NULL;
+ if (!GetFrameInfo(browser, &frame_info))
+ return;
+
+ if (frame_info->IsMainFrame()) {
+ main_frame_document_complete_ = false;
+
+ if (IsForwardBack(url)) {
+ frame_info->SetTransition(PageTransition::AUTO_BOOKMARK, FORWARD_BACK);
+ }
+ }
+
+ HRESULT hr = webnavigation_events_funnel().OnCommitted(
+ tab_handle_, url, frame_info->frame_id,
+ PageTransition::CoreTransitionString(frame_info->transition_type),
+ TransitionQualifiersString(frame_info->transition_qualifiers).c_str(),
+ timestamp);
+ DCHECK(SUCCEEDED(hr)) << "Failed to fire the webNavigation.onCommitted event "
+ << com::LogHr(hr);
+
+ if (frame_info->IsMainFrame())
+ subframe_map_.clear();
+}
+
+void WebProgressNotifier::OnNavigateError(IWebBrowser2* browser, BSTR url,
+ long status_code) {
+ if (browser == NULL || url == NULL)
+ return;
+
+ if (FilterOutWebBrowserEvent(browser, FilteringInfo::NAVIGATE_ERROR))
+ return;
+
+ FrameInfo* frame_info = NULL;
+ if (!GetFrameInfo(browser, &frame_info))
+ return;
+
+ HRESULT hr = webnavigation_events_funnel().OnErrorOccurred(
+ tab_handle_, url, frame_info->frame_id, CComBSTR(L""), base::Time::Now());
+ DCHECK(SUCCEEDED(hr))
+ << "Failed to fire the webNavigation.onErrorOccurred event "
+ << com::LogHr(hr);
+}
+
+void WebProgressNotifier::OnNewWindow(BSTR url_context, BSTR url) {
+ if (url_context == NULL || url == NULL)
+ return;
+
+ if (FilterOutWebBrowserEvent(NULL, FilteringInfo::NEW_WINDOW))
+ return;
+
+ HRESULT hr = webnavigation_events_funnel().OnBeforeRetarget(
+ tab_handle_, url_context, url, base::Time::Now());
+ DCHECK(SUCCEEDED(hr))
+ << "Failed to fire the webNavigation.onBeforeRetarget event "
+ << com::LogHr(hr);
+}
+
+void WebProgressNotifier::OnHandleMessage(
+ WindowMessageSource::MessageType type,
+ const MSG* message_info) {
+ DCHECK(create_thread_id_ == ::GetCurrentThreadId());
+
+ // This is called when a user input message is about to be handled by any
+ // window procedure on the current thread, we should not do anything expensive
+ // here that would degrade user experience.
+ switch (type) {
+ case WindowMessageSource::TAB_CONTENT_WINDOW: {
+ if (IsUserActionMessage(message_info->message)) {
+ tracking_content_window_action_ = true;
+ last_content_window_action_time_ = base::Time::Now();
+ }
+ break;
+ }
+ case WindowMessageSource::BROWSER_UI_SAME_THREAD: {
+ if (IsUserActionMessage(message_info->message)) {
+ tracking_browser_ui_action_ = true;
+ last_browser_ui_action_time_ = base::Time::Now();
+ }
+ break;
+ }
+ default: {
+ NOTREACHED();
+ break;
+ }
+ }
+}
+
+WindowMessageSource* WebProgressNotifier::CreateWindowMessageSource() {
+ scoped_ptr<WindowMessageSource> source(new WindowMessageSource());
+
+ return source->Initialize() ? source.release() : NULL;
+}
+
+std::string WebProgressNotifier::TransitionQualifiersString(
+ TransitionQualifiers qualifiers) {
+ std::string result;
+ for (unsigned int current_qualifier = FIRST_TRANSITION_QUALIFIER;
+ current_qualifier <= LAST_TRANSITION_QUALIFIER;
+ current_qualifier = current_qualifier << 1) {
+ if ((qualifiers & current_qualifier) != 0) {
+ if (!result.empty())
+ result.append("|");
+ switch (current_qualifier) {
+ case CLIENT_REDIRECT:
+ result.append(kClientRedirect);
+ break;
+ case SERVER_REDIRECT:
+ result.append(kServerRedirect);
+ break;
+ case FORWARD_BACK:
+ result.append(kForwardBack);
+ break;
+ case REDIRECT_META_REFRESH:
+ result.append(kRedirectMetaRefresh);
+ break;
+ case REDIRECT_ONLOAD:
+ result.append(kRedirectOnLoad);
+ break;
+ case REDIRECT_JAVASCRIPT:
+ result.append(kRedirectJavaScript);
+ break;
+ default:
+ NOTREACHED();
+ break;
+ }
+ }
+ }
+ return result;
+}
+
+bool WebProgressNotifier::GetFrameInfo(IWebBrowser2* browser,
+ FrameInfo** frame_info) {
+ DCHECK(browser != NULL && frame_info != NULL);
+
+ if (IsMainFrame(browser)) {
+ *frame_info = &main_frame_info_;
+ return true;
+ }
+
+ CComPtr<IUnknown> browser_identity;
+ HRESULT hr = browser->QueryInterface(&browser_identity);
+ DCHECK(SUCCEEDED(hr));
+ if (FAILED(hr))
+ return false;
+
+ SubframeMap::iterator iter = subframe_map_.find(browser_identity);
+ if (iter != subframe_map_.end()) {
+ *frame_info = &iter->second;
+ } else {
+ // PageTransition::MANUAL_SUBFRAME, as well as transition qualifiers for
+ // subframes, is not supported.
+ subframe_map_.insert(std::make_pair(browser_identity,
+ FrameInfo(PageTransition::AUTO_SUBFRAME,
+ 0, next_subframe_id_++)));
+ *frame_info = &subframe_map_[browser_identity];
+ }
+ return true;
+}
+
+bool WebProgressNotifier::GetDocument(IWebBrowser2* browser,
+ REFIID id,
+ void** document) {
+ DCHECK(browser != NULL && document != NULL);
+
+ CComPtr<IDispatch> document_disp;
+ if (FAILED(browser->get_Document(&document_disp)) || document_disp == NULL)
+ return false;
+ return SUCCEEDED(document_disp->QueryInterface(id, document)) &&
+ *document != NULL;
+}
+
+bool WebProgressNotifier::IsForwardBack(BSTR url) {
+ DWORD length = 0;
+ DWORD position = 0;
+
+ if (FAILED(travel_log_->GetCount(TLEF_RELATIVE_BACK | TLEF_RELATIVE_FORE |
+ TLEF_INCLUDE_UNINVOKEABLE,
+ &length))) {
+ length = -1;
+ } else {
+ length++; // Add 1 for the current entry.
+ }
+
+ if (FAILED(travel_log_->GetCount(TLEF_RELATIVE_FORE |
+ TLEF_INCLUDE_UNINVOKEABLE,
+ &position))) {
+ position = -1;
+ }
+
+ // Consider this is a forward/back navigation, if:
+ // (1) state of the forward/back list has been successfully retrieved, and
+ // (2) the length of the forward/back list is not changed, and
+ // (3) (a) the current position is not the newest entry of the
+ // forward/back list, or
+ // (b) we are not at the newest entry of the list before the current
+ // navigation and the URL of the newest entry is not changed by the
+ // current navigation.
+ bool is_forward_back =
+ length != -1 && previous_travel_log_info_.length != -1 &&
+ position != -1 && previous_travel_log_info_.position != -1 &&
+ length == previous_travel_log_info_.length &&
+ (position != 0 ||
+ (previous_travel_log_info_.position != 0 &&
+ previous_travel_log_info_.newest_url == url));
+
+ previous_travel_log_info_.length = length;
+ previous_travel_log_info_.position = position;
+ if (position == 0 && !is_forward_back)
+ previous_travel_log_info_.newest_url = url;
+
+ return is_forward_back;
+}
+
+bool WebProgressNotifier::InOnLoadEvent(IWebBrowser2* browser) {
+ DCHECK(browser != NULL);
+
+ CComPtr<IHTMLDocument2> document;
+ if (!GetDocument(browser, IID_IHTMLDocument2,
+ reinterpret_cast<void**>(&document))) {
+ return false;
+ }
+
+ CComPtr<IHTMLWindow2> window;
+ if (FAILED(document->get_parentWindow(&window)) || window == NULL)
+ return false;
+
+ CComPtr<IHTMLEventObj> event_obj;
+ if (FAILED(window->get_event(&event_obj)) || event_obj == NULL)
+ return false;
+
+ CComBSTR type;
+ if (FAILED(event_obj->get_type(&type)) || wcscmp(type, L"load") != 0)
+ return false;
+ else
+ return true;
+}
+
+bool WebProgressNotifier::IsMetaRefresh(IWebBrowser2* browser, BSTR url) {
+ DCHECK(browser != NULL && url != NULL);
+
+ CComPtr<IHTMLDocument3> document;
+ if (!GetDocument(browser, IID_IHTMLDocument3,
+ reinterpret_cast<void**>(&document))) {
+ return false;
+ }
+
+ std::wstring dest_url(url);
+ StringToLowerASCII(&dest_url);
+
+ static const wchar_t slash[] = { L'/' };
+ // IE can add/remove a slash to/from the URL specified in the meta refresh
+ // tag. No redirect occurs as a result of this URL change, so we just compare
+ // without slashes here.
+ TrimString(dest_url, slash, &dest_url);
+
+ CComPtr<IHTMLElementCollection> meta_elements;
+ long length = 0;
+ if (FAILED(DomUtils::GetElementsByTagName(document, CComBSTR(L"meta"),
+ &meta_elements, &length))) {
+ return false;
+ }
+
+ for (long index = 0; index < length; ++index) {
+ CComPtr<IHTMLMetaElement> meta_element;
+ if (FAILED(DomUtils::GetElementFromCollection(
+ meta_elements, index, IID_IHTMLMetaElement,
+ reinterpret_cast<void**>(&meta_element)))) {
+ continue;
+ }
+
+ CComBSTR http_equiv;
+ if (FAILED(meta_element->get_httpEquiv(&http_equiv)) ||
+ http_equiv == NULL || _wcsicmp(http_equiv, L"refresh") != 0) {
+ continue;
+ }
+
+ CComBSTR content_bstr;
+ if (FAILED(meta_element->get_content(&content_bstr)) ||
+ content_bstr == NULL)
+ continue;
+ std::wstring content(content_bstr);
+ StringToLowerASCII(&content);
+ size_t pos = content.find(L"url");
+ if (pos == std::wstring::npos)
+ continue;
+ pos = content.find(L"=", pos + 3);
+ if (pos == std::wstring::npos)
+ continue;
+
+ std::wstring content_url(content.begin() + pos + 1, content.end());
+ TrimWhitespace(content_url, TRIM_ALL, &content_url);
+ TrimString(content_url, slash, &content_url);
+
+ // It is possible that the meta tag specifies a relative URL.
+ if (!content_url.empty() && EndsWith(dest_url, content_url, true))
+ return true;
+ }
+ return false;
+}
+
+bool WebProgressNotifier::HasPotentialJavaScriptRedirect(
+ IWebBrowser2* browser) {
+ DCHECK(browser != NULL);
+
+ CComPtr<IHTMLDocument3> document;
+ if (!GetDocument(browser, IID_IHTMLDocument3,
+ reinterpret_cast<void**>(&document))) {
+ return false;
+ }
+
+ CComPtr<IHTMLElementCollection> script_elements;
+ long length = 0;
+ if (FAILED(DomUtils::GetElementsByTagName(document, CComBSTR(L"script"),
+ &script_elements, &length))) {
+ return false;
+ }
+
+ for (long index = 0; index < length; ++index) {
+ CComPtr<IHTMLScriptElement> script_element;
+ if (FAILED(DomUtils::GetElementFromCollection(
+ script_elements, index, IID_IHTMLScriptElement,
+ reinterpret_cast<void**>(&script_element)))) {
+ continue;
+ }
+
+ CComBSTR text;
+ if (FAILED(script_element->get_text(&text)) || text == NULL)
+ continue;
+
+ if (wcsstr(text, L"location.href") != NULL ||
+ wcsstr(text, L"location.replace") != NULL ||
+ wcsstr(text, L"location.assign") != NULL ||
+ wcsstr(text, L"location.reload") != NULL) {
+ return true;
+ }
+ }
+
+ return false;
+}
+
+bool WebProgressNotifier::IsPossibleUserActionInContentWindow() {
+ if (tracking_content_window_action_) {
+ base::TimeDelta delta = base::Time::Now() -
+ last_content_window_action_time_;
+ if (delta.InMilliseconds() < kUserActionTimeThresholdMs)
+ return true;
+ }
+
+ return false;
+}
+
+bool WebProgressNotifier::IsPossibleUserActionInBrowserUI() {
+ if (tracking_browser_ui_action_) {
+ base::TimeDelta delta = base::Time::Now() - last_browser_ui_action_time_;
+ if (delta.InMilliseconds() < kUserActionTimeThresholdMs)
+ return true;
+ }
+
+ // TODO(yzshen@google.com): The windows of the browser UI live in
+ // different threads or even processes, for example:
+ // 1) The menu bar, add-on toolbands, as well as the status bar at the bottom,
+ // live in the same thread as the tab content window.
+ // 2) In IE7, the browser frame lives in a different thread other than the one
+ // hosting the tab content window; in IE8, it lives in a different process.
+ // 3) Our extension UI (rendered by Chrome) lives in another process.
+ // Currently WebProgressNotifier only handles case (1). I need to find out a
+ // solution that can effectively handle case (2) and (3).
+ return false;
+}
+
+bool WebProgressNotifier::FilterOutWebBrowserEvent(IWebBrowser2* browser,
+ FilteringInfo::Event event) {
+ if (!IsMainFrame(browser)) {
+ if (filtering_info_.state == FilteringInfo::SUSPICIOUS_NAVIGATE_COMPLETE) {
+ filtering_info_.state = FilteringInfo::END;
+ HandleNavigateComplete(
+ filtering_info_.pending_navigate_complete_browser,
+ filtering_info_.pending_navigate_complete_url,
+ filtering_info_.pending_navigate_complete_timestamp);
+ }
+ } else {
+ switch (filtering_info_.state) {
+ case FilteringInfo::END: {
+ break;
+ }
+ case FilteringInfo::START: {
+ if (event == FilteringInfo::BEFORE_NAVIGATE)
+ filtering_info_.state = FilteringInfo::FIRST_BEFORE_NAVIGATE;
+
+ break;
+ }
+ case FilteringInfo::FIRST_BEFORE_NAVIGATE: {
+ if (event == FilteringInfo::BEFORE_NAVIGATE ||
+ event == FilteringInfo::NAVIGATE_COMPLETE) {
+ filtering_info_.state = FilteringInfo::END;
+ } else if (event == FilteringInfo::DOCUMENT_COMPLETE) {
+ filtering_info_.state = FilteringInfo::SUSPICIOUS_DOCUMENT_COMPLETE;
+ return true;
+ }
+
+ break;
+ }
+ case FilteringInfo::SUSPICIOUS_DOCUMENT_COMPLETE: {
+ if (event == FilteringInfo::BEFORE_NAVIGATE) {
+ filtering_info_.state = FilteringInfo::END;
+ } else if (event == FilteringInfo::NAVIGATE_COMPLETE) {
+ filtering_info_.state = FilteringInfo::SUSPICIOUS_NAVIGATE_COMPLETE;
+ return true;
+ }
+
+ break;
+ }
+ case FilteringInfo::SUSPICIOUS_NAVIGATE_COMPLETE: {
+ if (event == FilteringInfo::NAVIGATE_COMPLETE) {
+ filtering_info_.state = FilteringInfo::END;
+ // Ignore the pending OnNavigateComplete event.
+ } else {
+ filtering_info_.state = FilteringInfo::END;
+ HandleNavigateComplete(
+ filtering_info_.pending_navigate_complete_browser,
+ filtering_info_.pending_navigate_complete_url,
+ filtering_info_.pending_navigate_complete_timestamp);
+ }
+ break;
+ }
+ default: {
+ NOTREACHED() << "Unknown state type.";
+ break;
+ }
+ }
+ }
+ return false;
+}
diff --git a/ceee/ie/plugin/bho/web_progress_notifier.h b/ceee/ie/plugin/bho/web_progress_notifier.h
new file mode 100644
index 0000000..3c364c8
--- /dev/null
+++ b/ceee/ie/plugin/bho/web_progress_notifier.h
@@ -0,0 +1,366 @@
+// 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.
+//
+// Web progress notifier implementation.
+#ifndef CEEE_IE_PLUGIN_BHO_WEB_PROGRESS_NOTIFIER_H_
+#define CEEE_IE_PLUGIN_BHO_WEB_PROGRESS_NOTIFIER_H_
+
+#include <atlbase.h>
+#include <tlogstg.h>
+
+#include <map>
+#include <string>
+
+#include "base/scoped_ptr.h"
+#include "ceee/ie/plugin/bho/web_browser_events_source.h"
+#include "ceee/ie/plugin/bho/webnavigation_events_funnel.h"
+#include "ceee/ie/plugin/bho/webrequest_notifier.h"
+#include "ceee/ie/plugin/bho/window_message_source.h"
+#include "chrome/common/page_transition_types.h"
+
+// WebProgressNotifier sends to the Broker various Web progress events,
+// including Web page navigation events and HTTP request/response events.
+class WebProgressNotifier : public WebBrowserEventsSource::Sink,
+ public WindowMessageSource::Sink {
+ public:
+ WebProgressNotifier();
+ virtual ~WebProgressNotifier();
+
+ HRESULT Initialize(
+ WebBrowserEventsSource* web_browser_events_source,
+ HWND tab_window,
+ IWebBrowser2* main_browser);
+ void TearDown();
+
+ // @name WebBrowserEventsSource::Sink implementation
+ // @{
+ virtual void OnBeforeNavigate(IWebBrowser2* browser, BSTR url);
+ virtual void OnDocumentComplete(IWebBrowser2* browser, BSTR url);
+ virtual void OnNavigateComplete(IWebBrowser2* browser, BSTR url);
+ virtual void OnNavigateError(IWebBrowser2* browser, BSTR url,
+ long status_code);
+ virtual void OnNewWindow(BSTR url_context, BSTR url);
+ // @}
+
+ // @name WindowMessageSource::Sink implementation
+ // @{
+ virtual void OnHandleMessage(WindowMessageSource::MessageType type,
+ const MSG* message_info);
+ // @}
+
+ protected:
+ // The main frame ID.
+ static const int kMainFrameId = 0;
+
+ // Sometimes IE fires unexpected events, which could possibly corrupt the
+ // internal state of WebProgressNotifier and lead to incorrect webNavigation
+ // event sequence. Here is a common issue:
+ //
+ // When a URL is opened in a new tab/window (CTRL + mouse click, or using
+ // "_blank" target in <a> element/IHTMLDocument2::open),
+ // (1) if the URL results in server-initiated redirect, the event sequence is:
+ // (1-1) OnNewWindow
+ // (1-2) OnBeforeNavigate http://www.originalurl.com/
+ // (1-3) OnDocumentComplete http://www.originalurl.com/
+ // (1-4) OnNavigateComplete http://www.originalurl.com/
+ // (1-5) OnNavigateComplete http://www.redirecturl.com/
+ // (1-6) OnDocumentComplete http://www.redirecturl.com/
+ // (2) otherwise, the event sequence is:
+ // (2-1) OnNewWindow
+ // (2-2) OnBeforeNavigate http://www.url.com/
+ // (2-3) OnDocumentComplete http://www.url.com/
+ // (2-4) OnNavigateComplete http://www.url.com/
+ // (2-5) OnDocumentComplete http://www.url.com/
+ // (NOTE: HTTP responses with status code 304 fall into the 2nd category.)
+ //
+ // Event 1-3, 1-4 and 2-3 are undesired, comparing with the (normal) event
+ // sequence of opening a link in the current tab/window.
+ // FilteringInfo is used to filter out these events.
+ //
+ // It is easy to get rid of event 1-3 and 2-3. If we observe an
+ // OnDocumentComplete event immediately after OnBeforeNavigate, we know that
+ // it should be ignored.
+ // However, it is hard to get rid of event 1-4, since we have no way to tell
+ // the difference between 1-4 and 2-4, until we get the next event.
+ // (At the first glance, we could tell 1-4 from 2-4 by observing
+ // INTERNET_STATUS_REDIRECT to see whether the navigation involves
+ // server-initiated redirect. However, INTERNET_STATUS_REDIRECT actually
+ // happens *after* event 1-4.) As a result, when we receive an
+ // OnNavigateComplete event after an undesired OnDocumentComplete event, we
+ // have to postpone making the decision of processing it or not until we
+ // receive the next event. We process it only if the next event is not another
+ // OnNavigateComplete.
+ struct FilteringInfo {
+ enum State {
+ // The tab/window is newly created.
+ START,
+ // The first OnBeforeNavigate in the main frame has been observed.
+ FIRST_BEFORE_NAVIGATE,
+ // A suspicious OnDocumentComplete in the main frame has been observed.
+ SUSPICIOUS_DOCUMENT_COMPLETE,
+ // A suspicious OnNavigateComplete in the main frame has been observed.
+ SUSPICIOUS_NAVIGATE_COMPLETE,
+ // Filtering has finished.
+ END
+ };
+
+ enum Event {
+ // An OnBeforeNavigate has been fired.
+ BEFORE_NAVIGATE,
+ // An OnDocumentComplete has been fired.
+ DOCUMENT_COMPLETE,
+ // An OnNavigateComplete has been fired.
+ NAVIGATE_COMPLETE,
+ // An OnNavigateError has been fired.
+ NAVIGATE_ERROR,
+ // An OnNewWindow has been fired.
+ NEW_WINDOW
+ };
+
+ FilteringInfo() : state(START) {}
+
+ State state;
+
+ // Arguments of a pending OnNavigateComplete event.
+ CComBSTR pending_navigate_complete_url;
+ CComPtr<IWebBrowser2> pending_navigate_complete_browser;
+ base::Time pending_navigate_complete_timestamp;
+ };
+
+ // Any transition type can be augmented by qualifiers, which further define
+ // the navigation.
+ // Transition types could be found in chrome/common/page_transition_types.h.
+ // We are not using the qualifiers defined in that file, since we need to
+ // define a few IE/FF specific qualifiers for now.
+ enum TransitionQualifier {
+ // Redirects caused by JavaScript or a meta refresh tag on the page.
+ CLIENT_REDIRECT = 0x1,
+ // Redirects sent from the server by HTTP headers.
+ SERVER_REDIRECT = 0x2,
+ // Users use Forward or Back button to navigate among browsing history.
+ FORWARD_BACK = 0x4,
+ // Client redirects caused by <meta http-equiv="refresh">. (IE/FF specific)
+ REDIRECT_META_REFRESH = 0x8,
+ // Client redirects happening in JavaScript onload event handler.
+ // (IE/FF specific)
+ REDIRECT_ONLOAD = 0x10,
+ // Non-onload JavaScript redirects. (IE/FF specific)
+ REDIRECT_JAVASCRIPT = 0x20,
+ FIRST_TRANSITION_QUALIFIER = CLIENT_REDIRECT,
+ LAST_TRANSITION_QUALIFIER = REDIRECT_JAVASCRIPT
+ };
+ // Represents zero or more transition qualifiers.
+ typedef unsigned int TransitionQualifiers;
+
+ // Information related to a frame.
+ struct FrameInfo {
+ FrameInfo() : transition_type(PageTransition::LINK),
+ transition_qualifiers(0),
+ frame_id(-1) {
+ }
+
+ FrameInfo(PageTransition::Type in_transition_type,
+ TransitionQualifiers in_transition_qualifiers,
+ int in_frame_id)
+ : transition_type(in_transition_type),
+ transition_qualifiers(in_transition_qualifiers),
+ frame_id(in_frame_id) {
+ }
+
+ // Clears transition type as well as qualifiers.
+ void ClearTransition() {
+ SetTransition(PageTransition::LINK, 0);
+ }
+
+ // Sets transition type and qualifiers.
+ void SetTransition(PageTransition::Type type,
+ TransitionQualifiers qualifiers) {
+ transition_type = type;
+ transition_qualifiers = qualifiers;
+ }
+
+ // Appends more transition qualifiers.
+ void AppendTransitionQualifiers(TransitionQualifiers qualifiers) {
+ transition_qualifiers |= qualifiers;
+ }
+
+ // Whether this FrameInfo instance is associated with the main frame.
+ bool IsMainFrame() const {
+ return frame_id == kMainFrameId;
+ }
+
+ // The transition type for the current navigation in this frame.
+ PageTransition::Type transition_type;
+ // The transition qualifiers for the current navigation in this frame.
+ TransitionQualifiers transition_qualifiers;
+ // The frame ID.
+ const int frame_id;
+ };
+
+ // Accessor so that we can mock it in unit tests.
+ virtual WebNavigationEventsFunnel& webnavigation_events_funnel() {
+ return webnavigation_events_funnel_;
+ }
+
+ // Accessor so that we can mock WebRequestNotifier in unit tests.
+ virtual WebRequestNotifier* webrequest_notifier() {
+ if (cached_webrequest_notifier_ == NULL) {
+ cached_webrequest_notifier_ = ProductionWebRequestNotifier::get();
+ }
+ return cached_webrequest_notifier_;
+ }
+
+ // Unit testing seems to create a WindowMessageSource instance.
+ virtual WindowMessageSource* CreateWindowMessageSource();
+
+ // Whether the current navigation is a navigation among the browsing history
+ // (forward/back list).
+ // The method is made virtual so that we could easily mock it in unit tests.
+ virtual bool IsForwardBack(BSTR url);
+
+ // Whether we are currently inside the onload event handler of the page.
+ // The method is made virtual so that we could easily mock it in unit tests.
+ virtual bool InOnLoadEvent(IWebBrowser2* browser);
+
+ // Whether there is meta refresh tag on the current page.
+ // The method is made virtual so that we could easily mock it in unit tests.
+ virtual bool IsMetaRefresh(IWebBrowser2* browser, BSTR url);
+
+ // Whether there is JavaScript code on the current page that could possibly
+ // cause a navigation.
+ // The method is made virtual so that we could easily mock it in unit tests.
+ virtual bool HasPotentialJavaScriptRedirect(IWebBrowser2* browser);
+
+ // Whether there is user action in the content window that could possibly
+ // cause a navigation.
+ // The method is made virtual so that we could easily mock it in unit tests.
+ virtual bool IsPossibleUserActionInContentWindow();
+
+ // Whether there is user action in the browser UI that could possibly cause a
+ // navigation.
+ // The method is made virtual so that we could easily mock it in unit tests.
+ virtual bool IsPossibleUserActionInBrowserUI();
+
+ // Converts a set of transition qualifier values into a string.
+ std::string TransitionQualifiersString(TransitionQualifiers qualifiers);
+
+ // Whether the IWebBrowser2 interface belongs to the main frame.
+ bool IsMainFrame(IWebBrowser2* browser) {
+ return browser != NULL && main_browser_.IsEqualObject(browser);
+ }
+
+ // Gets the information related to a frame.
+ // @param browser The corresponding IWebBrowser2 interface of a frame.
+ // @param frame_info An output parameter to return the information. The caller
+ // doesn't take ownership of the returned object. The FrameInfo
+ // instance for the main frame will live as long as the
+ // WebProgressNotifier instance; all FrameInfo instances for subframes
+ // will be deleted when the main frame navigates.
+ // @return Whether the operation is successful or not.
+ bool GetFrameInfo(IWebBrowser2* browser, FrameInfo** frame_info);
+
+ // Gets the document of the frame.
+ // @param browser The corresponding IWebBrowser2 interface of a frame.
+ // @param id The IID of the document interface to return.
+ // @param document An output parameter to return the interface pointer.
+ // @return Whether the operation is successful or not.
+ bool GetDocument(IWebBrowser2* browser, REFIID id, void** document);
+
+ // Whether the specified Windows message represents a user action.
+ bool IsUserActionMessage(UINT message) {
+ return message == WM_LBUTTONUP || message == WM_KEYUP ||
+ message == WM_KEYDOWN;
+ }
+
+ // Handles OnNavigateComplete events.
+ // @param browser The corresponding IWebBrowser2 interface of a frame.
+ // @param url The URL that the frame navigated to.
+ // @param timestamp The time when the OnNavigateComplete event was fired.
+ void HandleNavigateComplete(IWebBrowser2* browser,
+ BSTR url,
+ const base::Time& timestamp);
+
+ // Decides whether to filter out a navigation event.
+ // The method may call HandleNavigateComplete to handle delayed
+ // OnNavigateComplete events.
+ // @param browser The frame in which the navigation event happens.
+ // @param event The navigation event.
+ // @return Returns true if the event should be ignored.
+ bool FilterOutWebBrowserEvent(IWebBrowser2* browser,
+ FilteringInfo::Event event);
+
+ // This class doesn't have ownership of the object that
+ // web_browser_events_source_ points to.
+ WebBrowserEventsSource* web_browser_events_source_;
+ // Publisher of events about Windows message handling.
+ scoped_ptr<WindowMessageSource> window_message_source_;
+
+ // The funnel for sending webNavigation events to the broker.
+ WebNavigationEventsFunnel webnavigation_events_funnel_;
+
+ // Information related to the main frame.
+ FrameInfo main_frame_info_;
+
+ // IWebBrowser2 interface pointer of the main frame.
+ CComPtr<IWebBrowser2> main_browser_;
+ // ITravelLogStg interface pointer to manage the forward/back list.
+ CComPtr<ITravelLogStg> travel_log_;
+ // Window handle of the tab.
+ CeeeWindowHandle tab_handle_;
+
+ // The ID to assign to the next subframe.
+ int next_subframe_id_;
+ // Maintains a map from subframes and their corresponding FrameInfo instances.
+ typedef std::map<CAdapt<CComPtr<IUnknown> >, FrameInfo> SubframeMap;
+ SubframeMap subframe_map_;
+
+ // Information related to the forward/back list.
+ struct TravelLogInfo {
+ TravelLogInfo() : length(-1), position(-1) {
+ }
+ // The length of the forward/back list, including the current entry.
+ DWORD length;
+ // The current position within the forward/back list, defined as the
+ // distance between the current entry and the newest entry in the
+ // forward/back list. That is, if the current entry is the newest one
+ // in the forward/back list then the position is 0.
+ DWORD position;
+ // The URL of the newest entry in the forward/back list.
+ CComBSTR newest_url;
+ };
+ // The state of the forward/back list before the current navigation.
+ TravelLogInfo previous_travel_log_info_;
+
+ // Whether the previous navigation of the main frame reaches DocumentComplete.
+ bool main_frame_document_complete_;
+
+ // If tracking_content_window_action_ is true, consider user action in the tab
+ // content window as a possible cause for the next navigation.
+ bool tracking_content_window_action_;
+ // The last time when the user took action in the content window.
+ base::Time last_content_window_action_time_;
+
+ // If tracking_browser_ui_action_ is true, consider user action in the browser
+ // UI (except the tab content window) as a possible cause for the next
+ // navigation.
+ bool tracking_browser_ui_action_;
+ // The last time when the user took action in the browser UI.
+ base::Time last_browser_ui_action_time_;
+
+ // Whether there is JavaScript code on the current page that can possibly
+ // cause a navigation.
+ bool has_potential_javascript_redirect_;
+
+ // A cached pointer to the singleton object.
+ WebRequestNotifier* cached_webrequest_notifier_;
+ bool webrequest_notifier_initialized_;
+
+ DWORD create_thread_id_;
+
+ FilteringInfo filtering_info_;
+ private:
+ DISALLOW_COPY_AND_ASSIGN(WebProgressNotifier);
+};
+
+#endif // CEEE_IE_PLUGIN_BHO_WEB_PROGRESS_NOTIFIER_H_
diff --git a/ceee/ie/plugin/bho/web_progress_notifier_unittest.cc b/ceee/ie/plugin/bho/web_progress_notifier_unittest.cc
new file mode 100644
index 0000000..46a1e57
--- /dev/null
+++ b/ceee/ie/plugin/bho/web_progress_notifier_unittest.cc
@@ -0,0 +1,593 @@
+// 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.
+//
+// Unit test for WebProgressNotifier class.
+#include "ceee/ie/plugin/bho/web_progress_notifier.h"
+
+#include "ceee/ie/plugin/bho/web_browser_events_source.h"
+#include "ceee/ie/plugin/bho/window_message_source.h"
+#include "ceee/ie/testing/mock_broker_and_friends.h"
+#include "ceee/testing/utils/mock_com.h"
+#include "ceee/testing/utils/test_utils.h"
+#include "gmock/gmock.h"
+#include "gtest/gtest.h"
+
+namespace {
+
+using testing::_;
+using testing::AddRef;
+using testing::AnyNumber;
+using testing::InSequence;
+using testing::MockFunction;
+using testing::NiceMock;
+using testing::NotNull;
+using testing::Return;
+using testing::SaveArg;
+using testing::SetArgumentPointee;
+using testing::StrEq;
+using testing::StrictMock;
+
+class FakeWebBrowserEventsSource : public WebBrowserEventsSource {
+ public:
+ FakeWebBrowserEventsSource() : sink_(NULL) {}
+ virtual ~FakeWebBrowserEventsSource() {
+ EXPECT_EQ(NULL, sink_);
+ }
+
+ virtual void RegisterSink(Sink* sink) {
+ ASSERT_TRUE(sink != NULL && sink_ == NULL);
+ sink_ = sink;
+ }
+
+ virtual void UnregisterSink(Sink* sink) {
+ ASSERT_TRUE(sink == sink_);
+ sink_ = NULL;
+ }
+
+ void FireOnBeforeNavigate(IWebBrowser2* browser, BSTR url) {
+ if (sink_ != NULL)
+ sink_->OnBeforeNavigate(browser, url);
+ }
+ void FireOnDocumentComplete(IWebBrowser2* browser, BSTR url) {
+ if (sink_ != NULL)
+ sink_->OnDocumentComplete(browser, url);
+ }
+ void FireOnNavigateComplete(IWebBrowser2* browser, BSTR url) {
+ if (sink_ != NULL)
+ sink_->OnNavigateComplete(browser, url);
+ }
+ void FireOnNavigateError(IWebBrowser2* browser, BSTR url, long status_code) {
+ if (sink_ != NULL)
+ sink_->OnNavigateError(browser, url, status_code);
+ }
+ void FireOnNewWindow(BSTR url_context, BSTR url) {
+ if (sink_ != NULL)
+ sink_->OnNewWindow(url_context, url);
+ }
+
+ private:
+ Sink* sink_;
+};
+
+class FakeWindowMessageSource : public WindowMessageSource {
+ public:
+ FakeWindowMessageSource() : sink_(NULL) {}
+ virtual ~FakeWindowMessageSource() {
+ EXPECT_EQ(NULL, sink_);
+ }
+
+ virtual void RegisterSink(Sink* sink) {
+ ASSERT_TRUE(sink != NULL && sink_ == NULL);
+ sink_ = sink;
+ }
+
+ virtual void UnregisterSink(Sink* sink) {
+ ASSERT_TRUE(sink == sink_);
+ sink_ = NULL;
+ }
+
+ void FireOnHandleMessage(MessageType type,
+ const MSG* message_info) {
+ if (sink_ != NULL)
+ sink_->OnHandleMessage(type, message_info);
+ }
+
+ private:
+ Sink* sink_;
+};
+
+class TestWebProgressNotifier : public WebProgressNotifier {
+ public:
+ TestWebProgressNotifier()
+ : mock_is_forward_back_(false),
+ mock_in_onload_event_(false),
+ mock_is_meta_refresh_(false),
+ mock_has_potential_javascript_redirect_(false),
+ mock_is_possible_user_action_in_content_window_(false),
+ mock_is_possible_user_action_in_browser_ui_(false) {}
+
+ virtual WebNavigationEventsFunnel& webnavigation_events_funnel() {
+ return mock_webnavigation_events_funnel_;
+ }
+
+ virtual WindowMessageSource* CreateWindowMessageSource() {
+ return new FakeWindowMessageSource();
+ }
+
+ virtual bool IsForwardBack(BSTR /*url*/) { return mock_is_forward_back_; }
+ virtual bool InOnLoadEvent(IWebBrowser2* /*browser*/) {
+ return mock_in_onload_event_;
+ }
+ virtual bool IsMetaRefresh(IWebBrowser2* /*browser*/, BSTR /*url*/) {
+ return mock_is_meta_refresh_;
+ }
+ virtual bool HasPotentialJavaScriptRedirect(IWebBrowser2* /*browser*/) {
+ return mock_has_potential_javascript_redirect_;
+ }
+ virtual bool IsPossibleUserActionInContentWindow() {
+ return mock_is_possible_user_action_in_content_window_;
+ }
+ virtual bool IsPossibleUserActionInBrowserUI() {
+ return mock_is_possible_user_action_in_browser_ui_;
+ }
+
+ bool CallRealIsForwardBack(BSTR url) {
+ return WebProgressNotifier::IsForwardBack(url);
+ }
+
+ TravelLogInfo& previous_travel_log_info() {
+ return previous_travel_log_info_;
+ }
+
+ StrictMock<testing::MockWebNavigationEventsFunnel>
+ mock_webnavigation_events_funnel_;
+ bool mock_is_forward_back_;
+ bool mock_in_onload_event_;
+ bool mock_is_meta_refresh_;
+ bool mock_has_potential_javascript_redirect_;
+ bool mock_is_possible_user_action_in_content_window_;
+ bool mock_is_possible_user_action_in_browser_ui_;
+};
+
+class WebProgressNotifierTestFixture : public testing::Test {
+ protected:
+ WebProgressNotifierTestFixture() : mock_web_browser_(NULL),
+ mock_travel_log_stg_(NULL) {
+ }
+
+ virtual void SetUp() {
+ CComObject<testing::MockIWebBrowser2>::CreateInstance(
+ &mock_web_browser_);
+ ASSERT_TRUE(mock_web_browser_ != NULL);
+ web_browser_ = mock_web_browser_;
+
+ CComObject<testing::MockITravelLogStg>::CreateInstance(
+ &mock_travel_log_stg_);
+ ASSERT_TRUE(mock_travel_log_stg_ != NULL);
+ travel_log_stg_ = mock_travel_log_stg_;
+
+ // We cannot use CopyInterfaceToArgument here because QueryService takes
+ // void** as argument.
+ EXPECT_CALL(*mock_web_browser_,
+ QueryService(SID_STravelLogCursor, IID_ITravelLogStg,
+ NotNull()))
+ .WillRepeatedly(DoAll(SetArgumentPointee<2>(travel_log_stg_.p),
+ AddRef(travel_log_stg_.p),
+ Return(S_OK)));
+
+ web_browser_events_source_.reset(new FakeWebBrowserEventsSource());
+
+ web_progress_notifier_.reset(new TestWebProgressNotifier());
+ ASSERT_HRESULT_SUCCEEDED(web_progress_notifier_->Initialize(
+ web_browser_events_source_.get(), reinterpret_cast<HWND>(1024),
+ web_browser_));
+ }
+
+ virtual void TearDown() {
+ web_progress_notifier_->TearDown();
+ web_progress_notifier_.reset(NULL);
+ web_browser_events_source_.reset(NULL);
+ mock_web_browser_ = NULL;
+ web_browser_.Release();
+ mock_travel_log_stg_ = NULL;
+ travel_log_stg_.Release();
+ }
+
+ void IgnoreCallsToEventsFunnelExceptOnCommitted() {
+ EXPECT_CALL(web_progress_notifier_->mock_webnavigation_events_funnel_,
+ OnBeforeNavigate(_, _, _, _, _))
+ .Times(AnyNumber());
+ EXPECT_CALL(web_progress_notifier_->mock_webnavigation_events_funnel_,
+ OnBeforeRetarget(_, _, _, _))
+ .Times(AnyNumber());
+ EXPECT_CALL(web_progress_notifier_->mock_webnavigation_events_funnel_,
+ OnCompleted(_, _, _, _))
+ .Times(AnyNumber());
+ EXPECT_CALL(web_progress_notifier_->mock_webnavigation_events_funnel_,
+ OnDOMContentLoaded(_, _, _, _))
+ .Times(AnyNumber());
+ EXPECT_CALL(web_progress_notifier_->mock_webnavigation_events_funnel_,
+ OnErrorOccurred(_, _, _, _, _))
+ .Times(AnyNumber());
+ }
+
+ void FireNavigationEvents(IWebBrowser2* browser, BSTR url) {
+ web_browser_events_source_->FireOnBeforeNavigate(browser, url);
+ web_browser_events_source_->FireOnNavigateComplete(browser, url);
+ web_browser_events_source_->FireOnDocumentComplete(browser, url);
+ }
+
+ HRESULT CreateMockWebBrowser(IWebBrowser2** web_browser) {
+ EXPECT_TRUE(web_browser != NULL);
+ CComObject<testing::MockIWebBrowser2>* mock_web_browser = NULL;
+ CComObject<testing::MockIWebBrowser2>::CreateInstance(&mock_web_browser);
+ EXPECT_TRUE(mock_web_browser != NULL);
+
+ *web_browser = mock_web_browser;
+ (*web_browser)->AddRef();
+ return S_OK;
+ }
+
+ CComObject<testing::MockIWebBrowser2>* mock_web_browser_;
+ CComPtr<IWebBrowser2> web_browser_;
+ CComObject<testing::MockITravelLogStg>* mock_travel_log_stg_;
+ CComPtr<ITravelLogStg> travel_log_stg_;
+ scoped_ptr<FakeWebBrowserEventsSource> web_browser_events_source_;
+ scoped_ptr<TestWebProgressNotifier> web_progress_notifier_;
+};
+
+TEST_F(WebProgressNotifierTestFixture, FireEvents) {
+ MockFunction<void(int check_point)> check;
+ {
+ InSequence sequence;
+ EXPECT_CALL(web_progress_notifier_->mock_webnavigation_events_funnel_,
+ OnBeforeNavigate(_, _, _, _, _));
+ EXPECT_CALL(check, Call(1));
+ EXPECT_CALL(web_progress_notifier_->mock_webnavigation_events_funnel_,
+ OnBeforeRetarget(_, _, _, _));
+ EXPECT_CALL(check, Call(2));
+ EXPECT_CALL(web_progress_notifier_->mock_webnavigation_events_funnel_,
+ OnCommitted(_, _, _, _, _, _));
+ EXPECT_CALL(check, Call(3));
+ EXPECT_CALL(web_progress_notifier_->mock_webnavigation_events_funnel_,
+ OnCompleted(_, _, _, _));
+ EXPECT_CALL(check, Call(4));
+ EXPECT_CALL(web_progress_notifier_->mock_webnavigation_events_funnel_,
+ OnErrorOccurred(_, _, _, _, _));
+
+ web_browser_events_source_->FireOnBeforeNavigate(
+ web_browser_, CComBSTR(L"http://www.google.com/"));
+ check.Call(1);
+ web_browser_events_source_->FireOnNewWindow(
+ CComBSTR(L"http://www.google.com/"),
+ CComBSTR(L"http://mail.google.com/"));
+ check.Call(2);
+ web_browser_events_source_->FireOnNavigateComplete(
+ web_browser_, CComBSTR(L"http://www.google.com/"));
+ check.Call(3);
+ web_browser_events_source_->FireOnDocumentComplete(
+ web_browser_, CComBSTR(L"http://www.google.com/"));
+ check.Call(4);
+ web_browser_events_source_->FireOnNavigateError(
+ web_browser_, CComBSTR(L"http://www.google.com/"), 400);
+ }
+}
+
+TEST_F(WebProgressNotifierTestFixture, FilterAbnormalWebBrowserEvents) {
+ MockFunction<void(int check_point)> check;
+ {
+ InSequence sequence;
+ EXPECT_CALL(web_progress_notifier_->mock_webnavigation_events_funnel_,
+ OnBeforeNavigate(_, _, _, _, _));
+ EXPECT_CALL(web_progress_notifier_->mock_webnavigation_events_funnel_,
+ OnCommitted(_, _, _, _, _, _));
+ EXPECT_CALL(web_progress_notifier_->mock_webnavigation_events_funnel_,
+ OnCompleted(_, _, _, _));
+ }
+
+ web_browser_events_source_->FireOnBeforeNavigate(
+ web_browser_, CComBSTR(L"http://www.google.com/"));
+ // Undesired event.
+ web_browser_events_source_->FireOnDocumentComplete(
+ web_browser_, CComBSTR(L"http://www.google.com/"));
+ // Undesired event.
+ web_browser_events_source_->FireOnNavigateComplete(
+ web_browser_, CComBSTR(L"http://www.google.com/"));
+ web_browser_events_source_->FireOnNavigateComplete(
+ web_browser_, CComBSTR(L"http://mail.google.com/"));
+ web_browser_events_source_->FireOnDocumentComplete(
+ web_browser_, CComBSTR(L"http://mail.google.com/"));
+}
+
+TEST_F(WebProgressNotifierTestFixture, TestFrameId) {
+ int subframe_id_1 = -1;
+ int current_frame_id = -1;
+
+ CComPtr<IWebBrowser2> subframe_1;
+ ASSERT_HRESULT_SUCCEEDED(CreateMockWebBrowser(&subframe_1));
+
+ CComPtr<IWebBrowser2> subframe_2;
+ ASSERT_HRESULT_SUCCEEDED(CreateMockWebBrowser(&subframe_2));
+
+ MockFunction<void(int check_point)> check;
+ {
+ InSequence sequence;
+ EXPECT_CALL(web_progress_notifier_->mock_webnavigation_events_funnel_,
+ OnBeforeNavigate(_, _, _, _, _))
+ .WillOnce(DoAll(SaveArg<2>(&current_frame_id),
+ Return(S_OK)));
+ EXPECT_CALL(check, Call(1));
+ EXPECT_CALL(web_progress_notifier_->mock_webnavigation_events_funnel_,
+ OnCommitted(_, _, _, _, _, _))
+ .WillOnce(DoAll(SaveArg<2>(&current_frame_id),
+ Return(S_OK)));
+ EXPECT_CALL(check, Call(2));
+ EXPECT_CALL(web_progress_notifier_->mock_webnavigation_events_funnel_,
+ OnBeforeNavigate(_, _, _, _, _))
+ .WillOnce(DoAll(SaveArg<2>(&current_frame_id),
+ Return(S_OK)));
+ EXPECT_CALL(check, Call(3));
+ EXPECT_CALL(web_progress_notifier_->mock_webnavigation_events_funnel_,
+ OnCommitted(_, _, _, _, _, _))
+ .WillOnce(DoAll(SaveArg<2>(&current_frame_id),
+ Return(S_OK)));
+ EXPECT_CALL(check, Call(4));
+ EXPECT_CALL(web_progress_notifier_->mock_webnavigation_events_funnel_,
+ OnBeforeNavigate(_, _, _, _, _))
+ .WillOnce(DoAll(SaveArg<2>(&current_frame_id),
+ Return(S_OK)));
+
+ web_browser_events_source_->FireOnBeforeNavigate(
+ web_browser_, CComBSTR(L"http://www.google.com/"));
+ // The main frame has 0 as frame ID.
+ EXPECT_EQ(0, current_frame_id);
+ check.Call(1);
+
+ current_frame_id = -1;
+ web_browser_events_source_->FireOnNavigateComplete(
+ web_browser_, CComBSTR(L"http://www.google.com/"));
+ // The main frame has 0 as frame ID.
+ EXPECT_EQ(0, current_frame_id);
+ check.Call(2);
+
+ current_frame_id = -1;
+ web_browser_events_source_->FireOnBeforeNavigate(
+ subframe_1, CComBSTR(L"http://www.google.com/"));
+ subframe_id_1 = current_frame_id;
+ // A subframe should not have 0 as frame ID.
+ EXPECT_NE(0, subframe_id_1);
+ check.Call(3);
+
+ current_frame_id = -1;
+ web_browser_events_source_->FireOnNavigateComplete(
+ subframe_1, CComBSTR(L"http://www.google.com/"));
+ // The frame ID of a subframe remains the same.
+ EXPECT_EQ(subframe_id_1, current_frame_id);
+ check.Call(4);
+
+ current_frame_id = -1;
+ web_browser_events_source_->FireOnBeforeNavigate(
+ subframe_2, CComBSTR(L"http://www.google.com/"));
+ // Different subframes have different frame IDs.
+ EXPECT_NE(subframe_id_1, current_frame_id);
+ }
+}
+
+TEST_F(WebProgressNotifierTestFixture, TestTransitionClientRedirect) {
+ IgnoreCallsToEventsFunnelExceptOnCommitted();
+
+ CComBSTR url(L"http://www.google.com");
+ CComBSTR javascript_url(L"javascript:someScript();");
+ const char* link = "link";
+ const char* redirect_javascript = "client_redirect|redirect_javascript";
+ const char* redirect_onload = "client_redirect|redirect_onload";
+ const char* redirect_meta_refresh = "client_redirect|redirect_meta_refresh";
+ MockFunction<void(int check_point)> check;
+ {
+ InSequence sequence;
+ EXPECT_CALL(web_progress_notifier_->mock_webnavigation_events_funnel_,
+ OnCommitted(_, _, _, StrEq(link), StrEq(redirect_javascript),
+ _));
+ EXPECT_CALL(check, Call(1));
+
+ EXPECT_CALL(web_progress_notifier_->mock_webnavigation_events_funnel_,
+ OnCommitted(_, _, _, StrEq(link), StrEq(redirect_javascript),
+ _));
+ EXPECT_CALL(check, Call(2));
+
+ EXPECT_CALL(web_progress_notifier_->mock_webnavigation_events_funnel_,
+ OnCommitted(_, _, _, _, _, _));
+ EXPECT_CALL(web_progress_notifier_->mock_webnavigation_events_funnel_,
+ OnCommitted(_, _, _, StrEq(link), StrEq(redirect_javascript),
+ _));
+ EXPECT_CALL(check, Call(3));
+
+ EXPECT_CALL(web_progress_notifier_->mock_webnavigation_events_funnel_,
+ OnCommitted(_, _, _, StrEq(link), StrEq(redirect_onload), _));
+ EXPECT_CALL(check, Call(4));
+
+ EXPECT_CALL(web_progress_notifier_->mock_webnavigation_events_funnel_,
+ OnCommitted(_, _, _, StrEq(link), StrEq(redirect_meta_refresh),
+ _));
+ }
+
+ // If target URL starts with "javascript:" and no other signals are found,
+ // consider the transition as JavaScript redirect.
+ web_browser_events_source_->FireOnBeforeNavigate(web_browser_,
+ javascript_url);
+ web_browser_events_source_->FireOnNavigateComplete(web_browser_,
+ javascript_url);
+ check.Call(1);
+
+ // If DocumentComplete hasn't been received for the previous navigation, and
+ // no other signals are found, consider the transition as JavaScript redirect.
+ FireNavigationEvents(web_browser_, url);
+ check.Call(2);
+
+ web_progress_notifier_->mock_has_potential_javascript_redirect_ = true;
+ FireNavigationEvents(web_browser_, url);
+ // If JavaScript code to navigate the page is found on the previous page, and
+ // no other signals are found, consider the transition as JavaScript redirect.
+ FireNavigationEvents(web_browser_, url);
+ check.Call(3);
+
+ // If currently we are in the onload event handler, consider the transition as
+ // onload redirect.
+ web_progress_notifier_->mock_has_potential_javascript_redirect_ = false;
+ web_progress_notifier_->mock_in_onload_event_ = true;
+ FireNavigationEvents(web_browser_, url);
+ check.Call(4);
+
+ // If the previous page has <meta http-equiv="refresh"> tag, consider the
+ // transition as meta-refresh redirect.
+ web_progress_notifier_->mock_in_onload_event_ = false;
+ web_progress_notifier_->mock_is_meta_refresh_ = true;
+ FireNavigationEvents(web_browser_, url);
+}
+
+TEST_F(WebProgressNotifierTestFixture, TestTransitionUserAction) {
+ IgnoreCallsToEventsFunnelExceptOnCommitted();
+
+ CComBSTR url(L"http://www.google.com");
+ const char* link = "link";
+ const char* typed = "typed";
+ const char* no_qualifier = "";
+ MockFunction<void(int check_point)> check;
+ {
+ InSequence sequence;
+ EXPECT_CALL(web_progress_notifier_->mock_webnavigation_events_funnel_,
+ OnCommitted(_, _, _, StrEq(link), StrEq(no_qualifier), _));
+ EXPECT_CALL(check, Call(1));
+
+ EXPECT_CALL(web_progress_notifier_->mock_webnavigation_events_funnel_,
+ OnCommitted(_, _, _, StrEq(typed), StrEq(no_qualifier), _));
+ }
+
+ // User actions override JavaScript redirect signals, so setting the following
+ // flag should have no effect.
+ web_progress_notifier_->mock_has_potential_javascript_redirect_ = true;
+
+ // If there is user action in the content window, consider the transition as
+ // link.
+ web_progress_notifier_->mock_is_possible_user_action_in_content_window_ =
+ true;
+ FireNavigationEvents(web_browser_, url);
+ check.Call(1);
+
+ // If there is user action in the browser UI, consider the transition as
+ // typed.
+ web_progress_notifier_->mock_is_possible_user_action_in_content_window_ =
+ false;
+ web_progress_notifier_->mock_is_possible_user_action_in_browser_ui_ = true;
+ FireNavigationEvents(web_browser_, url);
+}
+
+TEST_F(WebProgressNotifierTestFixture, TestTransitionForwardBack) {
+ IgnoreCallsToEventsFunnelExceptOnCommitted();
+
+ CComBSTR url(L"http://www.google.com");
+ const char* auto_bookmark = "auto_bookmark";
+ const char* forward_back = "forward_back";
+
+ EXPECT_CALL(web_progress_notifier_->mock_webnavigation_events_funnel_,
+ OnCommitted(_, _, _, StrEq(auto_bookmark), StrEq(forward_back),
+ _));
+
+ // Forward/back overrides other signals, so setting the following flags
+ // should have no effect.
+ web_progress_notifier_->mock_is_possible_user_action_in_content_window_ =
+ true;
+ web_progress_notifier_->mock_is_possible_user_action_in_browser_ui_ =
+ true;
+ web_progress_notifier_->mock_in_onload_event_ = true;
+ web_progress_notifier_->mock_is_meta_refresh_ = true;
+
+ // If the current navigation doesn't cause browsing history to change,
+ // consider the transition as forward/back.
+ web_progress_notifier_->mock_is_forward_back_ = true;
+ FireNavigationEvents(web_browser_, url);
+}
+
+TEST_F(WebProgressNotifierTestFixture, TestDetectingForwardBack) {
+ TLENUMF back_fore = TLEF_RELATIVE_BACK | TLEF_RELATIVE_FORE |
+ TLEF_INCLUDE_UNINVOKEABLE;
+ TLENUMF fore = TLEF_RELATIVE_FORE | TLEF_INCLUDE_UNINVOKEABLE;
+ MockFunction<void(int check_point)> check;
+ {
+ InSequence sequence;
+
+ EXPECT_CALL(*mock_travel_log_stg_, GetCount(back_fore, NotNull()))
+ .WillOnce(DoAll(SetArgumentPointee<1>(5),
+ Return(S_OK)));
+ EXPECT_CALL(*mock_travel_log_stg_, GetCount(fore, NotNull()))
+ .WillOnce(DoAll(SetArgumentPointee<1>(0),
+ Return(S_OK)));
+ EXPECT_CALL(check, Call(1));
+
+ EXPECT_CALL(*mock_travel_log_stg_, GetCount(back_fore, NotNull()))
+ .WillOnce(DoAll(SetArgumentPointee<1>(6),
+ Return(S_OK)));
+ EXPECT_CALL(*mock_travel_log_stg_, GetCount(fore, NotNull()))
+ .WillOnce(DoAll(SetArgumentPointee<1>(0),
+ Return(S_OK)));
+ EXPECT_CALL(check, Call(2));
+
+ EXPECT_CALL(*mock_travel_log_stg_, GetCount(back_fore, NotNull()))
+ .WillOnce(DoAll(SetArgumentPointee<1>(6),
+ Return(S_OK)));
+ EXPECT_CALL(*mock_travel_log_stg_, GetCount(fore, NotNull()))
+ .WillOnce(DoAll(SetArgumentPointee<1>(3),
+ Return(S_OK)));
+ EXPECT_CALL(check, Call(3));
+
+ EXPECT_CALL(*mock_travel_log_stg_, GetCount(back_fore, NotNull()))
+ .WillOnce(DoAll(SetArgumentPointee<1>(6),
+ Return(S_OK)));
+ EXPECT_CALL(*mock_travel_log_stg_, GetCount(fore, NotNull()))
+ .WillOnce(DoAll(SetArgumentPointee<1>(0),
+ Return(S_OK)));
+ EXPECT_CALL(check, Call(4));
+
+ EXPECT_CALL(*mock_travel_log_stg_, GetCount(back_fore, NotNull()))
+ .WillOnce(DoAll(SetArgumentPointee<1>(6),
+ Return(S_OK)));
+ EXPECT_CALL(*mock_travel_log_stg_, GetCount(fore, NotNull()))
+ .WillOnce(DoAll(SetArgumentPointee<1>(0),
+ Return(S_OK)));
+ }
+
+ CComBSTR google_url(L"http://www.google.com/");
+ CComBSTR gmail_url(L"http://mail.google.com/");
+
+ // Test recording of the previous travel log info.
+ EXPECT_FALSE(web_progress_notifier_->CallRealIsForwardBack(google_url));
+ EXPECT_EQ(6, web_progress_notifier_->previous_travel_log_info().length);
+ EXPECT_EQ(0, web_progress_notifier_->previous_travel_log_info().position);
+ EXPECT_EQ(google_url,
+ web_progress_notifier_->previous_travel_log_info().newest_url);
+ check.Call(1);
+
+ // If the length of the forward/back list has changed, the navigation is not
+ // forward/back.
+ EXPECT_FALSE(web_progress_notifier_->CallRealIsForwardBack(google_url));
+ check.Call(2);
+
+ // If the length of the forward/back list remains the same, and there are
+ // entries in the forward list, the navigation is forward/back.
+ EXPECT_TRUE(web_progress_notifier_->CallRealIsForwardBack(gmail_url));
+ EXPECT_NE(gmail_url,
+ web_progress_notifier_->previous_travel_log_info().newest_url);
+ check.Call(3);
+
+ // If the length of the forward/back list remains the same, and the URL of the
+ // last entry in the list remains the same, the navigation is forward/back.
+ EXPECT_TRUE(web_progress_notifier_->CallRealIsForwardBack(google_url));
+ check.Call(4);
+
+ // If the length of the forward/back list remains the same, but the URL of the
+ // last entry in the list has changed, the navigation is not forward/back.
+ EXPECT_FALSE(web_progress_notifier_->CallRealIsForwardBack(gmail_url));
+}
+
+} // namespace
diff --git a/ceee/ie/plugin/bho/webnavigation_events_funnel.cc b/ceee/ie/plugin/bho/webnavigation_events_funnel.cc
new file mode 100644
index 0000000..2717131
--- /dev/null
+++ b/ceee/ie/plugin/bho/webnavigation_events_funnel.cc
@@ -0,0 +1,113 @@
+// 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.
+//
+// Funnel of Chrome Extension Events from wherever through the Broker.
+
+#include "ceee/ie/plugin/bho/webnavigation_events_funnel.h"
+
+#include "base/logging.h"
+#include "base/values.h"
+#include "chrome/browser/extensions/extension_webnavigation_api_constants.h"
+
+namespace keys = extension_webnavigation_api_constants;
+
+namespace {
+
+double MilliSecondsFromTime(const base::Time& time) {
+ return base::Time::kMillisecondsPerSecond * time.ToDoubleT();
+}
+
+} // namespace
+
+HRESULT WebNavigationEventsFunnel::OnBeforeNavigate(
+ CeeeWindowHandle tab_handle,
+ BSTR url,
+ int frame_id,
+ int request_id,
+ const base::Time& time_stamp) {
+ DictionaryValue args;
+ args.SetInteger(keys::kTabIdKey, static_cast<int>(tab_handle));
+ args.SetString(keys::kUrlKey, url);
+ args.SetInteger(keys::kFrameIdKey, frame_id);
+ args.SetInteger(keys::kRequestIdKey, request_id);
+ args.SetReal(keys::kTimeStampKey, MilliSecondsFromTime(time_stamp));
+
+ return SendEvent(keys::kOnBeforeNavigate, args);
+}
+
+HRESULT WebNavigationEventsFunnel::OnBeforeRetarget(
+ CeeeWindowHandle source_tab_handle,
+ BSTR source_url,
+ BSTR target_url,
+ const base::Time& time_stamp) {
+ DictionaryValue args;
+ args.SetInteger(keys::kSourceTabIdKey, static_cast<int>(source_tab_handle));
+ args.SetString(keys::kSourceUrlKey, source_url);
+ args.SetString(keys::kTargetUrlKey, target_url);
+ args.SetReal(keys::kTimeStampKey, MilliSecondsFromTime(time_stamp));
+
+ return SendEvent(keys::kOnBeforeRetarget, args);
+}
+
+HRESULT WebNavigationEventsFunnel::OnCommitted(
+ CeeeWindowHandle tab_handle,
+ BSTR url,
+ int frame_id,
+ const char* transition_type,
+ const char* transition_qualifiers,
+ const base::Time& time_stamp) {
+ DictionaryValue args;
+ args.SetInteger(keys::kTabIdKey, static_cast<int>(tab_handle));
+ args.SetString(keys::kUrlKey, url);
+ args.SetInteger(keys::kFrameIdKey, frame_id);
+ args.SetString(keys::kTransitionTypeKey, transition_type);
+ args.SetString(keys::kTransitionQualifiersKey, transition_qualifiers);
+ args.SetReal(keys::kTimeStampKey, MilliSecondsFromTime(time_stamp));
+
+ return SendEvent(keys::kOnCommitted, args);
+}
+
+HRESULT WebNavigationEventsFunnel::OnCompleted(
+ CeeeWindowHandle tab_handle,
+ BSTR url,
+ int frame_id,
+ const base::Time& time_stamp) {
+ DictionaryValue args;
+ args.SetInteger(keys::kTabIdKey, static_cast<int>(tab_handle));
+ args.SetString(keys::kUrlKey, url);
+ args.SetInteger(keys::kFrameIdKey, frame_id);
+ args.SetReal(keys::kTimeStampKey, MilliSecondsFromTime(time_stamp));
+
+ return SendEvent(keys::kOnCompleted, args);
+}
+
+HRESULT WebNavigationEventsFunnel::OnDOMContentLoaded(
+ CeeeWindowHandle tab_handle,
+ BSTR url,
+ int frame_id,
+ const base::Time& time_stamp) {
+ DictionaryValue args;
+ args.SetInteger(keys::kTabIdKey, static_cast<int>(tab_handle));
+ args.SetString(keys::kUrlKey, url);
+ args.SetInteger(keys::kFrameIdKey, frame_id);
+ args.SetReal(keys::kTimeStampKey, MilliSecondsFromTime(time_stamp));
+
+ return SendEvent(keys::kOnDOMContentLoaded, args);
+}
+
+HRESULT WebNavigationEventsFunnel::OnErrorOccurred(
+ CeeeWindowHandle tab_handle,
+ BSTR url,
+ int frame_id,
+ BSTR error,
+ const base::Time& time_stamp) {
+ DictionaryValue args;
+ args.SetInteger(keys::kTabIdKey, static_cast<int>(tab_handle));
+ args.SetString(keys::kUrlKey, url);
+ args.SetInteger(keys::kFrameIdKey, frame_id);
+ args.SetString(keys::kErrorKey, error);
+ args.SetReal(keys::kTimeStampKey, MilliSecondsFromTime(time_stamp));
+
+ return SendEvent(keys::kOnErrorOccurred, args);
+}
diff --git a/ceee/ie/plugin/bho/webnavigation_events_funnel.h b/ceee/ie/plugin/bho/webnavigation_events_funnel.h
new file mode 100644
index 0000000..68243cc
--- /dev/null
+++ b/ceee/ie/plugin/bho/webnavigation_events_funnel.h
@@ -0,0 +1,108 @@
+// 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.
+//
+// Funnel of Chrome Extension Web Navigation Events.
+
+#ifndef CEEE_IE_PLUGIN_BHO_WEBNAVIGATION_EVENTS_FUNNEL_H_
+#define CEEE_IE_PLUGIN_BHO_WEBNAVIGATION_EVENTS_FUNNEL_H_
+#include <atlcomcli.h>
+
+#include "base/time.h"
+#include "ceee/ie/plugin/bho/events_funnel.h"
+
+#include "toolband.h" // NOLINT
+
+// Implements a set of methods to send web navigation related events to the
+// Broker.
+class WebNavigationEventsFunnel : public EventsFunnel {
+ public:
+ WebNavigationEventsFunnel() : EventsFunnel(false) {}
+
+ // Sends the webNavigation.onBeforeNavigate event to the Broker.
+ // @param tab_handle The window handle of the tab in which the navigation is
+ // about to occur.
+ // @param url The URL of the navigation.
+ // @param frame_id 0 indicates the navigation happens in the tab content
+ // window; positive value indicates navigation in a subframe.
+ // @param request_id The ID of the request to retrieve the document of this
+ // navigation.
+ // @param time_stamp The time when the browser was about to start the
+ // navigation.
+ virtual HRESULT OnBeforeNavigate(CeeeWindowHandle tab_handle,
+ BSTR url,
+ int frame_id,
+ int request_id,
+ const base::Time& time_stamp);
+
+ // Sends the webNavigation.onBeforeRetarget event to the Broker.
+ // @param source_tab_handle The window handle of the tab in which the
+ // navigation is triggered.
+ // @param source_url The URL of the document that is opening the new window.
+ // @param target_url The URL to be opened in the new window.
+ // @param time_stamp The time when the browser was about to create a new view.
+ virtual HRESULT OnBeforeRetarget(CeeeWindowHandle source_tab_handle,
+ BSTR source_url,
+ BSTR target_url,
+ const base::Time& time_stamp);
+
+ // Sends the webNavigation.onCommitted event to the Broker.
+ // @param tab_handle The window handle of the tab in which the navigation
+ // occurs.
+ // @param url The URL of the navigation.
+ // @param frame_id 0 indicates the navigation happens in the tab content
+ // window; positive value indicates navigation in a subframe.
+ // @param transition_type Cause of the navigation.
+ // @param transition_qualifiers Zero or more transition qualifiers delimited
+ // by "|".
+ // @param time_stamp The time when the navigation was committed.
+ virtual HRESULT OnCommitted(CeeeWindowHandle tab_handle,
+ BSTR url,
+ int frame_id,
+ const char* transition_type,
+ const char* transition_qualifiers,
+ const base::Time& time_stamp);
+
+ // Sends the webNavigation.onCompleted event to the Broker.
+ // @param tab_handle The window handle of the tab in which the navigation
+ // occurs.
+ // @param url The URL of the navigation.
+ // @param frame_id 0 indicates the navigation happens in the tab content
+ // window; positive value indicates navigation in a subframe.
+ // @param time_stamp The time when the document finished loading.
+ virtual HRESULT OnCompleted(CeeeWindowHandle tab_handle,
+ BSTR url,
+ int frame_id,
+ const base::Time& time_stamp);
+
+ // Sends the webNavigation.onDOMContentLoaded event to the Broker.
+ // @param tab_handle The window handle of the tab in which the navigation
+ // occurs.
+ // @param url The URL of the navigation.
+ // @param frame_id 0 indicates the navigation happens in the tab content
+ // window; positive value indicates navigation in a subframe.
+ // @param time_stamp The time when the page's DOM was fully constructed.
+ virtual HRESULT OnDOMContentLoaded(CeeeWindowHandle tab_handle,
+ BSTR url,
+ int frame_id,
+ const base::Time& time_stamp);
+
+ // Sends the webNavigation.onErrorOccurred event to the Broker.
+ // @param tab_handle The window handle of the tab in which the navigation
+ // occurs.
+ // @param url The URL of the navigation.
+ // @param frame_id 0 indicates the navigation happens in the tab content
+ // window; positive value indicates navigation in a subframe.
+ // @param error The error description.
+ // @param time_stamp The time when the error occurred.
+ virtual HRESULT OnErrorOccurred(CeeeWindowHandle tab_handle,
+ BSTR url,
+ int frame_id,
+ BSTR error,
+ const base::Time& time_stamp);
+
+ private:
+ DISALLOW_COPY_AND_ASSIGN(WebNavigationEventsFunnel);
+};
+
+#endif // CEEE_IE_PLUGIN_BHO_WEBNAVIGATION_EVENTS_FUNNEL_H_
diff --git a/ceee/ie/plugin/bho/webnavigation_events_funnel_unittest.cc b/ceee/ie/plugin/bho/webnavigation_events_funnel_unittest.cc
new file mode 100644
index 0000000..bc7d7f9b
--- /dev/null
+++ b/ceee/ie/plugin/bho/webnavigation_events_funnel_unittest.cc
@@ -0,0 +1,180 @@
+// 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.
+//
+// Unit tests for WebNavigationEventsFunnel.
+
+#include <atlcomcli.h>
+
+#include "base/time.h"
+#include "base/values.h"
+#include "ceee/ie/plugin/bho/webnavigation_events_funnel.h"
+#include "chrome/browser/extensions/extension_webnavigation_api_constants.h"
+#include "gmock/gmock.h"
+#include "gtest/gtest.h"
+
+
+namespace keys = extension_webnavigation_api_constants;
+
+namespace {
+
+using testing::Return;
+using testing::StrEq;
+
+MATCHER_P(ValuesEqual, value, "") {
+ return arg.Equals(value);
+}
+
+class TestWebNavigationEventsFunnel : public WebNavigationEventsFunnel {
+ public:
+ MOCK_METHOD2(SendEvent, HRESULT(const char*, const Value&));
+};
+
+TEST(WebNavigationEventsFunnelTest, OnBeforeNavigate) {
+ TestWebNavigationEventsFunnel webnavigation_events_funnel;
+
+ int tab_handle = 256;
+ std::string url("http://www.google.com/");
+ int frame_id = 512;
+ int request_id = 1024;
+ base::Time time_stamp = base::Time::FromDoubleT(2048.0);
+
+ DictionaryValue args;
+ args.SetInteger(keys::kTabIdKey, tab_handle);
+ args.SetString(keys::kUrlKey, url);
+ args.SetInteger(keys::kFrameIdKey, frame_id);
+ args.SetInteger(keys::kRequestIdKey, request_id);
+ args.SetReal(keys::kTimeStampKey,
+ base::Time::kMillisecondsPerSecond * time_stamp.ToDoubleT());
+
+ EXPECT_CALL(webnavigation_events_funnel,
+ SendEvent(StrEq(keys::kOnBeforeNavigate), ValuesEqual(&args)))
+ .WillOnce(Return(S_OK));
+ EXPECT_HRESULT_SUCCEEDED(webnavigation_events_funnel.OnBeforeNavigate(
+ static_cast<CeeeWindowHandle>(tab_handle), CComBSTR(url.c_str()),
+ frame_id, request_id, time_stamp));
+}
+
+TEST(WebNavigationEventsFunnelTest, OnBeforeRetarget) {
+ TestWebNavigationEventsFunnel webnavigation_events_funnel;
+
+ int source_tab_handle = 256;
+ std::string source_url("http://docs.google.com/");
+ std::string target_url("http://calendar.google.com/");
+ base::Time time_stamp = base::Time::FromDoubleT(2048.0);
+
+ DictionaryValue args;
+ args.SetInteger(keys::kSourceTabIdKey, source_tab_handle);
+ args.SetString(keys::kSourceUrlKey, source_url);
+ args.SetString(keys::kTargetUrlKey, target_url);
+ args.SetReal(keys::kTimeStampKey,
+ base::Time::kMillisecondsPerSecond * time_stamp.ToDoubleT());
+
+ EXPECT_CALL(webnavigation_events_funnel,
+ SendEvent(StrEq(keys::kOnBeforeRetarget), ValuesEqual(&args)))
+ .WillOnce(Return(S_OK));
+ EXPECT_HRESULT_SUCCEEDED(webnavigation_events_funnel.OnBeforeRetarget(
+ static_cast<CeeeWindowHandle>(source_tab_handle),
+ CComBSTR(source_url.c_str()), CComBSTR(target_url.c_str()), time_stamp));
+}
+
+TEST(WebNavigationEventsFunnelTest, OnCommitted) {
+ TestWebNavigationEventsFunnel webnavigation_events_funnel;
+
+ int tab_handle = 256;
+ std::string url("http://mail.google.com/");
+ int frame_id = 512;
+ std::string transition_type("link");
+ std::string transition_qualifiers("client_redirect");
+ base::Time time_stamp = base::Time::FromDoubleT(2048.0);
+
+ DictionaryValue args;
+ args.SetInteger(keys::kTabIdKey, tab_handle);
+ args.SetString(keys::kUrlKey, url);
+ args.SetInteger(keys::kFrameIdKey, frame_id);
+ args.SetString(keys::kTransitionTypeKey, transition_type);
+ args.SetString(keys::kTransitionQualifiersKey, transition_qualifiers);
+ args.SetReal(keys::kTimeStampKey,
+ base::Time::kMillisecondsPerSecond * time_stamp.ToDoubleT());
+
+ EXPECT_CALL(webnavigation_events_funnel,
+ SendEvent(StrEq(keys::kOnCommitted), ValuesEqual(&args)))
+ .WillOnce(Return(S_OK));
+ EXPECT_HRESULT_SUCCEEDED(webnavigation_events_funnel.OnCommitted(
+ static_cast<CeeeWindowHandle>(tab_handle), CComBSTR(url.c_str()),
+ frame_id, transition_type.c_str(), transition_qualifiers.c_str(),
+ time_stamp));
+}
+
+TEST(WebNavigationEventsFunnelTest, OnCompleted) {
+ TestWebNavigationEventsFunnel webnavigation_events_funnel;
+
+ int tab_handle = 256;
+ std::string url("http://groups.google.com/");
+ int frame_id = 512;
+ base::Time time_stamp = base::Time::FromDoubleT(2048.0);
+
+ DictionaryValue args;
+ args.SetInteger(keys::kTabIdKey, tab_handle);
+ args.SetString(keys::kUrlKey, url);
+ args.SetInteger(keys::kFrameIdKey, frame_id);
+ args.SetReal(keys::kTimeStampKey,
+ base::Time::kMillisecondsPerSecond * time_stamp.ToDoubleT());
+
+ EXPECT_CALL(webnavigation_events_funnel,
+ SendEvent(StrEq(keys::kOnCompleted), ValuesEqual(&args)))
+ .WillOnce(Return(S_OK));
+ EXPECT_HRESULT_SUCCEEDED(webnavigation_events_funnel.OnCompleted(
+ static_cast<CeeeWindowHandle>(tab_handle), CComBSTR(url.c_str()),
+ frame_id, time_stamp));
+}
+
+TEST(WebNavigationEventsFunnelTest, OnDOMContentLoaded) {
+ TestWebNavigationEventsFunnel webnavigation_events_funnel;
+
+ int tab_handle = 256;
+ std::string url("http://mail.google.com/");
+ int frame_id = 512;
+ base::Time time_stamp = base::Time::FromDoubleT(2048.0);
+
+ DictionaryValue args;
+ args.SetInteger(keys::kTabIdKey, tab_handle);
+ args.SetString(keys::kUrlKey, url);
+ args.SetInteger(keys::kFrameIdKey, frame_id);
+ args.SetReal(keys::kTimeStampKey,
+ base::Time::kMillisecondsPerSecond * time_stamp.ToDoubleT());
+
+ EXPECT_CALL(webnavigation_events_funnel,
+ SendEvent(StrEq(keys::kOnDOMContentLoaded), ValuesEqual(&args)))
+ .WillOnce(Return(S_OK));
+ EXPECT_HRESULT_SUCCEEDED(webnavigation_events_funnel.OnDOMContentLoaded(
+ static_cast<CeeeWindowHandle>(tab_handle), CComBSTR(url.c_str()),
+ frame_id, time_stamp));
+}
+
+TEST(WebNavigationEventsFunnelTest, OnErrorOccurred) {
+ TestWebNavigationEventsFunnel webnavigation_events_funnel;
+
+ int tab_handle = 256;
+ std::string url("http://mail.google.com/");
+ int frame_id = 512;
+ std::string error("not a valid URL");
+ base::Time time_stamp = base::Time::FromDoubleT(2048.0);
+
+ DictionaryValue args;
+ args.SetInteger(keys::kTabIdKey, tab_handle);
+ args.SetString(keys::kUrlKey, url);
+ args.SetInteger(keys::kFrameIdKey, frame_id);
+ args.SetString(keys::kErrorKey, error);
+ args.SetReal(keys::kTimeStampKey,
+ base::Time::kMillisecondsPerSecond * time_stamp.ToDoubleT());
+
+ EXPECT_CALL(webnavigation_events_funnel,
+ SendEvent(StrEq(keys::kOnErrorOccurred), ValuesEqual(&args)))
+ .WillOnce(Return(S_OK));
+ EXPECT_HRESULT_SUCCEEDED(webnavigation_events_funnel.OnErrorOccurred(
+ static_cast<CeeeWindowHandle>(tab_handle), CComBSTR(url.c_str()),
+ frame_id, CComBSTR(error.c_str()), time_stamp));
+}
+
+} // namespace
diff --git a/ceee/ie/plugin/bho/webrequest_events_funnel.cc b/ceee/ie/plugin/bho/webrequest_events_funnel.cc
new file mode 100644
index 0000000..c0d5b1e
--- /dev/null
+++ b/ceee/ie/plugin/bho/webrequest_events_funnel.cc
@@ -0,0 +1,106 @@
+// 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.
+//
+// Funnel of Chrome Extension Events from whereever through the Broker.
+
+#include "ceee/ie/plugin/bho/webrequest_events_funnel.h"
+
+#include "base/logging.h"
+#include "base/values.h"
+#include "chrome/browser/extensions/extension_webrequest_api_constants.h"
+
+namespace keys = extension_webrequest_api_constants;
+
+namespace {
+
+double MilliSecondsFromTime(const base::Time& time) {
+ return base::Time::kMillisecondsPerSecond * time.ToDoubleT();
+}
+
+} // namespace
+
+HRESULT WebRequestEventsFunnel::OnBeforeRedirect(int request_id,
+ const wchar_t* url,
+ DWORD status_code,
+ const wchar_t* redirect_url,
+ const base::Time& time_stamp) {
+ DictionaryValue args;
+ args.SetInteger(keys::kRequestIdKey, request_id);
+ args.SetString(keys::kUrlKey, url);
+ args.SetInteger(keys::kStatusCodeKey, status_code);
+ args.SetString(keys::kRedirectUrlKey, redirect_url);
+ args.SetReal(keys::kTimeStampKey, MilliSecondsFromTime(time_stamp));
+
+ return SendEvent(keys::kOnBeforeRedirect, args);
+}
+
+HRESULT WebRequestEventsFunnel::OnBeforeRequest(int request_id,
+ const wchar_t* url,
+ const char* method,
+ CeeeWindowHandle tab_handle,
+ const char* type,
+ const base::Time& time_stamp) {
+ DictionaryValue args;
+ args.SetInteger(keys::kRequestIdKey, request_id);
+ args.SetString(keys::kUrlKey, url);
+ args.SetString(keys::kMethodKey, method);
+ args.SetInteger(keys::kTabIdKey, static_cast<int>(tab_handle));
+ args.SetString(keys::kTypeKey, type);
+ args.SetReal(keys::kTimeStampKey, MilliSecondsFromTime(time_stamp));
+
+ return SendEvent(keys::kOnBeforeRequest, args);
+}
+
+HRESULT WebRequestEventsFunnel::OnCompleted(int request_id,
+ const wchar_t* url,
+ DWORD status_code,
+ const base::Time& time_stamp) {
+ DictionaryValue args;
+ args.SetInteger(keys::kRequestIdKey, request_id);
+ args.SetString(keys::kUrlKey, url);
+ args.SetInteger(keys::kStatusCodeKey, status_code);
+ args.SetReal(keys::kTimeStampKey, MilliSecondsFromTime(time_stamp));
+
+ return SendEvent(keys::kOnCompleted, args);
+}
+
+HRESULT WebRequestEventsFunnel::OnErrorOccurred(int request_id,
+ const wchar_t* url,
+ const wchar_t* error,
+ const base::Time& time_stamp) {
+ DictionaryValue args;
+ args.SetInteger(keys::kRequestIdKey, request_id);
+ args.SetString(keys::kUrlKey, url);
+ args.SetString(keys::kErrorKey, error);
+ args.SetReal(keys::kTimeStampKey, MilliSecondsFromTime(time_stamp));
+
+ return SendEvent(keys::kOnErrorOccurred, args);
+}
+
+HRESULT WebRequestEventsFunnel::OnHeadersReceived(
+ int request_id,
+ const wchar_t* url,
+ DWORD status_code,
+ const base::Time& time_stamp) {
+ DictionaryValue args;
+ args.SetInteger(keys::kRequestIdKey, request_id);
+ args.SetString(keys::kUrlKey, url);
+ args.SetInteger(keys::kStatusCodeKey, status_code);
+ args.SetReal(keys::kTimeStampKey, MilliSecondsFromTime(time_stamp));
+
+ return SendEvent(keys::kOnHeadersReceived, args);
+}
+
+HRESULT WebRequestEventsFunnel::OnRequestSent(int request_id,
+ const wchar_t* url,
+ const char* ip,
+ const base::Time& time_stamp) {
+ DictionaryValue args;
+ args.SetInteger(keys::kRequestIdKey, request_id);
+ args.SetString(keys::kUrlKey, url);
+ args.SetString(keys::kIpKey, ip);
+ args.SetReal(keys::kTimeStampKey, MilliSecondsFromTime(time_stamp));
+
+ return SendEvent(keys::kOnRequestSent, args);
+}
diff --git a/ceee/ie/plugin/bho/webrequest_events_funnel.h b/ceee/ie/plugin/bho/webrequest_events_funnel.h
new file mode 100644
index 0000000..55879cc
--- /dev/null
+++ b/ceee/ie/plugin/bho/webrequest_events_funnel.h
@@ -0,0 +1,98 @@
+// 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.
+//
+// Funnel of Chrome Extension Web Request Events.
+
+#ifndef CEEE_IE_PLUGIN_BHO_WEBREQUEST_EVENTS_FUNNEL_H_
+#define CEEE_IE_PLUGIN_BHO_WEBREQUEST_EVENTS_FUNNEL_H_
+
+#include <atlcomcli.h>
+
+#include "base/time.h"
+#include "ceee/ie/plugin/bho/events_funnel.h"
+
+#include "toolband.h" // NOLINT
+
+// Implements a set of methods to send web request related events to the
+// Broker.
+class WebRequestEventsFunnel : public EventsFunnel {
+ public:
+ WebRequestEventsFunnel() : EventsFunnel(false) {}
+
+ // Sends the webRequest.onBeforeRedirect event to the broker.
+ // @param request_id The ID of the request.
+ // @param url The URL of the current request.
+ // @param status_code Standard HTTP status code returned by the server.
+ // @param redirect_url The new URL.
+ // @param time_stamp The time when the browser was about to make the redirect.
+ virtual HRESULT OnBeforeRedirect(int request_id,
+ const wchar_t* url,
+ DWORD status_code,
+ const wchar_t* redirect_url,
+ const base::Time& time_stamp);
+
+ // Sends the webRequest.onBeforeRequest event to the broker.
+ // @param request_id The ID of the request.
+ // @param url The URL of the request.
+ // @param method Standard HTTP method, such as "GET" or "POST".
+ // @param tab_handle The window handle of the tab in which the request takes
+ // place. Set to INVALID_HANDLE_VALUE if the request isn't related to a
+ // tab.
+ // @param type How the requested resource will be used, such as "main_frame"
+ // or "sub_frame". Please find the complete list and explanation on
+ // http://www.chromium.org/developers/design-documents/extensions/notifications-of-web-request-and-navigation
+ // @param time_stamp The time when the browser was about to make the request.
+ virtual HRESULT OnBeforeRequest(int request_id,
+ const wchar_t* url,
+ const char* method,
+ CeeeWindowHandle tab_handle,
+ const char* type,
+ const base::Time& time_stamp);
+
+ // Sends the webRequest.onCompleted event to the broker.
+ // @param request_id The ID of the request.
+ // @param url The URL of the request.
+ // @param status_code Standard HTTP status code returned by the server.
+ // @param time_stamp The time when the response was received completely.
+ virtual HRESULT OnCompleted(int request_id,
+ const wchar_t* url,
+ DWORD status_code,
+ const base::Time& time_stamp);
+
+ // Sends the webRequest.onErrorOccurred event to the broker.
+ // @param request_id The ID of the request.
+ // @param url The URL of the request.
+ // @param error The error description.
+ // @param time_stamp The time when the error occurred.
+ virtual HRESULT OnErrorOccurred(int request_id,
+ const wchar_t* url,
+ const wchar_t* error,
+ const base::Time& time_stamp);
+
+ // Sends the webRequest.onHeadersReceived event to the broker.
+ // @param request_id The ID of the request.
+ // @param url The URL of the request.
+ // @param status_code Standard HTTP status code returned by the server.
+ // @param time_stamp The time when the status line and response headers were
+ // received.
+ virtual HRESULT OnHeadersReceived(int request_id,
+ const wchar_t* url,
+ DWORD status_code,
+ const base::Time& time_stamp);
+
+ // Sends the webRequest.onRequestSent event to the broker.
+ // @param request_id The ID of the request.
+ // @param url The URL of the request.
+ // @param ip The server IP address that is actually connected to.
+ // @param time_stamp The time when the browser finished sending the request.
+ virtual HRESULT OnRequestSent(int request_id,
+ const wchar_t* url,
+ const char* ip,
+ const base::Time& time_stamp);
+
+ private:
+ DISALLOW_COPY_AND_ASSIGN(WebRequestEventsFunnel);
+};
+
+#endif // CEEE_IE_PLUGIN_BHO_WEBREQUEST_EVENTS_FUNNEL_H_
diff --git a/ceee/ie/plugin/bho/webrequest_events_funnel_unittest.cc b/ceee/ie/plugin/bho/webrequest_events_funnel_unittest.cc
new file mode 100644
index 0000000..b56a35c
--- /dev/null
+++ b/ceee/ie/plugin/bho/webrequest_events_funnel_unittest.cc
@@ -0,0 +1,171 @@
+// 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.
+//
+// Unit tests for WebRequestEventsFunnel.
+
+#include <atlcomcli.h>
+
+#include "base/time.h"
+#include "base/values.h"
+#include "ceee/ie/plugin/bho/webrequest_events_funnel.h"
+#include "chrome/browser/extensions/extension_webrequest_api_constants.h"
+#include "gmock/gmock.h"
+#include "gtest/gtest.h"
+
+namespace keys = extension_webrequest_api_constants;
+
+namespace {
+
+using testing::Return;
+using testing::StrEq;
+
+MATCHER_P(ValuesEqual, value, "") {
+ return arg.Equals(value);
+}
+
+class TestWebRequestEventsFunnel : public WebRequestEventsFunnel {
+ public:
+ MOCK_METHOD2(SendEvent, HRESULT(const char*, const Value&));
+};
+
+TEST(WebRequestEventsFunnelTest, OnBeforeRedirect) {
+ TestWebRequestEventsFunnel webrequest_events_funnel;
+
+ int request_id = 256;
+ std::wstring url(L"http://www.google.com/");
+ DWORD status_code = 200;
+ std::wstring redirect_url(L"http://mail.google.com/");
+ base::Time time_stamp = base::Time::FromDoubleT(2048.0);
+
+ DictionaryValue args;
+ args.SetInteger(keys::kRequestIdKey, request_id);
+ args.SetString(keys::kUrlKey, url);
+ args.SetInteger(keys::kStatusCodeKey, status_code);
+ args.SetString(keys::kRedirectUrlKey, redirect_url);
+ args.SetReal(keys::kTimeStampKey,
+ base::Time::kMillisecondsPerSecond * time_stamp.ToDoubleT());
+
+ EXPECT_CALL(webrequest_events_funnel,
+ SendEvent(StrEq(keys::kOnBeforeRedirect), ValuesEqual(&args)))
+ .WillOnce(Return(S_OK));
+ EXPECT_HRESULT_SUCCEEDED(webrequest_events_funnel.OnBeforeRedirect(
+ request_id, url.c_str(), status_code, redirect_url.c_str(), time_stamp));
+}
+
+TEST(WebRequestEventsFunnelTest, OnBeforeRequest) {
+ TestWebRequestEventsFunnel webrequest_events_funnel;
+
+ int request_id = 256;
+ std::wstring url(L"http://calendar.google.com/");
+ std::string method("GET");
+ int tab_handle = 512;
+ std::string type("main_frame");
+ base::Time time_stamp = base::Time::FromDoubleT(2048.0);
+
+ DictionaryValue args;
+ args.SetInteger(keys::kRequestIdKey, request_id);
+ args.SetString(keys::kUrlKey, url);
+ args.SetString(keys::kMethodKey, method);
+ args.SetInteger(keys::kTabIdKey, tab_handle);
+ args.SetString(keys::kTypeKey, type);
+ args.SetReal(keys::kTimeStampKey,
+ base::Time::kMillisecondsPerSecond * time_stamp.ToDoubleT());
+
+ EXPECT_CALL(webrequest_events_funnel,
+ SendEvent(StrEq(keys::kOnBeforeRequest), ValuesEqual(&args)))
+ .WillOnce(Return(S_OK));
+ EXPECT_HRESULT_SUCCEEDED(webrequest_events_funnel.OnBeforeRequest(
+ request_id, url.c_str(), method.c_str(),
+ static_cast<CeeeWindowHandle>(tab_handle), type.c_str(), time_stamp));
+}
+
+TEST(WebRequestEventsFunnelTest, OnCompleted) {
+ TestWebRequestEventsFunnel webrequest_events_funnel;
+
+ int request_id = 256;
+ std::wstring url(L"http://image.google.com/");
+ DWORD status_code = 404;
+ base::Time time_stamp = base::Time::FromDoubleT(2048.0);
+
+ DictionaryValue args;
+ args.SetInteger(keys::kRequestIdKey, request_id);
+ args.SetString(keys::kUrlKey, url);
+ args.SetInteger(keys::kStatusCodeKey, status_code);
+ args.SetReal(keys::kTimeStampKey,
+ base::Time::kMillisecondsPerSecond * time_stamp.ToDoubleT());
+
+ EXPECT_CALL(webrequest_events_funnel,
+ SendEvent(StrEq(keys::kOnCompleted), ValuesEqual(&args)))
+ .WillOnce(Return(S_OK));
+ EXPECT_HRESULT_SUCCEEDED(webrequest_events_funnel.OnCompleted(
+ request_id, url.c_str(), status_code, time_stamp));
+}
+
+TEST(WebRequestEventsFunnelTest, OnErrorOccurred) {
+ TestWebRequestEventsFunnel webrequest_events_funnel;
+
+ int request_id = 256;
+ std::wstring url(L"http://docs.google.com/");
+ std::wstring error(L"cannot resolve the host");
+ base::Time time_stamp = base::Time::FromDoubleT(2048.0);
+
+ DictionaryValue args;
+ args.SetInteger(keys::kRequestIdKey, request_id);
+ args.SetString(keys::kUrlKey, url);
+ args.SetString(keys::kErrorKey, error);
+ args.SetReal(keys::kTimeStampKey,
+ base::Time::kMillisecondsPerSecond * time_stamp.ToDoubleT());
+
+ EXPECT_CALL(webrequest_events_funnel,
+ SendEvent(StrEq(keys::kOnErrorOccurred), ValuesEqual(&args)))
+ .WillOnce(Return(S_OK));
+ EXPECT_HRESULT_SUCCEEDED(webrequest_events_funnel.OnErrorOccurred(
+ request_id, url.c_str(), error.c_str(), time_stamp));
+}
+
+TEST(WebRequestEventsFunnelTest, OnHeadersReceived) {
+ TestWebRequestEventsFunnel webrequest_events_funnel;
+
+ int request_id = 256;
+ std::wstring url(L"http://news.google.com/");
+ DWORD status_code = 200;
+ base::Time time_stamp = base::Time::FromDoubleT(2048.0);
+
+ DictionaryValue args;
+ args.SetInteger(keys::kRequestIdKey, request_id);
+ args.SetString(keys::kUrlKey, url);
+ args.SetInteger(keys::kStatusCodeKey, status_code);
+ args.SetReal(keys::kTimeStampKey,
+ base::Time::kMillisecondsPerSecond * time_stamp.ToDoubleT());
+
+ EXPECT_CALL(webrequest_events_funnel,
+ SendEvent(StrEq(keys::kOnHeadersReceived), ValuesEqual(&args)))
+ .WillOnce(Return(S_OK));
+ EXPECT_HRESULT_SUCCEEDED(webrequest_events_funnel.OnHeadersReceived(
+ request_id, url.c_str(), status_code, time_stamp));
+}
+
+TEST(WebRequestEventsFunnelTest, OnRequestSent) {
+ TestWebRequestEventsFunnel webrequest_events_funnel;
+
+ int request_id = 256;
+ std::wstring url(L"http://finance.google.com/");
+ std::string ip("127.0.0.1");
+ base::Time time_stamp = base::Time::FromDoubleT(2048.0);
+
+ DictionaryValue args;
+ args.SetInteger(keys::kRequestIdKey, request_id);
+ args.SetString(keys::kUrlKey, url);
+ args.SetString(keys::kIpKey, ip);
+ args.SetReal(keys::kTimeStampKey,
+ base::Time::kMillisecondsPerSecond * time_stamp.ToDoubleT());
+
+ EXPECT_CALL(webrequest_events_funnel,
+ SendEvent(StrEq(keys::kOnRequestSent), ValuesEqual(&args)))
+ .WillOnce(Return(S_OK));
+ EXPECT_HRESULT_SUCCEEDED(webrequest_events_funnel.OnRequestSent(
+ request_id, url.c_str(), ip.c_str(), time_stamp));
+}
+
+} // namespace
diff --git a/ceee/ie/plugin/bho/webrequest_notifier.cc b/ceee/ie/plugin/bho/webrequest_notifier.cc
new file mode 100644
index 0000000..e0449e4
--- /dev/null
+++ b/ceee/ie/plugin/bho/webrequest_notifier.cc
@@ -0,0 +1,811 @@
+// 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.
+//
+// Web request notifier implementation.
+#include "ceee/ie/plugin/bho/webrequest_notifier.h"
+
+#include "base/logging.h"
+#include "base/scoped_ptr.h"
+#include "chrome_frame/function_stub.h"
+#include "chrome_frame/utils.h"
+
+namespace {
+
+const wchar_t kUrlMonModuleName[] = L"urlmon.dll";
+const char kWinINetModuleName[] = "wininet.dll";
+const char kInternetSetStatusCallbackAFunctionName[] =
+ "InternetSetStatusCallbackA";
+const char kInternetSetStatusCallbackWFunctionName[] =
+ "InternetSetStatusCallbackW";
+const char kInternetConnectAFunctionName[] = "InternetConnectA";
+const char kInternetConnectWFunctionName[] = "InternetConnectW";
+const char kHttpOpenRequestAFunctionName[] = "HttpOpenRequestA";
+const char kHttpOpenRequestWFunctionName[] = "HttpOpenRequestW";
+const char kHttpSendRequestAFunctionName[] = "HttpSendRequestA";
+const char kHttpSendRequestWFunctionName[] = "HttpSendRequestW";
+const char kInternetReadFileFunctionName[] = "InternetReadFile";
+
+} // namespace
+
+WebRequestNotifier::WebRequestNotifier()
+ : internet_status_callback_stub_(NULL),
+ start_count_(0),
+ initialize_state_(NOT_INITIALIZED) {
+}
+
+WebRequestNotifier::~WebRequestNotifier() {
+ DCHECK_EQ(start_count_, 0);
+}
+
+bool WebRequestNotifier::RequestToStart() {
+ {
+ CComCritSecLock<CComAutoCriticalSection> lock(critical_section_);
+ start_count_++;
+
+ if (initialize_state_ != NOT_INITIALIZED)
+ return initialize_state_ != FAILED_TO_INITIALIZE;
+ initialize_state_ = INITIALIZING;
+ }
+
+ bool success = false;
+ do {
+ // We are not going to unpatch any of the patched WinINet functions or the
+ // status callback function. Instead, we pin our DLL in memory so that all
+ // the patched functions can be accessed until the process goes away.
+ PinModule();
+
+ PatchWinINetFunction(kInternetSetStatusCallbackAFunctionName,
+ &internet_set_status_callback_a_patch_,
+ InternetSetStatusCallbackAPatch);
+ PatchWinINetFunction(kInternetSetStatusCallbackWFunctionName,
+ &internet_set_status_callback_w_patch_,
+ InternetSetStatusCallbackWPatch);
+ if (!HasPatchedOneVersion(internet_set_status_callback_a_patch_,
+ internet_set_status_callback_w_patch_)) {
+ break;
+ }
+
+ PatchWinINetFunction(kInternetConnectAFunctionName,
+ &internet_connect_a_patch_,
+ InternetConnectAPatch);
+ PatchWinINetFunction(kInternetConnectWFunctionName,
+ &internet_connect_w_patch_,
+ InternetConnectWPatch);
+ if (!HasPatchedOneVersion(internet_connect_a_patch_,
+ internet_connect_w_patch_)) {
+ break;
+ }
+
+ PatchWinINetFunction(kHttpOpenRequestAFunctionName,
+ &http_open_request_a_patch_,
+ HttpOpenRequestAPatch);
+ PatchWinINetFunction(kHttpOpenRequestWFunctionName,
+ &http_open_request_w_patch_,
+ HttpOpenRequestWPatch);
+ if (!HasPatchedOneVersion(http_open_request_a_patch_,
+ http_open_request_w_patch_)) {
+ break;
+ }
+
+ PatchWinINetFunction(kHttpSendRequestAFunctionName,
+ &http_send_request_a_patch_,
+ HttpSendRequestAPatch);
+ PatchWinINetFunction(kHttpSendRequestWFunctionName,
+ &http_send_request_w_patch_,
+ HttpSendRequestWPatch);
+ if (!HasPatchedOneVersion(http_send_request_a_patch_,
+ http_send_request_w_patch_)) {
+ break;
+ }
+
+ PatchWinINetFunction(kInternetReadFileFunctionName,
+ &internet_read_file_patch_,
+ InternetReadFilePatch);
+ if (!internet_read_file_patch_.is_patched())
+ break;
+
+ success = true;
+ } while (false);
+
+ {
+ CComCritSecLock<CComAutoCriticalSection> lock(critical_section_);
+ initialize_state_ = success ? SUCCEEDED_TO_INITIALIZE :
+ FAILED_TO_INITIALIZE;
+ }
+ return success;
+}
+
+void WebRequestNotifier::RequestToStop() {
+ CComCritSecLock<CComAutoCriticalSection> lock(critical_section_);
+ if (start_count_ <= 0) {
+ NOTREACHED();
+ return;
+ }
+
+ start_count_--;
+ if (start_count_ == 0) {
+ // It is supposed that every handle must be closed using
+ // InternetCloseHandle(). However, IE seems to leak handles in some (rare)
+ // cases. For example, when the current page is a JPG or GIF image and the
+ // user refreshes the page.
+ // If that happens, the server_map_ and request_map_ won't be empty.
+ LOG_IF(WARNING, !server_map_.empty() || !request_map_.empty())
+ << "There are Internet handles that haven't been closed when "
+ << "WebRequestNotifier stops.";
+
+ for (RequestMap::iterator iter = request_map_.begin();
+ iter != request_map_.end(); ++iter) {
+ TransitRequestToNextState(RequestInfo::ERROR_OCCURRED, &iter->second);
+ }
+
+ server_map_.clear();
+ request_map_.clear();
+ }
+}
+
+void WebRequestNotifier::PatchWinINetFunction(
+ const char* name,
+ app::win::IATPatchFunction* patch_function,
+ void* handler) {
+ DWORD error = patch_function->Patch(kUrlMonModuleName, kWinINetModuleName,
+ name, handler);
+ // The patching operation is either successful, or failed cleanly.
+ DCHECK(error == NO_ERROR || !patch_function->is_patched());
+}
+
+INTERNET_STATUS_CALLBACK STDAPICALLTYPE
+ WebRequestNotifier::InternetSetStatusCallbackAPatch(
+ HINTERNET internet,
+ INTERNET_STATUS_CALLBACK callback) {
+ WebRequestNotifier* instance = ProductionWebRequestNotifier::get();
+ INTERNET_STATUS_CALLBACK new_callback =
+ instance->HandleBeforeInternetSetStatusCallback(internet, callback);
+ return ::InternetSetStatusCallbackA(internet, new_callback);
+}
+
+INTERNET_STATUS_CALLBACK STDAPICALLTYPE
+ WebRequestNotifier::InternetSetStatusCallbackWPatch(
+ HINTERNET internet,
+ INTERNET_STATUS_CALLBACK callback) {
+ WebRequestNotifier* instance = ProductionWebRequestNotifier::get();
+ INTERNET_STATUS_CALLBACK new_callback =
+ instance->HandleBeforeInternetSetStatusCallback(internet, callback);
+ return ::InternetSetStatusCallbackW(internet, new_callback);
+}
+
+HINTERNET STDAPICALLTYPE WebRequestNotifier::InternetConnectAPatch(
+ HINTERNET internet,
+ LPCSTR server_name,
+ INTERNET_PORT server_port,
+ LPCSTR user_name,
+ LPCSTR password,
+ DWORD service,
+ DWORD flags,
+ DWORD_PTR context) {
+ WebRequestNotifier* instance = ProductionWebRequestNotifier::get();
+ instance->HandleBeforeInternetConnect(internet);
+
+ HINTERNET server = ::InternetConnectA(internet, server_name, server_port,
+ user_name, password, service, flags,
+ context);
+
+ instance->HandleAfterInternetConnect(server, CA2W(server_name), server_port,
+ service);
+ return server;
+}
+
+HINTERNET STDAPICALLTYPE WebRequestNotifier::InternetConnectWPatch(
+ HINTERNET internet,
+ LPCWSTR server_name,
+ INTERNET_PORT server_port,
+ LPCWSTR user_name,
+ LPCWSTR password,
+ DWORD service,
+ DWORD flags,
+ DWORD_PTR context) {
+ WebRequestNotifier* instance = ProductionWebRequestNotifier::get();
+ instance->HandleBeforeInternetConnect(internet);
+
+ HINTERNET server = ::InternetConnectW(internet, server_name, server_port,
+ user_name, password, service, flags,
+ context);
+
+ instance->HandleAfterInternetConnect(server, server_name, server_port,
+ service);
+ return server;
+}
+
+HINTERNET STDAPICALLTYPE WebRequestNotifier::HttpOpenRequestAPatch(
+ HINTERNET connect,
+ LPCSTR verb,
+ LPCSTR object_name,
+ LPCSTR version,
+ LPCSTR referrer,
+ LPCSTR* accept_types,
+ DWORD flags,
+ DWORD_PTR context) {
+ HINTERNET request = ::HttpOpenRequestA(connect, verb, object_name, version,
+ referrer, accept_types, flags,
+ context);
+
+ WebRequestNotifier* instance = ProductionWebRequestNotifier::get();
+ instance->HandleAfterHttpOpenRequest(connect, request, verb,
+ CA2W(object_name), flags);
+ return request;
+}
+
+HINTERNET STDAPICALLTYPE WebRequestNotifier::HttpOpenRequestWPatch(
+ HINTERNET connect,
+ LPCWSTR verb,
+ LPCWSTR object_name,
+ LPCWSTR version,
+ LPCWSTR referrer,
+ LPCWSTR* accept_types,
+ DWORD flags,
+ DWORD_PTR context) {
+ HINTERNET request = ::HttpOpenRequestW(connect, verb, object_name, version,
+ referrer, accept_types, flags,
+ context);
+
+ WebRequestNotifier* instance = ProductionWebRequestNotifier::get();
+ instance->HandleAfterHttpOpenRequest(connect, request, CW2A(verb),
+ object_name, flags);
+ return request;
+}
+
+BOOL STDAPICALLTYPE WebRequestNotifier::HttpSendRequestAPatch(
+ HINTERNET request,
+ LPCSTR headers,
+ DWORD headers_length,
+ LPVOID optional,
+ DWORD optional_length) {
+ WebRequestNotifier* instance = ProductionWebRequestNotifier::get();
+ instance->HandleBeforeHttpSendRequest(request);
+ return ::HttpSendRequestA(request, headers, headers_length, optional,
+ optional_length);
+}
+
+BOOL STDAPICALLTYPE WebRequestNotifier::HttpSendRequestWPatch(
+ HINTERNET request,
+ LPCWSTR headers,
+ DWORD headers_length,
+ LPVOID optional,
+ DWORD optional_length) {
+ WebRequestNotifier* instance = ProductionWebRequestNotifier::get();
+ instance->HandleBeforeHttpSendRequest(request);
+ return ::HttpSendRequestW(request, headers, headers_length, optional,
+ optional_length);
+}
+
+void CALLBACK WebRequestNotifier::InternetStatusCallbackPatch(
+ INTERNET_STATUS_CALLBACK original,
+ HINTERNET internet,
+ DWORD_PTR context,
+ DWORD internet_status,
+ LPVOID status_information,
+ DWORD status_information_length) {
+ WebRequestNotifier* instance = ProductionWebRequestNotifier::get();
+ instance->HandleBeforeInternetStatusCallback(original, internet, context,
+ internet_status,
+ status_information,
+ status_information_length);
+ original(internet, context, internet_status, status_information,
+ status_information_length);
+}
+
+BOOL STDAPICALLTYPE WebRequestNotifier::InternetReadFilePatch(
+ HINTERNET file,
+ LPVOID buffer,
+ DWORD number_of_bytes_to_read,
+ LPDWORD number_of_bytes_read) {
+ BOOL result = ::InternetReadFile(file, buffer, number_of_bytes_to_read,
+ number_of_bytes_read);
+ WebRequestNotifier* instance = ProductionWebRequestNotifier::get();
+ instance->HandleAfterInternetReadFile(file, result, number_of_bytes_read);
+
+ return result;
+}
+
+INTERNET_STATUS_CALLBACK
+ WebRequestNotifier::HandleBeforeInternetSetStatusCallback(
+ HINTERNET internet,
+ INTERNET_STATUS_CALLBACK internet_callback) {
+ CComCritSecLock<CComAutoCriticalSection> lock(critical_section_);
+ if (IsNotRunning())
+ return internet_callback;
+
+ if (internet_callback == NULL)
+ return NULL;
+
+ if (internet_status_callback_stub_ == NULL) {
+ return CreateInternetStatusCallbackStub(internet_callback);
+ } else {
+ if (internet_status_callback_stub_->argument() !=
+ reinterpret_cast<uintptr_t>(internet_callback)) {
+ NOTREACHED();
+ return CreateInternetStatusCallbackStub(internet_callback);
+ } else {
+ return reinterpret_cast<INTERNET_STATUS_CALLBACK>(
+ internet_status_callback_stub_->code());
+ }
+ }
+}
+
+void WebRequestNotifier::HandleBeforeInternetConnect(HINTERNET internet) {
+ CComCritSecLock<CComAutoCriticalSection> lock(critical_section_);
+ if (IsNotRunning())
+ return;
+
+ if (internet_status_callback_stub_ == NULL) {
+ INTERNET_STATUS_CALLBACK original_callback =
+ ::InternetSetStatusCallbackA(internet, NULL);
+ if (original_callback != NULL) {
+ INTERNET_STATUS_CALLBACK new_callback = CreateInternetStatusCallbackStub(
+ original_callback);
+ ::InternetSetStatusCallbackA(internet, new_callback);
+ }
+ }
+}
+
+// NOTE: this method must be called within a lock.
+INTERNET_STATUS_CALLBACK WebRequestNotifier::CreateInternetStatusCallbackStub(
+ INTERNET_STATUS_CALLBACK original_callback) {
+ DCHECK(original_callback != NULL);
+
+ internet_status_callback_stub_ = FunctionStub::Create(
+ reinterpret_cast<uintptr_t>(original_callback),
+ InternetStatusCallbackPatch);
+ // internet_status_callback_stub_ is not NULL if the function stub is
+ // successfully created.
+ if (internet_status_callback_stub_ != NULL) {
+ return reinterpret_cast<INTERNET_STATUS_CALLBACK>(
+ internet_status_callback_stub_->code());
+ } else {
+ NOTREACHED();
+ return original_callback;
+ }
+}
+
+void WebRequestNotifier::HandleAfterInternetConnect(HINTERNET server,
+ const wchar_t* server_name,
+ INTERNET_PORT server_port,
+ DWORD service) {
+ if (service != INTERNET_SERVICE_HTTP || server == NULL ||
+ IS_INTRESOURCE(server_name) || wcslen(server_name) == 0) {
+ return;
+ }
+
+ CComCritSecLock<CComAutoCriticalSection> lock(critical_section_);
+ if (IsNotRunning())
+ return;
+
+ // It is not possible that the same connection handle is opened more than
+ // once.
+ DCHECK(server_map_.find(server) == server_map_.end());
+
+ ServerInfo server_info;
+ server_info.server_name = server_name;
+ server_info.server_port = server_port;
+
+ server_map_.insert(
+ std::make_pair<HINTERNET, ServerInfo>(server, server_info));
+}
+
+void WebRequestNotifier::HandleAfterHttpOpenRequest(HINTERNET server,
+ HINTERNET request,
+ const char* method,
+ const wchar_t* path,
+ DWORD flags) {
+ if (server == NULL || request == NULL)
+ return;
+
+ CComCritSecLock<CComAutoCriticalSection> lock(critical_section_);
+ if (IsNotRunning())
+ return;
+
+ // It is not possible that the same request handle is opened more than once.
+ DCHECK(request_map_.find(request) == request_map_.end());
+
+ ServerMap::iterator server_iter = server_map_.find(server);
+ // It is possible to find that we haven't recorded the connection handle to
+ // the server, if we patch WinINet functions after the InternetConnect call.
+ // In that case, we will ignore all events related to requests happening on
+ // that connection.
+ if (server_iter == server_map_.end())
+ return;
+
+ RequestInfo request_info;
+ // TODO(yzshen@google.com): create the request ID.
+ request_info.server_handle = server;
+ request_info.method = method == NULL ? "GET" : method;
+ if (!ConstructUrl((flags & INTERNET_FLAG_SECURE) != 0,
+ server_iter->second.server_name.c_str(),
+ server_iter->second.server_port,
+ path,
+ &request_info.url)) {
+ NOTREACHED();
+ return;
+ }
+
+ request_map_.insert(
+ std::make_pair<HINTERNET, RequestInfo>(request, request_info));
+}
+
+void WebRequestNotifier::HandleBeforeHttpSendRequest(HINTERNET request) {
+ CComCritSecLock<CComAutoCriticalSection> lock(critical_section_);
+ if (IsNotRunning())
+ return;
+
+ RequestMap::iterator request_iter = request_map_.find(request);
+ if (request_iter != request_map_.end()) {
+ request_iter->second.before_request_time = base::Time::Now();
+ TransitRequestToNextState(RequestInfo::WILL_NOTIFY_BEFORE_REQUEST,
+ &request_iter->second);
+ }
+}
+
+void WebRequestNotifier::HandleBeforeInternetStatusCallback(
+ INTERNET_STATUS_CALLBACK original,
+ HINTERNET internet,
+ DWORD_PTR context,
+ DWORD internet_status,
+ LPVOID status_information,
+ DWORD status_information_length) {
+ switch (internet_status) {
+ case INTERNET_STATUS_HANDLE_CLOSING: {
+ CComCritSecLock<CComAutoCriticalSection> lock(critical_section_);
+ if (IsNotRunning())
+ return;
+
+ // We don't know whether we are closing a server or a request handle. As a
+ // result, we have to test both server_map_ and request_map_.
+ ServerMap::iterator server_iter = server_map_.find(internet);
+ if (server_iter != server_map_.end()) {
+ server_map_.erase(server_iter);
+ } else {
+ RequestMap::iterator request_iter = request_map_.find(internet);
+ if (request_iter != request_map_.end()) {
+ // TODO(yzshen@google.com): For now, we don't bother
+ // checking whether the content of the response has
+ // completed downloading in this case. Have to make
+ // improvement if the requirement for more accurate
+ // webRequest.onCompleted notifications emerges.
+ TransitRequestToNextState(RequestInfo::NOTIFIED_COMPLETED,
+ &request_iter->second);
+ request_map_.erase(request_iter);
+ }
+ }
+ break;
+ }
+ case INTERNET_STATUS_REQUEST_SENT: {
+ CComCritSecLock<CComAutoCriticalSection> lock(critical_section_);
+ if (IsNotRunning())
+ return;
+
+ RequestMap::iterator request_iter = request_map_.find(internet);
+ if (request_iter != request_map_.end()) {
+ TransitRequestToNextState(RequestInfo::NOTIFIED_REQUEST_SENT,
+ &request_iter->second);
+ }
+ break;
+ }
+ case INTERNET_STATUS_REDIRECT: {
+ DWORD status_code = 0;
+ bool result = QueryHttpInfoNumber(internet, HTTP_QUERY_STATUS_CODE,
+ &status_code);
+ {
+ CComCritSecLock<CComAutoCriticalSection> lock(critical_section_);
+ if (IsNotRunning())
+ return;
+
+ RequestMap::iterator request_iter = request_map_.find(internet);
+ if (request_iter != request_map_.end()) {
+ RequestInfo& info = request_iter->second;
+ if (result) {
+ info.status_code = status_code;
+ TransitRequestToNextState(RequestInfo::NOTIFIED_HEADERS_RECEIVED,
+ &info);
+
+ info.original_url = info.url;
+ info.url = CA2W(reinterpret_cast<PCSTR>(status_information));
+ TransitRequestToNextState(RequestInfo::NOTIFIED_BEFORE_REDIRECT,
+ &info);
+ } else {
+ TransitRequestToNextState(RequestInfo::ERROR_OCCURRED, &info);
+ }
+ }
+ }
+ break;
+ }
+ case INTERNET_STATUS_REQUEST_COMPLETE: {
+ DWORD status_code = 0;
+ DWORD content_length = 0;
+ RequestInfo::MessageLengthType length_type =
+ RequestInfo::UNKNOWN_MESSAGE_LENGTH_TYPE;
+
+ bool result = QueryHttpInfoNumber(internet, HTTP_QUERY_STATUS_CODE,
+ &status_code) &&
+ DetermineMessageLength(internet, status_code,
+ &content_length, &length_type);
+ {
+ CComCritSecLock<CComAutoCriticalSection> lock(critical_section_);
+ if (IsNotRunning())
+ return;
+
+ RequestMap::iterator request_iter = request_map_.find(internet);
+ if (request_iter != request_map_.end() &&
+ request_iter->second.state == RequestInfo::NOTIFIED_REQUEST_SENT) {
+ RequestInfo& info = request_iter->second;
+ if (result) {
+ info.status_code = status_code;
+ info.content_length = content_length;
+ info.length_type = length_type;
+ TransitRequestToNextState(RequestInfo::NOTIFIED_HEADERS_RECEIVED,
+ &info);
+ if (info.length_type == RequestInfo::NO_MESSAGE_BODY)
+ TransitRequestToNextState(RequestInfo::NOTIFIED_COMPLETED, &info);
+ } else {
+ TransitRequestToNextState(RequestInfo::ERROR_OCCURRED, &info);
+ }
+ }
+ }
+ break;
+ }
+ case INTERNET_STATUS_CONNECTED_TO_SERVER: {
+ // TODO(yzshen@google.com): get IP information.
+ break;
+ }
+ case INTERNET_STATUS_SENDING_REQUEST: {
+ CComCritSecLock<CComAutoCriticalSection> lock(critical_section_);
+ if (IsNotRunning())
+ return;
+ // Some HttpSendRequest() calls don't actually make HTTP requests, and the
+ // corresponding request handles are closed right after the calls.
+ // In that case, we ignore them totally, and don't send
+ // webRequest.onBeforeRequest notifications.
+ RequestMap::iterator request_iter = request_map_.find(internet);
+ if (request_iter != request_map_.end() &&
+ request_iter->second.state ==
+ RequestInfo::WILL_NOTIFY_BEFORE_REQUEST) {
+ TransitRequestToNextState(RequestInfo::NOTIFIED_BEFORE_REQUEST,
+ &request_iter->second);
+ }
+ break;
+ }
+ default: {
+ break;
+ }
+ }
+}
+
+void WebRequestNotifier::HandleAfterInternetReadFile(
+ HINTERNET request,
+ BOOL result,
+ LPDWORD number_of_bytes_read) {
+ if (!result || number_of_bytes_read == NULL)
+ return;
+
+ CComCritSecLock<CComAutoCriticalSection> lock(critical_section_);
+ if (IsNotRunning())
+ return;
+
+ RequestMap::iterator iter = request_map_.find(request);
+ if (iter != request_map_.end()) {
+ RequestInfo& info = iter->second;
+ // We don't update the length_type field until we reach the last request
+ // of the redirection chain. As a result, the check below also prevents us
+ // from firing webRequest.onCompleted before the last request in the
+ // chain.
+ if (info.length_type == RequestInfo::CONTENT_LENGTH_HEADER) {
+ info.read_progress += *number_of_bytes_read;
+ if (info.read_progress >= info.content_length &&
+ info.state == RequestInfo::NOTIFIED_HEADERS_RECEIVED) {
+ DCHECK(info.read_progress == info.content_length);
+ TransitRequestToNextState(RequestInfo::NOTIFIED_COMPLETED, &info);
+ }
+ }
+ }
+}
+
+// Currently this method is always called within a lock.
+bool WebRequestNotifier::ConstructUrl(bool https,
+ const wchar_t* server_name,
+ INTERNET_PORT server_port,
+ const wchar_t* path,
+ std::wstring* url) {
+ if (url == NULL || server_name == NULL || wcslen(server_name) == 0)
+ return false;
+
+ url->clear();
+ url->append(https ? L"https://" : L"http://");
+ url->append(server_name);
+
+ bool need_port = server_port != INTERNET_INVALID_PORT_NUMBER &&
+ (https ? server_port != INTERNET_DEFAULT_HTTPS_PORT :
+ server_port != INTERNET_DEFAULT_HTTP_PORT);
+ if (need_port) {
+ static const int kMaxPortLength = 10;
+ wchar_t buffer[kMaxPortLength];
+ if (swprintf(buffer, kMaxPortLength, L":%d", server_port) == -1)
+ return false;
+ url->append(buffer);
+ }
+
+ url->append(path);
+ return true;
+}
+
+bool WebRequestNotifier::QueryHttpInfoNumber(HINTERNET request,
+ DWORD info_flag,
+ DWORD* value) {
+ DCHECK(value != NULL);
+ *value = 0;
+
+ DWORD size = sizeof(info_flag);
+ return ::HttpQueryInfo(request, info_flag | HTTP_QUERY_FLAG_NUMBER, value,
+ &size, NULL) ? true : false;
+}
+
+bool WebRequestNotifier::DetermineMessageLength(
+ HINTERNET request,
+ DWORD status_code,
+ DWORD* length,
+ RequestInfo::MessageLengthType* type) {
+ // Please see http://www.w3.org/Protocols/rfc2616/rfc2616-sec4.html#sec4.2
+ // for how the length of a message is determined.
+
+ DCHECK(length != NULL && type != NULL);
+ *length = 0;
+ *type = RequestInfo::UNKNOWN_MESSAGE_LENGTH_TYPE;
+
+ std::wstring method;
+ // Request methods are case-sensitive.
+ if ((status_code >= 100 && status_code < 200) ||
+ status_code == 204 ||
+ status_code == 304 ||
+ (QueryHttpInfoString(request, HTTP_QUERY_REQUEST_METHOD, &method) &&
+ method == L"HEAD")) {
+ *type = RequestInfo::NO_MESSAGE_BODY;
+ return true;
+ }
+
+ std::wstring transfer_encoding;
+ // All transfer-coding values are case-insensitive.
+ if (QueryHttpInfoString(request, HTTP_QUERY_TRANSFER_ENCODING,
+ &transfer_encoding) &&
+ _wcsicmp(transfer_encoding.c_str(), L"entity") != 0) {
+ *type = RequestInfo::VARIABLE_MESSAGE_LENGTH;
+ return true;
+ }
+
+ DWORD content_length = 0;
+ if (QueryHttpInfoNumber(request, HTTP_QUERY_CONTENT_LENGTH,
+ &content_length)) {
+ *type = RequestInfo::CONTENT_LENGTH_HEADER;
+ *length = content_length;
+ return true;
+ }
+
+ *type = RequestInfo::VARIABLE_MESSAGE_LENGTH;
+ return true;
+}
+
+bool WebRequestNotifier::QueryHttpInfoString(HINTERNET request,
+ DWORD info_flag,
+ std::wstring* value) {
+ DCHECK(value != NULL);
+ value->clear();
+
+ DWORD size = 20;
+ scoped_array<wchar_t> buffer(new wchar_t[size]);
+
+ BOOL result = ::HttpQueryInfo(request, info_flag,
+ reinterpret_cast<LPVOID>(buffer.get()), &size,
+ NULL);
+ if (!result && GetLastError() == ERROR_INSUFFICIENT_BUFFER) {
+ buffer.reset(new wchar_t[size]);
+ result = ::HttpQueryInfo(request, info_flag,
+ reinterpret_cast<LPVOID>(buffer.get()), &size,
+ NULL);
+ }
+ if (!result)
+ return false;
+
+ *value = buffer.get();
+ return true;
+}
+
+// NOTE: this method must be called within a lock.
+void WebRequestNotifier::TransitRequestToNextState(
+ RequestInfo::State next_state,
+ RequestInfo* info) {
+ // TODO(yzshen@google.com): generate and fill in missing parameters
+ // for notifications.
+ DCHECK(info != NULL);
+
+ bool fire_on_error_occurred = false;
+ switch (info->state) {
+ case RequestInfo::BEGIN:
+ if (next_state != RequestInfo::WILL_NOTIFY_BEFORE_REQUEST &&
+ next_state != RequestInfo::ERROR_OCCURRED) {
+ next_state = RequestInfo::ERROR_OCCURRED;
+ // We don't fire webRequest.onErrorOccurred in this case, since the
+ // first event for any request has to be webRequest.onBeforeRequest.
+ }
+ break;
+ case RequestInfo::WILL_NOTIFY_BEFORE_REQUEST:
+ if (next_state == RequestInfo::NOTIFIED_BEFORE_REQUEST) {
+ webrequest_events_funnel().OnBeforeRequest(
+ info->id, info->url.c_str(), info->method.c_str(), info->tab_handle,
+ "other", info->before_request_time);
+ } else if (next_state != RequestInfo::ERROR_OCCURRED) {
+ next_state = RequestInfo::ERROR_OCCURRED;
+ // We don't fire webRequest.onErrorOccurred in this case, since the
+ // first event for any request has to be webRequest.onBeforeRequest.
+ }
+ break;
+ case RequestInfo::NOTIFIED_BEFORE_REQUEST:
+ case RequestInfo::NOTIFIED_BEFORE_REDIRECT:
+ if (next_state == RequestInfo::NOTIFIED_REQUEST_SENT) {
+ webrequest_events_funnel().OnRequestSent(
+ info->id, info->url.c_str(), info->ip.c_str(), base::Time::Now());
+ } else {
+ if (next_state != RequestInfo::ERROR_OCCURRED)
+ next_state = RequestInfo::ERROR_OCCURRED;
+
+ fire_on_error_occurred = true;
+ }
+ break;
+ case RequestInfo::NOTIFIED_REQUEST_SENT:
+ if (next_state == RequestInfo::NOTIFIED_HEADERS_RECEIVED) {
+ webrequest_events_funnel().OnHeadersReceived(
+ info->id, info->url.c_str(), info->status_code, base::Time::Now());
+ } else {
+ if (next_state != RequestInfo::ERROR_OCCURRED)
+ next_state = RequestInfo::ERROR_OCCURRED;
+
+ fire_on_error_occurred = true;
+ }
+ break;
+ case RequestInfo::NOTIFIED_HEADERS_RECEIVED:
+ if (next_state == RequestInfo::NOTIFIED_BEFORE_REDIRECT) {
+ webrequest_events_funnel().OnBeforeRedirect(
+ info->id, info->original_url.c_str(), info->status_code,
+ info->url.c_str(), base::Time::Now());
+ } else if (next_state == RequestInfo::NOTIFIED_COMPLETED) {
+ webrequest_events_funnel().OnCompleted(
+ info->id, info->url.c_str(), info->status_code, base::Time::Now());
+ } else {
+ if (next_state != RequestInfo::ERROR_OCCURRED)
+ next_state = RequestInfo::ERROR_OCCURRED;
+
+ fire_on_error_occurred = true;
+ }
+ break;
+ case RequestInfo::NOTIFIED_COMPLETED:
+ // The webRequest.onCompleted notification is supposed to be the last
+ // event sent for a given request. As a result, if the request is already
+ // in the NOTIFIED_COMPLETED state, we just keep it in that state without
+ // sending any further notification.
+ //
+ // When a request handle is closed, we consider transiting the state to
+ // NOTIFIED_COMPLETED. If there is no response body or we have completed
+ // reading the response body, the request has already been in this state.
+ // In that case, we will hit this code path.
+ next_state = RequestInfo::NOTIFIED_COMPLETED;
+ break;
+ case RequestInfo::ERROR_OCCURRED:
+ next_state = RequestInfo::ERROR_OCCURRED;
+ break;
+ default:
+ NOTREACHED();
+ break;
+ }
+
+ if (fire_on_error_occurred) {
+ webrequest_events_funnel().OnErrorOccurred(
+ info->id, info->url.c_str(), L"", base::Time::Now());
+ }
+ info->state = next_state;
+}
diff --git a/ceee/ie/plugin/bho/webrequest_notifier.h b/ceee/ie/plugin/bho/webrequest_notifier.h
new file mode 100644
index 0000000..2529c42
--- /dev/null
+++ b/ceee/ie/plugin/bho/webrequest_notifier.h
@@ -0,0 +1,453 @@
+// 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.
+//
+// Web request notifier implementation.
+#ifndef CEEE_IE_PLUGIN_BHO_WEBREQUEST_NOTIFIER_H_
+#define CEEE_IE_PLUGIN_BHO_WEBREQUEST_NOTIFIER_H_
+
+#include <atlbase.h>
+#include <wininet.h>
+
+#include <map>
+#include <string>
+
+#include "app/win/iat_patch_function.h"
+#include "base/singleton.h"
+#include "ceee/ie/plugin/bho/webrequest_events_funnel.h"
+#include "toolband.h"
+
+struct FunctionStub;
+
+// WebRequestNotifier monitors HTTP request/response events via WinINet hooks,
+// and sends the events to the broker.
+class WebRequestNotifier {
+ public:
+ // Starts the service if it hasn't been started.
+ // @return Returns true if the service is being initialized or has been
+ // successfully initialized.
+ bool RequestToStart();
+ // Stops the service if it is currently running and nobody is interested in
+ // the service any more. Every call to RequestToStart (even if it failed)
+ // should be paired with a call to RequestToStop.
+ void RequestToStop();
+
+ protected:
+ // Information related to an Internet connection.
+ struct ServerInfo {
+ ServerInfo() : server_port(INTERNET_INVALID_PORT_NUMBER) {}
+
+ // The host name of the server.
+ std::wstring server_name;
+ // The port number on the server.
+ INTERNET_PORT server_port;
+ };
+
+ // Information related to a HTTP request.
+ struct RequestInfo {
+ RequestInfo()
+ : server_handle(NULL),
+ id(-1),
+ tab_handle(reinterpret_cast<CeeeWindowHandle>(INVALID_HANDLE_VALUE)),
+ status_code(0),
+ state(BEGIN),
+ content_length(0),
+ read_progress(0),
+ length_type(UNKNOWN_MESSAGE_LENGTH_TYPE) {
+ }
+
+ enum State {
+ // The start state.
+ // Possible next state: WILL_NOTIFY_BEFORE_REQUEST;
+ // ERROR_OCCURRED.
+ BEGIN = 0,
+ // We are about to fire webRequest.onBeforeRequest.
+ // Possible next state: NOTIFIED_BEFORE_REQUEST;
+ // ERROR_OCCURRED.
+ WILL_NOTIFY_BEFORE_REQUEST = 1,
+ // The last event fired is webRequest.onBeforeRequest.
+ // Possible next state: NOTIFIED_REQUEST_SENT;
+ // ERROR_OCCURRED.
+ NOTIFIED_BEFORE_REQUEST = 2,
+ // The last event fired is webRequest.onRequestSent.
+ // Possible next state: NOTIFIED_HEADERS_RECEIVED;
+ // ERROR_OCCURRED.
+ NOTIFIED_REQUEST_SENT = 3,
+ // The last event fired is webRequest.onHeadersReceived.
+ // Possible next state: NOTIFIED_BEFORE_REDIRECT;
+ // NOTIFIED_COMPLETED;
+ // ERROR_OCCURRED.
+ NOTIFIED_HEADERS_RECEIVED = 4,
+ // The last event fired is webRequest.onBeforeRedirect.
+ // Possible next state: NOTIFIED_REQUEST_SENT;
+ // ERROR_OCCURRED.
+ NOTIFIED_BEFORE_REDIRECT = 5,
+ // One of the two stop states.
+ // The last event fired is webRequest.onCompleted.
+ // Possible next state: NOTIFIED_COMPLETED.
+ NOTIFIED_COMPLETED = 6,
+ // One of the two stop states.
+ // Some error has occurred.
+ // Possible next state: ERROR_OCCURRED.
+ ERROR_OCCURRED = 7
+ };
+
+ // How the length of a message body is decided.
+ enum MessageLengthType {
+ UNKNOWN_MESSAGE_LENGTH_TYPE = 0,
+ // There is no message body.
+ NO_MESSAGE_BODY = 1,
+ // The length is decided by Content-Length header.
+ CONTENT_LENGTH_HEADER = 2,
+ // Including:
+ // (1) a Transfer-Encoding header field is present and has any value
+ // other than "identity";
+ // (2) the message uses the media type "multipart/byteranges";
+ // (3) the length should be decided by the server closing the connection.
+ VARIABLE_MESSAGE_LENGTH = 3,
+ };
+
+ // The handle of the connection to the server.
+ HINTERNET server_handle;
+ // The request ID, which is unique within a browser session.
+ int id;
+ // The window handle of the tab which sent the request.
+ CeeeWindowHandle tab_handle;
+ // The standard HTTP method, such as "GET" or "POST".
+ std::string method;
+ // The URL to retrieve.
+ std::wstring url;
+ // The URL before redirection.
+ std::wstring original_url;
+ // The server IP.
+ std::string ip;
+ // The standard HTTP status code returned by the server.
+ DWORD status_code;
+ // The request state to help decide what event should be sent next.
+ State state;
+ // When the browser was about to make the request.
+ base::Time before_request_time;
+ // The length of the response body. It is meaningful only when length_type
+ // is set to CONTENT_LENGTH_HEADER.
+ DWORD content_length;
+ // The progress of reading the response body.
+ DWORD read_progress;
+ // How the length of the response body is decided.
+ MessageLengthType length_type;
+ };
+
+ WebRequestNotifier();
+ virtual ~WebRequestNotifier();
+
+ // Accessor so that we can mock it in unit tests.
+ // Currently this method is always called within a lock.
+ virtual WebRequestEventsFunnel& webrequest_events_funnel() {
+ return webrequest_events_funnel_;
+ }
+
+ // Gets called before calling InternetSetStatusCallback.
+ // @param internet The handle for which the callback is set.
+ // @param callback The real callback function.
+ // @return A patch for the callback function.
+ INTERNET_STATUS_CALLBACK HandleBeforeInternetSetStatusCallback(
+ HINTERNET internet,
+ INTERNET_STATUS_CALLBACK callback);
+
+ // Gets called before calling InternetConnect.
+ // @param internet Handle returned by InternetOpen.
+ void HandleBeforeInternetConnect(HINTERNET internet);
+
+ // Gets called after calling InternetConnect.
+ // @param server The sever handle returned by InternetConnect.
+ // @param server_name The host name of the server.
+ // @param server_port The port number on the server.
+ // @param service Type of service to access.
+ void HandleAfterInternetConnect(HINTERNET server,
+ const wchar_t* server_name,
+ INTERNET_PORT server_port,
+ DWORD service);
+
+ // Gets called after calling HttpOpenRequest.
+ // @param server The server handle.
+ // @param request The request handle.
+ // @param method Standard HTTP method, such as "GET" or "POST".
+ // @param path The path to the target object.
+ // @param flags Internet options that are passed into HttpOpenRequest.
+ void HandleAfterHttpOpenRequest(HINTERNET server,
+ HINTERNET request,
+ const char* method,
+ const wchar_t* path,
+ DWORD flags);
+
+ // Gets called before calling HttpSendRequest.
+ // @param request The request handle.
+ void HandleBeforeHttpSendRequest(HINTERNET request);
+
+ // Gets called before calling InternetStatusCallback.
+ // @param original The original status callback function.
+ // @param internet The handle for which the callback function is called.
+ // @param context The application-defined context value associated with
+ // the internet parameter.
+ // @param internet_status A status code that indicates why the callback
+ // function is called.
+ // @param status_information A pointer to additional status information.
+ // @param status_information_length The size, in bytes, of the additional
+ // status information.
+ void HandleBeforeInternetStatusCallback(INTERNET_STATUS_CALLBACK original,
+ HINTERNET internet,
+ DWORD_PTR context,
+ DWORD internet_status,
+ LPVOID status_information,
+ DWORD status_information_length);
+
+ // Gets called after calling InternetReadFile.
+ // @param request The request handle.
+ // @param result Whether the read operation is successful or not.
+ // @param number_of_bytes_read How many bytes have been read.
+ void HandleAfterInternetReadFile(HINTERNET request,
+ BOOL result,
+ LPDWORD number_of_bytes_read);
+
+ // InternetSetStatusCallback function patches.
+ // InternetSetStatusCallback documentation can be found at:
+ // http://msdn.microsoft.com/en-us/library/aa385120(VS.85).aspx
+ static INTERNET_STATUS_CALLBACK STDAPICALLTYPE
+ InternetSetStatusCallbackAPatch(
+ HINTERNET internet,
+ INTERNET_STATUS_CALLBACK internet_callback);
+ static INTERNET_STATUS_CALLBACK STDAPICALLTYPE
+ InternetSetStatusCallbackWPatch(
+ HINTERNET internet,
+ INTERNET_STATUS_CALLBACK internet_callback);
+
+ // InternetConnect function patches.
+ // InternetConnect documentation can be found at:
+ // http://msdn.microsoft.com/en-us/library/aa384363(VS.85).aspx
+ static HINTERNET STDAPICALLTYPE InternetConnectAPatch(
+ HINTERNET internet,
+ LPCSTR server_name,
+ INTERNET_PORT server_port,
+ LPCSTR user_name,
+ LPCSTR password,
+ DWORD service,
+ DWORD flags,
+ DWORD_PTR context);
+ static HINTERNET STDAPICALLTYPE InternetConnectWPatch(
+ HINTERNET internet,
+ LPCWSTR server_name,
+ INTERNET_PORT server_port,
+ LPCWSTR user_name,
+ LPCWSTR password,
+ DWORD service,
+ DWORD flags,
+ DWORD_PTR context);
+
+ // HttpOpenRequest function patches.
+ // HttpOpenRequest documentation can be found at:
+ // http://msdn.microsoft.com/en-us/library/aa384233(v=VS.85).aspx
+ static HINTERNET STDAPICALLTYPE HttpOpenRequestAPatch(HINTERNET connect,
+ LPCSTR verb,
+ LPCSTR object_name,
+ LPCSTR version,
+ LPCSTR referrer,
+ LPCSTR* accept_types,
+ DWORD flags,
+ DWORD_PTR context);
+ static HINTERNET STDAPICALLTYPE HttpOpenRequestWPatch(HINTERNET connect,
+ LPCWSTR verb,
+ LPCWSTR object_name,
+ LPCWSTR version,
+ LPCWSTR referrer,
+ LPCWSTR* accept_types,
+ DWORD flags,
+ DWORD_PTR context);
+
+ // HttpSendRequest function patches.
+ // HttpSendRequest documentation can be found at:
+ // http://msdn.microsoft.com/en-us/library/aa384247(v=VS.85).aspx
+ static BOOL STDAPICALLTYPE HttpSendRequestAPatch(HINTERNET request,
+ LPCSTR headers,
+ DWORD headers_length,
+ LPVOID optional,
+ DWORD optional_length);
+ static BOOL STDAPICALLTYPE HttpSendRequestWPatch(HINTERNET request,
+ LPCWSTR headers,
+ DWORD headers_length,
+ LPVOID optional,
+ DWORD optional_length);
+
+ // InternetStatusCallback function patch.
+ // InternetStatusCallback function documentation can be found at:
+ // http://msdn.microsoft.com/en-us/library/aa385121(v=VS.85).aspx
+ static void CALLBACK InternetStatusCallbackPatch(
+ INTERNET_STATUS_CALLBACK original,
+ HINTERNET internet,
+ DWORD_PTR context,
+ DWORD internet_status,
+ LPVOID status_information,
+ DWORD status_information_length);
+
+ // InternetReadFile function patch.
+ // InternetReadFile documentation can be found at:
+ // http://msdn.microsoft.com/en-us/library/aa385103(v=VS.85).aspx
+ static BOOL STDAPICALLTYPE InternetReadFilePatch(
+ HINTERNET file,
+ LPVOID buffer,
+ DWORD number_of_bytes_to_read,
+ LPDWORD number_of_bytes_read);
+
+ // Patches a WinINet function.
+ // @param name The name of the function to be intercepted.
+ // @param patch_function The patching helper. You could check the is_patched
+ // member of this object to see whether the patching operation is
+ // successful or not.
+ // @param handler The new function implementation.
+ void PatchWinINetFunction(const char* name,
+ app::win::IATPatchFunction* patch_function,
+ void* handler);
+
+ // Constructs a URL. The method omits the port number if it is the default
+ // number for the protocol, or it is INTERNET_INVALID_PORT_NUMBER.
+ // Currently this method is always called within a lock.
+ // @param https Is it http or https?
+ // @param server_name The host name of the server.
+ // @param server_port The port number on the server.
+ // @param path The path to the target object.
+ // @param url The returned URL.
+ // @return Whether the operation is successful or not.
+ bool ConstructUrl(bool https,
+ const wchar_t* server_name,
+ INTERNET_PORT server_port,
+ const wchar_t* path,
+ std::wstring* url);
+
+ // Retrieves header information as a number.
+ // Make the method virtual so that we could mock it for unit tests.
+ // @param request The request handle.
+ // @param info_flag Query info flags could be found on:
+ // http://msdn.microsoft.com/en-us/library/aa385351(v=VS.85).aspx
+ // @param value The returned value.
+ // @return Whether the operation is successful or not.
+ virtual bool QueryHttpInfoNumber(HINTERNET request,
+ DWORD info_flag,
+ DWORD* value);
+ // Retrieves header information as a string.
+ // Make the method virtual so that we could mock it for unit tests.
+ // @param request The request handle.
+ // @param info_flag Query info flags could be found on:
+ // http://msdn.microsoft.com/en-us/library/aa385351(v=VS.85).aspx
+ // @param value The returned value.
+ // @return Whether the operation is successful or not.
+ virtual bool QueryHttpInfoString(HINTERNET request,
+ DWORD info_flag,
+ std::wstring* value);
+
+ // Determines the length of the response body.
+ // @param request The request handle.
+ // @param status_code Standard HTTP status code.
+ // @param length Returns the length of the response body. It is meaningful
+ // only when type is set to CONTENT_LENGTH_HEADER.
+ // @param type Returns how the length of the response body is decided.
+ // @return Whether the operation is successful or not.
+ bool DetermineMessageLength(HINTERNET request,
+ DWORD status_code,
+ DWORD* length,
+ RequestInfo::MessageLengthType* type);
+
+ // Performs state transition on a request.
+ // NOTE: this method must be called within a lock.
+ // @param state The target state. Please note that if it is not a valid
+ // transition, the request may end up with a state other than the
+ // target state.
+ // @param info Information about the request.
+ void TransitRequestToNextState(RequestInfo::State state, RequestInfo* info);
+
+ // Creates a function stub for the status callback function.
+ // NOTE: this method must be called within a lock.
+ // @param original_callback The original callback function.
+ // @return A patch for the callback function.
+ INTERNET_STATUS_CALLBACK CreateInternetStatusCallbackStub(
+ INTERNET_STATUS_CALLBACK original_callback);
+
+ // NOTE: this method must be called within a lock.
+ // @return Returns true if the service is not functioning, either because
+ // nobody is interested in the service or because the initialization
+ // has failed.
+ bool IsNotRunning() const {
+ return start_count_ == 0 || initialize_state_ != SUCCEEDED_TO_INITIALIZE;
+ }
+
+ // Returns true if exactly one (but not both) of the patches has been
+ // successfully applied.
+ // @param patch_function_1 A function patch.
+ // @param patch_function_2 Another function patch.
+ // @return Returns true if exactly one of them has been successfully applied.
+ bool HasPatchedOneVersion(
+ const app::win::IATPatchFunction& patch_function_1,
+ const app::win::IATPatchFunction& patch_function_2) const {
+ return (patch_function_1.is_patched() && !patch_function_2.is_patched()) ||
+ (!patch_function_1.is_patched() && patch_function_2.is_patched());
+ }
+
+ // Function patches that allow us to intercept WinINet functions.
+ app::win::IATPatchFunction internet_set_status_callback_a_patch_;
+ app::win::IATPatchFunction internet_set_status_callback_w_patch_;
+ app::win::IATPatchFunction internet_connect_a_patch_;
+ app::win::IATPatchFunction internet_connect_w_patch_;
+ app::win::IATPatchFunction http_open_request_a_patch_;
+ app::win::IATPatchFunction http_open_request_w_patch_;
+ app::win::IATPatchFunction http_send_request_a_patch_;
+ app::win::IATPatchFunction http_send_request_w_patch_;
+ app::win::IATPatchFunction internet_read_file_patch_;
+
+ // The funnel for sending webRequest events to the broker.
+ WebRequestEventsFunnel webrequest_events_funnel_;
+
+ // Used to protect the access to all the following data members.
+ CComAutoCriticalSection critical_section_;
+
+ // Used to intercept InternetStatusCallback function, which is defined by a
+ // WinINet client to observe status changes.
+ FunctionStub* internet_status_callback_stub_;
+
+ // Maps Internet connection handles to ServerInfo instances.
+ typedef std::map<HINTERNET, ServerInfo> ServerMap;
+ ServerMap server_map_;
+
+ // Maps HTTP request handles to RequestInfo instances.
+ typedef std::map<HINTERNET, RequestInfo> RequestMap;
+ RequestMap request_map_;
+
+ // The number of RequestToStart calls minus the number of RequestToStop calls.
+ // If the number drops to 0, then the service will be stopped.
+ int start_count_;
+
+ // Indicates the progress of initialization.
+ enum InitializeState {
+ // Initialization hasn't been started.
+ NOT_INITIALIZED,
+ // Initialization is happening.
+ INITIALIZING,
+ // Initialization has failed.
+ FAILED_TO_INITIALIZE,
+ // Initialization has succeeded.
+ SUCCEEDED_TO_INITIALIZE
+ };
+ InitializeState initialize_state_;
+
+ private:
+ DISALLOW_COPY_AND_ASSIGN(WebRequestNotifier);
+};
+
+// A singleton that keeps the WebRequestNotifier used by production code.
+class ProductionWebRequestNotifier
+ : public WebRequestNotifier,
+ public Singleton<ProductionWebRequestNotifier> {
+ private:
+ ProductionWebRequestNotifier() {}
+
+ friend struct DefaultSingletonTraits<ProductionWebRequestNotifier>;
+ DISALLOW_COPY_AND_ASSIGN(ProductionWebRequestNotifier);
+};
+
+#endif // CEEE_IE_PLUGIN_BHO_WEBREQUEST_NOTIFIER_H_
diff --git a/ceee/ie/plugin/bho/webrequest_notifier_unittest.cc b/ceee/ie/plugin/bho/webrequest_notifier_unittest.cc
new file mode 100644
index 0000000..81ee4d0
--- /dev/null
+++ b/ceee/ie/plugin/bho/webrequest_notifier_unittest.cc
@@ -0,0 +1,340 @@
+// 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.
+//
+// Unit test for WebRequestNotifier class.
+#include "ceee/ie/plugin/bho/webrequest_notifier.h"
+
+#include "ceee/ie/testing/mock_broker_and_friends.h"
+#include "ceee/testing/utils/test_utils.h"
+#include "gmock/gmock.h"
+#include "gtest/gtest.h"
+
+namespace {
+
+using testing::_;
+using testing::InSequence;
+using testing::MockFunction;
+using testing::StrictMock;
+
+class TestWebRequestNotifier : public WebRequestNotifier {
+ public:
+ TestWebRequestNotifier()
+ : mock_method_(L"GET"),
+ mock_status_code_(200),
+ mock_transfer_encoding_present_(false),
+ mock_content_length_(0),
+ mock_content_length_present_(false) {
+ }
+ virtual ~TestWebRequestNotifier() {}
+
+ virtual WebRequestEventsFunnel& webrequest_events_funnel() {
+ return mock_webrequest_events_funnel_;
+ }
+
+ virtual bool QueryHttpInfoString(HINTERNET /*request*/,
+ DWORD info_flag,
+ std::wstring* value) {
+ if (value == NULL)
+ return false;
+ if (info_flag == HTTP_QUERY_REQUEST_METHOD) {
+ *value = mock_method_;
+ return true;
+ } else if (mock_transfer_encoding_present_ &&
+ info_flag == HTTP_QUERY_TRANSFER_ENCODING) {
+ *value = mock_transfer_encoding_;
+ return true;
+ } else {
+ return false;
+ }
+ }
+
+ virtual bool QueryHttpInfoNumber(HINTERNET /*request*/,
+ DWORD info_flag,
+ DWORD* value) {
+ if (value == NULL)
+ return false;
+ if (info_flag == HTTP_QUERY_STATUS_CODE) {
+ *value = mock_status_code_;
+ return true;
+ } else if (mock_content_length_present_ &&
+ info_flag == HTTP_QUERY_CONTENT_LENGTH) {
+ *value = mock_content_length_;
+ return true;
+ } else {
+ return false;
+ }
+ }
+
+ StrictMock<testing::MockWebRequestEventsFunnel>
+ mock_webrequest_events_funnel_;
+
+ std::wstring mock_method_;
+ DWORD mock_status_code_;
+ std::wstring mock_transfer_encoding_;
+ bool mock_transfer_encoding_present_;
+ DWORD mock_content_length_;
+ bool mock_content_length_present_;
+
+ friend class GTEST_TEST_CLASS_NAME_(WebRequestNotifierTestFixture,
+ TestConstructUrl);
+ friend class GTEST_TEST_CLASS_NAME_(WebRequestNotifierTestFixture,
+ TestDetermineMessageLength);
+ friend class GTEST_TEST_CLASS_NAME_(WebRequestNotifierTestFixture,
+ TestTransitRequestToNextState);
+ friend class GTEST_TEST_CLASS_NAME_(WebRequestNotifierTestFixture,
+ TestWinINetPatchHandlers);
+};
+
+class WebRequestNotifierTestFixture : public testing::Test {
+ protected:
+ virtual void SetUp() {
+ webrequest_notifier_.reset(new TestWebRequestNotifier());
+ ASSERT_TRUE(webrequest_notifier_->RequestToStart());
+ }
+
+ virtual void TearDown() {
+ webrequest_notifier_->RequestToStop();
+ webrequest_notifier_.reset(NULL);
+ }
+
+ scoped_ptr<TestWebRequestNotifier> webrequest_notifier_;
+};
+
+TEST_F(WebRequestNotifierTestFixture, TestConstructUrl) {
+ std::wstring output;
+ EXPECT_TRUE(webrequest_notifier_->ConstructUrl(
+ true, L"www.google.com", INTERNET_INVALID_PORT_NUMBER, L"/foobar",
+ &output));
+ EXPECT_STREQ(L"https://www.google.com/foobar", output.c_str());
+
+ EXPECT_TRUE(webrequest_notifier_->ConstructUrl(
+ false, L"mail.google.com", INTERNET_DEFAULT_HTTP_PORT, L"/index.html",
+ &output));
+ EXPECT_STREQ(L"http://mail.google.com/index.html", output.c_str());
+
+ EXPECT_TRUE(webrequest_notifier_->ConstructUrl(
+ false, L"docs.google.com", INTERNET_DEFAULT_HTTPS_PORT, L"/login",
+ &output));
+ EXPECT_STREQ(L"http://docs.google.com:443/login", output.c_str());
+
+ EXPECT_TRUE(webrequest_notifier_->ConstructUrl(
+ true, L"image.google.com", 123, L"/helloworld", &output));
+ EXPECT_STREQ(L"https://image.google.com:123/helloworld", output.c_str());
+}
+
+TEST_F(WebRequestNotifierTestFixture, TestDetermineMessageLength) {
+ DWORD length = 0;
+ WebRequestNotifier::RequestInfo::MessageLengthType type =
+ WebRequestNotifier::RequestInfo::UNKNOWN_MESSAGE_LENGTH_TYPE;
+ HINTERNET request = reinterpret_cast<HINTERNET>(1024);
+
+ // Requests with "HEAD" method don't have a message body in the response.
+ webrequest_notifier_->mock_method_ = L"HEAD";
+ EXPECT_TRUE(webrequest_notifier_->DetermineMessageLength(
+ request, 200, &length, &type));
+ EXPECT_EQ(0, length);
+ EXPECT_EQ(WebRequestNotifier::RequestInfo::NO_MESSAGE_BODY, type);
+
+ // Requests with status code 1XX, 204 or 304 don't have a message body in the
+ // response.
+ webrequest_notifier_->mock_method_ = L"GET";
+ EXPECT_TRUE(webrequest_notifier_->DetermineMessageLength(
+ request, 100, &length, &type));
+ EXPECT_EQ(0, length);
+ EXPECT_EQ(WebRequestNotifier::RequestInfo::NO_MESSAGE_BODY, type);
+
+ // If a Transfer-Encoding header field is present and has any value other than
+ // "identity", the response body length is variable.
+ // The Content-Length field is ignored in this case.
+ webrequest_notifier_->mock_transfer_encoding_present_ = true;
+ webrequest_notifier_->mock_transfer_encoding_ = L"chunked";
+ webrequest_notifier_->mock_content_length_present_ = true;
+ webrequest_notifier_->mock_content_length_ = 256;
+ EXPECT_TRUE(webrequest_notifier_->DetermineMessageLength(
+ request, 200, &length, &type));
+ EXPECT_EQ(0, length);
+ EXPECT_EQ(WebRequestNotifier::RequestInfo::VARIABLE_MESSAGE_LENGTH, type);
+
+ // If a Content-Length header field is present, the response body length is
+ // the same as specified in the Content-Length header.
+ webrequest_notifier_->mock_transfer_encoding_present_ = false;
+ EXPECT_TRUE(webrequest_notifier_->DetermineMessageLength(
+ request, 200, &length, &type));
+ EXPECT_EQ(256, length);
+ EXPECT_EQ(WebRequestNotifier::RequestInfo::CONTENT_LENGTH_HEADER, type);
+
+ // Otherwise, consider the response body length is variable.
+ webrequest_notifier_->mock_content_length_present_ = false;
+ EXPECT_TRUE(webrequest_notifier_->DetermineMessageLength(
+ request, 200, &length, &type));
+ EXPECT_EQ(0, length);
+ EXPECT_EQ(WebRequestNotifier::RequestInfo::VARIABLE_MESSAGE_LENGTH, type);
+}
+
+TEST_F(WebRequestNotifierTestFixture, TestTransitRequestToNextState) {
+ scoped_ptr<WebRequestNotifier::RequestInfo> info(
+ new WebRequestNotifier::RequestInfo());
+ MockFunction<void(int check_point)> check;
+ {
+ InSequence sequence;
+ EXPECT_CALL(webrequest_notifier_->mock_webrequest_events_funnel_,
+ OnBeforeRequest(_, _, _, _, _, _));
+ EXPECT_CALL(webrequest_notifier_->mock_webrequest_events_funnel_,
+ OnRequestSent(_, _, _, _));
+ EXPECT_CALL(webrequest_notifier_->mock_webrequest_events_funnel_,
+ OnHeadersReceived(_, _, _, _));
+ EXPECT_CALL(webrequest_notifier_->mock_webrequest_events_funnel_,
+ OnBeforeRedirect(_, _, _, _, _));
+ EXPECT_CALL(webrequest_notifier_->mock_webrequest_events_funnel_,
+ OnRequestSent(_, _, _, _));
+ EXPECT_CALL(webrequest_notifier_->mock_webrequest_events_funnel_,
+ OnHeadersReceived(_, _, _, _));
+ EXPECT_CALL(webrequest_notifier_->mock_webrequest_events_funnel_,
+ OnCompleted(_, _, _, _));
+ EXPECT_CALL(check, Call(1));
+
+ EXPECT_CALL(check, Call(2));
+
+ EXPECT_CALL(check, Call(3));
+
+ EXPECT_CALL(webrequest_notifier_->mock_webrequest_events_funnel_,
+ OnBeforeRequest(_, _, _, _, _, _));
+ EXPECT_CALL(webrequest_notifier_->mock_webrequest_events_funnel_,
+ OnRequestSent(_, _, _, _));
+ EXPECT_CALL(webrequest_notifier_->mock_webrequest_events_funnel_,
+ OnErrorOccurred(_, _, _, _));
+ EXPECT_CALL(check, Call(4));
+
+ EXPECT_CALL(check, Call(5));
+ }
+
+ // The normal state transition sequence.
+ webrequest_notifier_->TransitRequestToNextState(
+ WebRequestNotifier::RequestInfo::WILL_NOTIFY_BEFORE_REQUEST, info.get());
+ webrequest_notifier_->TransitRequestToNextState(
+ WebRequestNotifier::RequestInfo::NOTIFIED_BEFORE_REQUEST, info.get());
+ webrequest_notifier_->TransitRequestToNextState(
+ WebRequestNotifier::RequestInfo::NOTIFIED_REQUEST_SENT, info.get());
+ webrequest_notifier_->TransitRequestToNextState(
+ WebRequestNotifier::RequestInfo::NOTIFIED_HEADERS_RECEIVED, info.get());
+ webrequest_notifier_->TransitRequestToNextState(
+ WebRequestNotifier::RequestInfo::NOTIFIED_BEFORE_REDIRECT, info.get());
+ webrequest_notifier_->TransitRequestToNextState(
+ WebRequestNotifier::RequestInfo::NOTIFIED_REQUEST_SENT, info.get());
+ webrequest_notifier_->TransitRequestToNextState(
+ WebRequestNotifier::RequestInfo::NOTIFIED_HEADERS_RECEIVED, info.get());
+ webrequest_notifier_->TransitRequestToNextState(
+ WebRequestNotifier::RequestInfo::NOTIFIED_COMPLETED, info.get());
+ check.Call(1);
+
+ // No event is fired after webRequest.onCompleted for any request.
+ webrequest_notifier_->TransitRequestToNextState(
+ WebRequestNotifier::RequestInfo::NOTIFIED_BEFORE_REQUEST, info.get());
+ webrequest_notifier_->TransitRequestToNextState(
+ WebRequestNotifier::RequestInfo::ERROR_OCCURRED, info.get());
+ check.Call(2);
+
+ // No webRequest.onErrorOccurred is fired since the first event for any
+ // request has to be webRequest.onBeforeRequest.
+ info.reset(new WebRequestNotifier::RequestInfo());
+ webrequest_notifier_->TransitRequestToNextState(
+ WebRequestNotifier::RequestInfo::WILL_NOTIFY_BEFORE_REQUEST, info.get());
+ webrequest_notifier_->TransitRequestToNextState(
+ WebRequestNotifier::RequestInfo::ERROR_OCCURRED, info.get());
+ check.Call(3);
+
+ // Unexpected next-state will result in webRequest.onErrorOccurred to be
+ // fired.
+ info.reset(new WebRequestNotifier::RequestInfo());
+ webrequest_notifier_->TransitRequestToNextState(
+ WebRequestNotifier::RequestInfo::WILL_NOTIFY_BEFORE_REQUEST, info.get());
+ webrequest_notifier_->TransitRequestToNextState(
+ WebRequestNotifier::RequestInfo::NOTIFIED_BEFORE_REQUEST, info.get());
+ webrequest_notifier_->TransitRequestToNextState(
+ WebRequestNotifier::RequestInfo::NOTIFIED_REQUEST_SENT, info.get());
+ webrequest_notifier_->TransitRequestToNextState(
+ WebRequestNotifier::RequestInfo::NOTIFIED_COMPLETED, info.get());
+ check.Call(4);
+
+ // No event is fired after webRequest.onErrorOccurred for any request.
+ webrequest_notifier_->TransitRequestToNextState(
+ WebRequestNotifier::RequestInfo::NOTIFIED_HEADERS_RECEIVED, info.get());
+ check.Call(5);
+}
+
+TEST_F(WebRequestNotifierTestFixture, TestWinINetPatchHandlers) {
+ MockFunction<void(int check_point)> check;
+ {
+ InSequence sequence;
+ EXPECT_CALL(webrequest_notifier_->mock_webrequest_events_funnel_,
+ OnBeforeRequest(_, _, _, _, _, _));
+ EXPECT_CALL(webrequest_notifier_->mock_webrequest_events_funnel_,
+ OnRequestSent(_, _, _, _));
+ EXPECT_CALL(webrequest_notifier_->mock_webrequest_events_funnel_,
+ OnHeadersReceived(_, _, _, _));
+ EXPECT_CALL(webrequest_notifier_->mock_webrequest_events_funnel_,
+ OnBeforeRedirect(_, _, _, _, _));
+ EXPECT_CALL(webrequest_notifier_->mock_webrequest_events_funnel_,
+ OnRequestSent(_, _, _, _));
+ EXPECT_CALL(webrequest_notifier_->mock_webrequest_events_funnel_,
+ OnHeadersReceived(_, _, _, _));
+ EXPECT_CALL(webrequest_notifier_->mock_webrequest_events_funnel_,
+ OnCompleted(_, _, _, _));
+ EXPECT_CALL(check, Call(1));
+ }
+
+ HINTERNET internet = reinterpret_cast<HINTERNET>(512);
+ HINTERNET server = reinterpret_cast<HINTERNET>(1024);
+ HINTERNET request = reinterpret_cast<HINTERNET>(2048);
+
+ webrequest_notifier_->mock_method_ = L"GET";
+ webrequest_notifier_->mock_status_code_ = 200;
+ webrequest_notifier_->mock_content_length_ = 256;
+ webrequest_notifier_->mock_content_length_present_ = true;
+
+ webrequest_notifier_->HandleBeforeInternetConnect(internet);
+ webrequest_notifier_->HandleAfterInternetConnect(
+ server, L"www.google.com", INTERNET_DEFAULT_HTTP_PORT,
+ INTERNET_SERVICE_HTTP);
+
+ webrequest_notifier_->HandleAfterHttpOpenRequest(server, request, "GET",
+ L"/", 0);
+
+ webrequest_notifier_->HandleBeforeHttpSendRequest(request);
+
+ webrequest_notifier_->HandleBeforeInternetStatusCallback(
+ NULL, request, NULL, INTERNET_STATUS_SENDING_REQUEST, NULL, 0);
+ webrequest_notifier_->HandleBeforeInternetStatusCallback(
+ NULL, request, NULL, INTERNET_STATUS_REQUEST_SENT, NULL, 0);
+ webrequest_notifier_->HandleBeforeInternetStatusCallback(
+ NULL, request, NULL, INTERNET_STATUS_REDIRECT,
+ "http://www.google.com/index.html", 32);
+ webrequest_notifier_->HandleBeforeInternetStatusCallback(
+ NULL, request, NULL, INTERNET_STATUS_SENDING_REQUEST, NULL, 0);
+ webrequest_notifier_->HandleBeforeInternetStatusCallback(
+ NULL, request, NULL, INTERNET_STATUS_REQUEST_SENT, NULL, 0);
+ webrequest_notifier_->HandleBeforeInternetStatusCallback(
+ NULL, request, NULL, INTERNET_STATUS_REQUEST_COMPLETE, NULL, 0);
+
+ DWORD number_of_bytes_read = 64;
+ webrequest_notifier_->HandleAfterInternetReadFile(request, TRUE,
+ &number_of_bytes_read);
+ number_of_bytes_read = 128;
+ webrequest_notifier_->HandleAfterInternetReadFile(request, TRUE,
+ &number_of_bytes_read);
+ number_of_bytes_read = 64;
+ webrequest_notifier_->HandleAfterInternetReadFile(request, TRUE,
+ &number_of_bytes_read);
+
+ // Since we have read the whole response body, webRequest.onCompleted has been
+ // sent at this point.
+ check.Call(1);
+
+ webrequest_notifier_->HandleBeforeInternetStatusCallback(
+ NULL, request, NULL, INTERNET_STATUS_HANDLE_CLOSING, NULL, 0);
+ webrequest_notifier_->HandleBeforeInternetStatusCallback(
+ NULL, server, NULL, INTERNET_STATUS_HANDLE_CLOSING, NULL, 0);
+}
+
+} // namespace
diff --git a/ceee/ie/plugin/bho/window_message_source.cc b/ceee/ie/plugin/bho/window_message_source.cc
new file mode 100644
index 0000000..ec6b0ea
--- /dev/null
+++ b/ceee/ie/plugin/bho/window_message_source.cc
@@ -0,0 +1,216 @@
+// 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.
+//
+// Window message source implementation.
+#include "ceee/ie/plugin/bho/window_message_source.h"
+
+#include <algorithm>
+
+#include "base/logging.h"
+#include "base/win_util.h"
+#include "ceee/common/window_utils.h"
+#include "ceee/common/windows_constants.h"
+
+WindowMessageSource::MessageSourceMap WindowMessageSource::message_source_map_;
+Lock WindowMessageSource::lock_;
+
+WindowMessageSource::WindowMessageSource()
+ : create_thread_id_(::GetCurrentThreadId()),
+ get_message_hook_(NULL),
+ call_wnd_proc_ret_hook_(NULL) {
+}
+
+WindowMessageSource::~WindowMessageSource() {
+ DCHECK(get_message_hook_ == NULL);
+ DCHECK(call_wnd_proc_ret_hook_ == NULL);
+ DCHECK(GetEntryFromMap(create_thread_id_) == NULL);
+}
+
+bool WindowMessageSource::Initialize() {
+ if (!AddEntryToMap(create_thread_id_, this))
+ return false;
+
+ get_message_hook_ = ::SetWindowsHookEx(WH_GETMESSAGE, GetMessageHookProc,
+ NULL, create_thread_id_);
+ if (get_message_hook_ == NULL) {
+ TearDown();
+ return false;
+ }
+
+ call_wnd_proc_ret_hook_ = ::SetWindowsHookEx(WH_CALLWNDPROCRET,
+ CallWndProcRetHookProc, NULL,
+ create_thread_id_);
+ if (call_wnd_proc_ret_hook_ == NULL) {
+ TearDown();
+ return false;
+ }
+ return true;
+}
+
+void WindowMessageSource::TearDown() {
+ if (get_message_hook_ != NULL) {
+ ::UnhookWindowsHookEx(get_message_hook_);
+ get_message_hook_ = NULL;
+ }
+
+ if (call_wnd_proc_ret_hook_ != NULL) {
+ ::UnhookWindowsHookEx(call_wnd_proc_ret_hook_);
+ call_wnd_proc_ret_hook_ = NULL;
+ }
+
+ RemoveEntryFromMap(create_thread_id_);
+}
+
+void WindowMessageSource::RegisterSink(Sink* sink) {
+ DCHECK(create_thread_id_ == ::GetCurrentThreadId());
+
+ if (sink == NULL)
+ return;
+
+ std::vector<Sink*>::iterator iter = std::find(sinks_.begin(), sinks_.end(),
+ sink);
+ if (iter == sinks_.end())
+ sinks_.push_back(sink);
+}
+
+void WindowMessageSource::UnregisterSink(Sink* sink) {
+ DCHECK(create_thread_id_ == ::GetCurrentThreadId());
+
+ if (sink == NULL)
+ return;
+
+ std::vector<Sink*>::iterator iter = std::find(sinks_.begin(), sinks_.end(),
+ sink);
+ if (iter != sinks_.end())
+ sinks_.erase(iter);
+}
+
+// static
+LRESULT CALLBACK WindowMessageSource::GetMessageHookProc(int code,
+ WPARAM wparam,
+ LPARAM lparam) {
+ if (code == HC_ACTION && wparam == PM_REMOVE) {
+ MSG* message_info = reinterpret_cast<MSG*>(lparam);
+ if (message_info != NULL) {
+ if ((message_info->message >= WM_MOUSEFIRST &&
+ message_info->message <= WM_MOUSELAST) ||
+ (message_info->message >= WM_KEYFIRST &&
+ message_info->message <= WM_KEYLAST)) {
+ WindowMessageSource* source = GetEntryFromMap(::GetCurrentThreadId());
+ if (source != NULL)
+ source->OnHandleMessage(message_info);
+ }
+ }
+ }
+
+ return ::CallNextHookEx(NULL, code, wparam, lparam);
+}
+
+void WindowMessageSource::OnHandleMessage(const MSG* message_info) {
+ DCHECK(create_thread_id_ == ::GetCurrentThreadId());
+ DCHECK(message_info != NULL);
+ MessageType type = IsWithinTabContentWindow(message_info->hwnd) ?
+ TAB_CONTENT_WINDOW : BROWSER_UI_SAME_THREAD;
+
+ for (std::vector<Sink*>::iterator iter = sinks_.begin(); iter != sinks_.end();
+ ++iter) {
+ (*iter)->OnHandleMessage(type, message_info);
+ }
+}
+
+// static
+LRESULT CALLBACK WindowMessageSource::CallWndProcRetHookProc(int code,
+ WPARAM wparam,
+ LPARAM lparam) {
+ if (code == HC_ACTION) {
+ CWPRETSTRUCT* message_info = reinterpret_cast<CWPRETSTRUCT*>(lparam);
+ if (message_info != NULL && message_info->message == WM_NCDESTROY) {
+ WindowMessageSource* source = GetEntryFromMap(::GetCurrentThreadId());
+ if (source != NULL)
+ source->OnWindowNcDestroy(message_info->hwnd);
+ }
+ }
+
+ return ::CallNextHookEx(NULL, code, wparam, lparam);
+}
+
+void WindowMessageSource::OnWindowNcDestroy(HWND window) {
+ DCHECK(create_thread_id_ == ::GetCurrentThreadId());
+ tab_content_window_map_.erase(window);
+}
+
+bool WindowMessageSource::IsWithinTabContentWindow(HWND window) {
+ DCHECK(create_thread_id_ == ::GetCurrentThreadId());
+
+ if (window == NULL)
+ return false;
+
+ // Look up the cache to see whether we have already examined this window
+ // handle and got the answer.
+ TabContentWindowMap::const_iterator iter =
+ tab_content_window_map_.find(window);
+ if (iter != tab_content_window_map_.end())
+ return iter->second;
+
+ // Examine whether the window or one of its ancestors is the tab content
+ // window.
+ std::vector<HWND> self_and_ancestors;
+ bool is_within_tab_content_window = false;
+ do {
+ self_and_ancestors.push_back(window);
+
+ if (window_utils::IsWindowClass(window,
+ windows::kIeTabContentWindowClass)) {
+ is_within_tab_content_window = true;
+ break;
+ }
+
+ window = ::GetAncestor(window, GA_PARENT);
+ if (window == NULL || !window_utils::IsWindowThread(window))
+ break;
+
+ TabContentWindowMap::const_iterator iter =
+ tab_content_window_map_.find(window);
+ if (iter != tab_content_window_map_.end()) {
+ is_within_tab_content_window = iter->second;
+ break;
+ }
+ } while (true);
+
+ // Add the windows that we have examined into the cache.
+ for (std::vector<HWND>::const_iterator iter = self_and_ancestors.begin();
+ iter != self_and_ancestors.end(); ++iter) {
+ tab_content_window_map_.insert(
+ std::make_pair<HWND, bool>(*iter, is_within_tab_content_window));
+ }
+ return is_within_tab_content_window;
+}
+
+// static
+bool WindowMessageSource::AddEntryToMap(DWORD thread_id,
+ WindowMessageSource* source) {
+ DCHECK(source != NULL);
+
+ AutoLock auto_lock(lock_);
+ MessageSourceMap::const_iterator iter = message_source_map_.find(thread_id);
+ if (iter != message_source_map_.end())
+ return false;
+
+ message_source_map_.insert(
+ std::make_pair<DWORD, WindowMessageSource*>(thread_id, source));
+ return true;
+}
+
+// static
+WindowMessageSource* WindowMessageSource::GetEntryFromMap(DWORD thread_id) {
+ AutoLock auto_lock(lock_);
+ MessageSourceMap::const_iterator iter = message_source_map_.find(thread_id);
+ return iter == message_source_map_.end() ? NULL : iter->second;
+}
+
+// static
+void WindowMessageSource::RemoveEntryFromMap(DWORD thread_id) {
+ AutoLock auto_lock(lock_);
+ message_source_map_.erase(thread_id);
+}
diff --git a/ceee/ie/plugin/bho/window_message_source.h b/ceee/ie/plugin/bho/window_message_source.h
new file mode 100644
index 0000000..baceafa
--- /dev/null
+++ b/ceee/ie/plugin/bho/window_message_source.h
@@ -0,0 +1,103 @@
+// 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.
+//
+// Window message source implementation.
+#ifndef CEEE_IE_PLUGIN_BHO_WINDOW_MESSAGE_SOURCE_H_
+#define CEEE_IE_PLUGIN_BHO_WINDOW_MESSAGE_SOURCE_H_
+
+#include <map>
+#include <vector>
+
+#include "base/basictypes.h"
+#include "base/lock.h"
+
+// A WindowMessageSource instance monitors keyboard and mouse messages on the
+// same thread as the one that creates the instance, and fires events to those
+// registered sinks.
+//
+// NOTE: (1) All the non-static methods are supposed to be called on the thread
+// that creates the instance.
+// (2) Multiple WindowMessageSource instances cannot live in the same
+// thread. Only the first one will be successfully initialized.
+class WindowMessageSource {
+ public:
+ enum MessageType {
+ // The destination of the message is the content window (or its descendants)
+ // of a tab.
+ TAB_CONTENT_WINDOW,
+ // Otherwise.
+ BROWSER_UI_SAME_THREAD
+ };
+
+ // The interface that event consumers have to implement.
+ // NOTE: All the callback methods will be called on the thread that creates
+ // the WindowMessageSource instance.
+ class Sink {
+ public:
+ virtual ~Sink() {}
+ // Called before a message is handled.
+ virtual void OnHandleMessage(MessageType type, const MSG* message_info) {}
+ };
+
+ WindowMessageSource();
+ virtual ~WindowMessageSource();
+
+ bool Initialize();
+ void TearDown();
+
+ virtual void RegisterSink(Sink* sink);
+ virtual void UnregisterSink(Sink* sink);
+
+ private:
+ // Hook procedure for WH_GETMESSAGE.
+ static LRESULT CALLBACK GetMessageHookProc(int code,
+ WPARAM wparam,
+ LPARAM lparam);
+ void OnHandleMessage(const MSG* message_info);
+
+ // Hook procedure for WH_CALLWNDPROCRET.
+ static LRESULT CALLBACK CallWndProcRetHookProc(int code,
+ WPARAM wparam,
+ LPARAM lparam);
+ void OnWindowNcDestroy(HWND window);
+
+ // Returns true if the specified window is the tab content window or one of
+ // its descendants.
+ bool IsWithinTabContentWindow(HWND window);
+
+ // Adds an entry to the message_source_map_. Returns false if the item was
+ // already present.
+ static bool AddEntryToMap(DWORD thread_id, WindowMessageSource* source);
+ // Retrieves an entry from the message_source_map_.
+ static WindowMessageSource* GetEntryFromMap(DWORD thread_id);
+ // Removes an entry from the message_source_map_.
+ static void RemoveEntryFromMap(DWORD thread_id);
+
+ // The thread that creates this object.
+ const DWORD create_thread_id_;
+ // Event consumers.
+ std::vector<Sink*> sinks_;
+
+ // The handle to the hook procedure of WH_GETMESSAGE.
+ HHOOK get_message_hook_;
+ // The handle to the hook procedure of WH_CALLWNDPROCRET.
+ HHOOK call_wnd_proc_ret_hook_;
+
+ // Caches the information about whether a given window is within the tab
+ // content window or not.
+ typedef std::map<HWND, bool> TabContentWindowMap;
+ TabContentWindowMap tab_content_window_map_;
+
+ // Maintains a map from thread IDs to their corresponding
+ // WindowMessageSource instances.
+ typedef std::map<DWORD, WindowMessageSource*> MessageSourceMap;
+ static MessageSourceMap message_source_map_;
+
+ // Used to protect access to the message_source_map_.
+ static Lock lock_;
+
+ DISALLOW_COPY_AND_ASSIGN(WindowMessageSource);
+};
+
+#endif // CEEE_IE_PLUGIN_BHO_WINDOW_MESSAGE_SOURCE_H_
diff --git a/ceee/ie/plugin/scripting/base.js b/ceee/ie/plugin/scripting/base.js
new file mode 100644
index 0000000..58401de
--- /dev/null
+++ b/ceee/ie/plugin/scripting/base.js
@@ -0,0 +1,1291 @@
+// Copyright 2006 Google Inc.
+// All Rights Reserved.
+// Redistribution and use in source and binary forms, with or without
+// modification, are permitted provided that the following conditions
+// are met:
+//
+// * Redistributions of source code must retain the above copyright
+// notice, this list of conditions and the following disclaimer.
+// * Redistributions in binary form must reproduce the above copyright
+// notice, this list of conditions and the following disclaimer in
+// the documentation and/or other materials provided with the
+// distribution.
+//
+// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
+// FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
+// COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
+// INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
+// BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
+// LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+// CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+// LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN
+// ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+// POSSIBILITY OF SUCH DAMAGE.
+
+/**
+ * @fileoverview Bootstrap for the Google JS Library (Closure) Also includes
+ * stuff taken from //depot/google3/javascript/lang.js.
+ */
+
+/*
+ * ORIGINAL VERSION: http://doctype.googlecode.com/svn/trunk/goog/base.js
+ *
+ * LOCAL CHANGES: Tagged "CEEE changes" below.
+ *
+ * This file has been changed from its original so that the deps.js file is
+ * not automatically inserted into the page. In the context of CEEE, there
+ * is no deps.js file. Furthermore, because this file is being injected into
+ * the page using the Firefox sandbox mechanism, it does not have the security
+ * rights to write to the document using the write() method.
+ *
+ * The reason for injecting base.js into the file is to use the closure-based
+ * json.js file. If we no longer need json.js, then it would be possible to
+ * remove this file too. Firefox does not have native json library that can
+ * be used by unprivileged js code, but it will in version 3.5.
+ */
+
+/**
+ * @define {boolean} Overridden to true by the compiler when --closure_pass
+ * or --mark_as_compiled is specified.
+ */
+var COMPILED = false;
+
+
+/**
+ * Base namespace for the Closure library. Checks to see goog is
+ * already defined in the current scope before assigning to prevent
+ * clobbering if base.js is loaded more than once.
+ */
+var goog = goog || {}; // Check to see if already defined in current scope
+
+
+/**
+ * Reference to the global context. In most cases this will be 'window'.
+ */
+goog.global = this;
+
+
+/**
+ * @define {boolean} DEBUG is provided as a convenience so that debugging code
+ * that should not be included in a production js_binary can be easily stripped
+ * by specifying --define goog.DEBUG=false to the JSCompiler. For example, most
+ * toString() methods should be declared inside an "if (goog.DEBUG)" conditional
+ * because they are generally used for debugging purposes and it is difficult
+ * for the JSCompiler to statically determine whether they are used.
+ */
+goog.DEBUG = true;
+
+
+/**
+ * @define {string} LOCALE defines the locale being used for compilation. It is
+ * used to select locale specific data to be compiled in js binary. BUILD rule
+ * can specify this value by "--define goog.LOCALE=<locale_name>" as JSCompiler
+ * option.
+ *
+ * Take into account that the locale code format is important. You should use
+ * the canonical Unicode format with hyphen as a delimiter. Language must be
+ * lowercase, Language Script - Capitalized, Region - UPPERCASE.
+ * There are few examples: pt-BR, en, en-US, sr-Latin-BO, zh-Hans-CN.
+ *
+ * See more info about locale codes here:
+ * http://www.unicode.org/reports/tr35/#Unicode_Language_and_Locale_Identifiers
+ *
+ * For language codes you should use values defined by ISO 693-1. See it here
+ * http://www.w3.org/WAI/ER/IG/ert/iso639.htm. There is only one exception from
+ * this rule: the Hebrew language. For legacy reasons the old code (iw) should
+ * be used instead of the new code (he), see http://wiki/Main/IIISynonyms.
+ */
+// CEEE changes: Changed from 'en' to 'en_US'
+goog.LOCALE = 'en_US'; // default to en_US
+
+
+/**
+ * Indicates whether or not we can call 'eval' directly to eval code in the
+ * global scope. Set to a Boolean by the first call to goog.globalEval (which
+ * empirically tests whether eval works for globals). @see goog.globalEval
+ * @type {boolean?}
+ * @private
+ */
+goog.evalWorksForGlobals_ = null;
+
+
+/**
+ * Creates object stubs for a namespace. When present in a file, goog.provide
+ * also indicates that the file defines the indicated object. Calls to
+ * goog.provide are resolved by the compiler if --closure_pass is set.
+ * @param {string} name name of the object that this file defines.
+ */
+goog.provide = function(name) {
+ if (!COMPILED) {
+ // Ensure that the same namespace isn't provided twice. This is intended
+ // to teach new developers that 'goog.provide' is effectively a variable
+ // declaration. And when JSCompiler transforms goog.provide into a real
+ // variable declaration, the compiled JS should work the same as the raw
+ // JS--even when the raw JS uses goog.provide incorrectly.
+ if (goog.getObjectByName(name) && !goog.implicitNamespaces_[name]) {
+ throw Error('Namespace "' + name + '" already declared.');
+ }
+
+ var namespace = name;
+ while ((namespace = namespace.substring(0, namespace.lastIndexOf('.')))) {
+ goog.implicitNamespaces_[namespace] = true;
+ }
+ }
+
+ goog.exportPath_(name);
+};
+
+
+if (!COMPILED) {
+ /**
+ * Namespaces implicitly defined by goog.provide. For example,
+ * goog.provide('goog.events.Event') implicitly declares
+ * that 'goog' and 'goog.events' must be namespaces.
+ *
+ * @type {Object}
+ * @private
+ */
+ goog.implicitNamespaces_ = {};
+}
+
+
+/**
+ * Builds an object structure for the provided namespace path,
+ * ensuring that names that already exist are not overwritten. For
+ * example:
+ * "a.b.c" -> a = {};a.b={};a.b.c={};
+ * Used by goog.provide and goog.exportSymbol.
+ * @param {string} name name of the object that this file defines.
+ * @param {Object} opt_object the object to expose at the end of the path.
+ * @param {Object} opt_objectToExportTo The object to add the path to; default
+ * is |goog.global|.
+ * @private
+ */
+goog.exportPath_ = function(name, opt_object, opt_objectToExportTo) {
+ var parts = name.split('.');
+ var cur = opt_objectToExportTo || goog.global;
+ var part;
+
+ // Internet Explorer exhibits strange behavior when throwing errors from
+ // methods externed in this manner. See the testExportSymbolExceptions in
+ // base_test.html for an example.
+ if (!(parts[0] in cur) && cur.execScript) {
+ cur.execScript('var ' + parts[0]);
+ }
+
+ // Parentheses added to eliminate strict JS warning in Firefox.
+ while (parts.length && (part = parts.shift())) {
+ if (!parts.length && goog.isDef(opt_object)) {
+ // last part and we have an object; use it
+ cur[part] = opt_object;
+ } else if (cur[part]) {
+ cur = cur[part];
+ } else {
+ cur = cur[part] = {};
+ }
+ }
+};
+
+
+/**
+ * Returns an object based on its fully qualified external name. If you are
+ * using a compilation pass that renames property names beware that using this
+ * function will not find renamed properties.
+ *
+ * @param {string} name The fully qualified name.
+ * @param {Object} opt_obj The object within which to look; default is
+ * |goog.global|.
+ * @return {Object?} The object or, if not found, null.
+ */
+goog.getObjectByName = function(name, opt_obj) {
+ var parts = name.split('.');
+ var cur = opt_obj || goog.global;
+ for (var part; part = parts.shift(); ) {
+ if (cur[part]) {
+ cur = cur[part];
+ } else {
+ return null;
+ }
+ }
+ return cur;
+};
+
+
+/**
+ * Globalizes a whole namespace, such as goog or goog.lang.
+ *
+ * @param {Object} obj The namespace to globalize.
+ * @param {Object} opt_global The object to add the properties to.
+ * @deprecated Properties may be explicitly exported to the global scope, but
+ * this should no longer be done in bulk.
+ */
+goog.globalize = function(obj, opt_global) {
+ var global = opt_global || goog.global;
+ for (var x in obj) {
+ global[x] = obj[x];
+ }
+};
+
+
+/**
+ * Adds a dependency from a file to the files it requires.
+ * @param {string} relPath The path to the js file.
+ * @param {Array} provides An array of strings with the names of the objects
+ * this file provides.
+ * @param {Array} requires An array of strings with the names of the objects
+ * this file requires.
+ */
+goog.addDependency = function(relPath, provides, requires) {
+ if (!COMPILED) {
+ var provide, require;
+ var path = relPath.replace(/\\/g, '/');
+ var deps = goog.dependencies_;
+ for (var i = 0; provide = provides[i]; i++) {
+ deps.nameToPath[provide] = path;
+ if (!(path in deps.pathToNames)) {
+ deps.pathToNames[path] = {};
+ }
+ deps.pathToNames[path][provide] = true;
+ }
+ for (var j = 0; require = requires[j]; j++) {
+ if (!(path in deps.requires)) {
+ deps.requires[path] = {};
+ }
+ deps.requires[path][require] = true;
+ }
+ }
+};
+
+
+/**
+ * Implements a system for the dynamic resolution of dependencies
+ * that works in parallel with the BUILD system. Note that all calls
+ * to goog.require will be stripped by the JSCompiler when the
+ * --closure_pass option is used.
+ * @param {string} rule Rule to include, in the form goog.package.part.
+ */
+goog.require = function(rule) {
+
+ // if the object already exists we do not need do do anything
+ if (!COMPILED) {
+ if (goog.getObjectByName(rule)) {
+ return;
+ }
+ var path = goog.getPathFromDeps_(rule);
+ if (path) {
+ goog.included_[path] = true;
+ goog.writeScripts_();
+ } else {
+ // NOTE(nicksantos): We could always throw an error, but this would break
+ // legacy users that depended on this failing silently. Instead, the
+ // compiler should warn us when there are invalid goog.require calls.
+ // For now, we simply give clients a way to turn strict mode on.
+ if (goog.useStrictRequires) {
+ throw new Error('goog.require could not find: ' + rule);
+ }
+ }
+ }
+};
+
+
+/**
+ * Whether goog.require should throw an exception if it fails.
+ * @type {boolean}
+ */
+goog.useStrictRequires = false;
+
+
+/**
+ * Path for included scripts
+ * @type {string}
+ */
+goog.basePath = '';
+
+
+/**
+ * Null function used for default values of callbacks, etc.
+ * @type {!Function}
+ */
+goog.nullFunction = function() {};
+
+
+/**
+ * The identity function. Returns its first argument.
+ *
+ * @param {*} var_args The arguments of the function.
+ * @return {*} The first argument.
+ * @deprecated Use goog.functions.identity instead.
+ */
+goog.identityFunction = function(var_args) {
+ return arguments[0];
+};
+
+
+/**
+ * When defining a class Foo with an abstract method bar(), you can do:
+ *
+ * Foo.prototype.bar = goog.abstractMethod
+ *
+ * Now if a subclass of Foo fails to override bar(), an error
+ * will be thrown when bar() is invoked.
+ *
+ * Note: This does not take the name of the function to override as
+ * an argument because that would make it more difficult to obfuscate
+ * our JavaScript code.
+ *
+ * @throws {Error} when invoked to indicate the method should be
+ * overridden.
+ */
+goog.abstractMethod = function() {
+ throw Error('unimplemented abstract method');
+};
+
+
+/**
+ * Adds a {@code getInstance} static method that always return the same instance
+ * object.
+ * @param {!Function} ctor The constructor for the class to add the static
+ * method to.
+ */
+goog.addSingletonGetter = function(ctor) {
+ ctor.getInstance = function() {
+ return ctor.instance_ || (ctor.instance_ = new ctor());
+ };
+};
+
+
+if (!COMPILED) {
+ /**
+ * Object used to keep track of urls that have already been added. This
+ * record allows the prevention of circular dependencies.
+ * @type {Object}
+ * @private
+ */
+ goog.included_ = {};
+
+
+ /**
+ * This object is used to keep track of dependencies and other data that is
+ * used for loading scripts
+ * @private
+ * @type {Object}
+ */
+ goog.dependencies_ = {
+ pathToNames: {}, // 1 to many
+ nameToPath: {}, // 1 to 1
+ requires: {}, // 1 to many
+ visited: {}, // used when resolving dependencies to prevent us from
+ // visiting the file twice
+ written: {} // used to keep track of script files we have written
+ };
+
+
+ /**
+ * Tries to detect the base path of the base.js script that bootstraps Closure
+ * @private
+ */
+ goog.findBasePath_ = function() {
+ var doc = goog.global.document;
+ if (typeof doc == 'undefined') {
+ return;
+ }
+ if (goog.global.CLOSURE_BASE_PATH) {
+ goog.basePath = goog.global.CLOSURE_BASE_PATH;
+ return;
+ } else {
+ // HACKHACK to hide compiler warnings :(
+ goog.global.CLOSURE_BASE_PATH = null;
+ }
+ var scripts = doc.getElementsByTagName('script');
+ for (var script, i = 0; script = scripts[i]; i++) {
+ var src = script.src;
+ var l = src.length;
+ if (src.substr(l - 7) == 'base.js') {
+ goog.basePath = src.substr(0, l - 7);
+ return;
+ }
+ }
+ };
+
+
+ /**
+ * Writes a script tag if, and only if, that script hasn't already been added
+ * to the document. (Must be called at execution time)
+ * @param {string} src Script source.
+ * @private
+ */
+ goog.writeScriptTag_ = function(src) {
+ var doc = goog.global.document;
+ if (typeof doc != 'undefined' &&
+ !goog.dependencies_.written[src]) {
+ goog.dependencies_.written[src] = true;
+ doc.write('<script type="text/javascript" src="' +
+ src + '"></' + 'script>');
+ }
+ };
+
+
+ /**
+ * Resolves dependencies based on the dependencies added using addDependency
+ * and calls writeScriptTag_ in the correct order.
+ * @private
+ */
+ goog.writeScripts_ = function() {
+ // the scripts we need to write this time
+ var scripts = [];
+ var seenScript = {};
+ var deps = goog.dependencies_;
+
+ function visitNode(path) {
+ if (path in deps.written) {
+ return;
+ }
+
+ // we have already visited this one. We can get here if we have cyclic
+ // dependencies
+ if (path in deps.visited) {
+ if (!(path in seenScript)) {
+ seenScript[path] = true;
+ scripts.push(path);
+ }
+ return;
+ }
+
+ deps.visited[path] = true;
+
+ if (path in deps.requires) {
+ for (var requireName in deps.requires[path]) {
+ if (requireName in deps.nameToPath) {
+ visitNode(deps.nameToPath[requireName]);
+ } else {
+ throw Error('Undefined nameToPath for ' + requireName);
+ }
+ }
+ }
+
+ if (!(path in seenScript)) {
+ seenScript[path] = true;
+ scripts.push(path);
+ }
+ }
+
+ for (var path in goog.included_) {
+ if (!deps.written[path]) {
+ visitNode(path);
+ }
+ }
+
+ for (var i = 0; i < scripts.length; i++) {
+ if (scripts[i]) {
+ goog.writeScriptTag_(goog.basePath + scripts[i]);
+ } else {
+ throw Error('Undefined script input');
+ }
+ }
+ };
+
+
+ /**
+ * Looks at the dependency rules and tries to determine the script file that
+ * fulfills a particular rule.
+ * @param {string} rule In the form goog.namespace.Class or project.script.
+ * @return {string?} Url corresponding to the rule, or null.
+ * @private
+ */
+ goog.getPathFromDeps_ = function(rule) {
+ if (rule in goog.dependencies_.nameToPath) {
+ return goog.dependencies_.nameToPath[rule];
+ } else {
+ return null;
+ }
+ };
+
+ goog.findBasePath_();
+ // start CEEE changes {{
+ // This file is injected into the page using the Firefox sandbox mechanism.
+ // There is no generated deps.js file to inject with it.
+ //goog.writeScriptTag_(goog.basePath + 'deps.js');
+ // }} end CEEE changes
+}
+
+
+
+//==============================================================================
+// Language Enhancements
+//==============================================================================
+
+
+/**
+ * This is a "fixed" version of the typeof operator. It differs from the typeof
+ * operator in such a way that null returns 'null' and arrays return 'array'.
+ * @param {*} value The value to get the type of.
+ * @return {string} The name of the type.
+ */
+goog.typeOf = function(value) {
+ var s = typeof value;
+ if (s == 'object') {
+ if (value) {
+ // We cannot use constructor == Array or instanceof Array because
+ // different frames have different Array objects. In IE6, if the iframe
+ // where the array was created is destroyed, the array loses its
+ // prototype. Then dereferencing val.splice here throws an exception, so
+ // we can't use goog.isFunction. Calling typeof directly returns 'unknown'
+ // so that will work. In this case, this function will return false and
+ // most array functions will still work because the array is still
+ // array-like (supports length and []) even though it has lost its
+ // prototype.
+ // Mark Miller noticed that Object.prototype.toString
+ // allows access to the unforgeable [[Class]] property.
+ // 15.2.4.2 Object.prototype.toString ( )
+ // When the toString method is called, the following steps are taken:
+ // 1. Get the [[Class]] property of this object.
+ // 2. Compute a string value by concatenating the three strings
+ // "[object ", Result(1), and "]".
+ // 3. Return Result(2).
+ // and this behavior survives the destruction of the execution context.
+ if (value instanceof Array || // Works quickly in same execution context.
+ // If value is from a different execution context then
+ // !(value instanceof Object), which lets us early out in the common
+ // case when value is from the same context but not an array.
+ // The {if (value)} check above means we don't have to worry about
+ // undefined behavior of Object.prototype.toString on null/undefined.
+ //
+ // HACK: In order to use an Object prototype method on the arbitrary
+ // value, the compiler requires the value be cast to type Object,
+ // even though the ECMA spec explicitly allows it.
+ (!(value instanceof Object) &&
+ Object.prototype.toString.call(
+ /** @type {Object} */(value)) == '[object Array]')) {
+ return 'array';
+ }
+ // HACK: There is still an array case that fails.
+ // function ArrayImpostor() {}
+ // ArrayImpostor.prototype = [];
+ // var impostor = new ArrayImpostor;
+ // this can be fixed by getting rid of the fast path
+ // (value instanceof Array) and solely relying on
+ // (value && Object.prototype.toString.vall(value) === '[object Array]')
+ // but that would require many more function calls and is not warranted
+ // unless closure code is receiving objects from untrusted sources.
+
+ // IE in cross-window calls does not correctly marshal the function type
+ // (it appears just as an object) so we cannot use just typeof val ==
+ // 'function'. However, if the object has a call property, it is a
+ // function.
+ if (typeof value.call != 'undefined') {
+ return 'function';
+ }
+ } else {
+ return 'null';
+ }
+
+ // In Safari typeof nodeList returns 'function', and on Firefox
+ // typeof behaves similarly for HTML{Applet,Embed,Object}Elements
+ // and RegExps. We would like to return object for those and we can
+ // detect an invalid function by making sure that the function
+ // object has a call method.
+ } else if (s == 'function' && typeof value.call == 'undefined') {
+ return 'object';
+ }
+ return s;
+};
+
+
+/**
+ * Safe way to test whether a property is enumarable. It allows testing
+ * for enumerable on objects where 'propertyIsEnumerable' is overridden or
+ * does not exist (like DOM nodes in IE). Does not use browser native
+ * Object.propertyIsEnumerable.
+ * @param {Object} object The object to test if the property is enumerable.
+ * @param {string} propName The property name to check for.
+ * @return {boolean} True if the property is enumarable.
+ * @private
+ */
+goog.propertyIsEnumerableCustom_ = function(object, propName) {
+ // KJS in Safari 2 is not ECMAScript compatible and lacks crucial methods
+ // such as propertyIsEnumerable. We therefore use a workaround.
+ // Does anyone know a more efficient work around?
+ if (propName in object) {
+ for (var key in object) {
+ if (key == propName &&
+ Object.prototype.hasOwnProperty.call(object, propName)) {
+ return true;
+ }
+ }
+ }
+ return false;
+};
+
+
+if (Object.prototype.propertyIsEnumerable) {
+ /**
+ * Safe way to test whether a property is enumarable. It allows testing
+ * for enumerable on objects where 'propertyIsEnumerable' is overridden or
+ * does not exist (like DOM nodes in IE).
+ * @param {Object} object The object to test if the property is enumerable.
+ * @param {string} propName The property name to check for.
+ * @return {boolean} True if the property is enumarable.
+ * @private
+ */
+ goog.propertyIsEnumerable_ = function(object, propName) {
+ // In IE if object is from another window, cannot use propertyIsEnumerable
+ // from this window's Object. Will raise a 'JScript object expected' error.
+ if (object instanceof Object) {
+ return Object.prototype.propertyIsEnumerable.call(object, propName);
+ } else {
+ return goog.propertyIsEnumerableCustom_(object, propName);
+ }
+ };
+} else {
+ // CEEE changes: Added the conditional above and this case as a bugfix.
+ goog.propertyIsEnumerable_ = goog.propertyIsEnumerableCustom_;
+}
+
+/**
+ * Returns true if the specified value is not |undefined|.
+ * WARNING: Do not use this to test if an object has a property. Use the in
+ * operator instead.
+ * @param {*} val Variable to test.
+ * @return {boolean} Whether variable is defined.
+ */
+goog.isDef = function(val) {
+ return typeof val != 'undefined';
+};
+
+
+/**
+ * Returns true if the specified value is |null|
+ * @param {*} val Variable to test.
+ * @return {boolean} Whether variable is null.
+ */
+goog.isNull = function(val) {
+ return val === null;
+};
+
+
+/**
+ * Returns true if the specified value is defined and not null
+ * @param {*} val Variable to test.
+ * @return {boolean} Whether variable is defined and not null.
+ */
+goog.isDefAndNotNull = function(val) {
+ return goog.isDef(val) && !goog.isNull(val);
+};
+
+
+/**
+ * Returns true if the specified value is an array
+ * @param {*} val Variable to test.
+ * @return {boolean} Whether variable is an array.
+ */
+goog.isArray = function(val) {
+ return goog.typeOf(val) == 'array';
+};
+
+
+/**
+ * Returns true if the object looks like an array. To qualify as array like
+ * the value needs to be either a NodeList or an object with a Number length
+ * property.
+ * @param {*} val Variable to test.
+ * @return {boolean} Whether variable is an array.
+ */
+goog.isArrayLike = function(val) {
+ var type = goog.typeOf(val);
+ return type == 'array' || type == 'object' && typeof val.length == 'number';
+};
+
+
+/**
+ * Returns true if the object looks like a Date. To qualify as Date-like
+ * the value needs to be an object and have a getFullYear() function.
+ * @param {*} val Variable to test.
+ * @return {boolean} Whether variable is a like a Date.
+ */
+goog.isDateLike = function(val) {
+ return goog.isObject(val) && typeof val.getFullYear == 'function';
+};
+
+
+/**
+ * Returns true if the specified value is a string
+ * @param {*} val Variable to test.
+ * @return {boolean} Whether variable is a string.
+ */
+goog.isString = function(val) {
+ return typeof val == 'string';
+};
+
+
+/**
+ * Returns true if the specified value is a boolean
+ * @param {*} val Variable to test.
+ * @return {boolean} Whether variable is boolean.
+ */
+goog.isBoolean = function(val) {
+ return typeof val == 'boolean';
+};
+
+
+/**
+ * Returns true if the specified value is a number
+ * @param {*} val Variable to test.
+ * @return {boolean} Whether variable is a number.
+ */
+goog.isNumber = function(val) {
+ return typeof val == 'number';
+};
+
+
+/**
+ * Returns true if the specified value is a function
+ * @param {*} val Variable to test.
+ * @return {boolean} Whether variable is a function.
+ */
+goog.isFunction = function(val) {
+ return goog.typeOf(val) == 'function';
+};
+
+
+/**
+ * Returns true if the specified value is an object. This includes arrays
+ * and functions.
+ * @param {*} val Variable to test.
+ * @return {boolean} Whether variable is an object.
+ */
+goog.isObject = function(val) {
+ var type = goog.typeOf(val);
+ return type == 'object' || type == 'array' || type == 'function';
+};
+
+
+/**
+ * Adds a hash code field to an object. The hash code is unique for the
+ * given object.
+ * @param {Object} obj The object to get the hash code for.
+ * @return {number} The hash code for the object.
+ */
+goog.getHashCode = function(obj) {
+ // In IE, DOM nodes do not extend Object so they do not have this method.
+ // we need to check hasOwnProperty because the proto might have this set.
+
+ if (obj.hasOwnProperty && obj.hasOwnProperty(goog.HASH_CODE_PROPERTY_)) {
+ var hashCode = obj[goog.HASH_CODE_PROPERTY_];
+ // CEEE changes: workaround for Chrome bug 1252508.
+ if (hashCode) {
+ return hashCode;
+ }
+ }
+ if (!obj[goog.HASH_CODE_PROPERTY_]) {
+ obj[goog.HASH_CODE_PROPERTY_] = ++goog.hashCodeCounter_;
+ }
+ return obj[goog.HASH_CODE_PROPERTY_];
+};
+
+
+/**
+ * Removes the hash code field from an object.
+ * @param {Object} obj The object to remove the field from.
+ */
+goog.removeHashCode = function(obj) {
+ // DOM nodes in IE are not instance of Object and throws exception
+ // for delete. Instead we try to use removeAttribute
+ if ('removeAttribute' in obj) {
+ obj.removeAttribute(goog.HASH_CODE_PROPERTY_);
+ }
+ /** @preserveTry */
+ try {
+ delete obj[goog.HASH_CODE_PROPERTY_];
+ } catch (ex) {
+ }
+};
+
+
+/**
+ * {String} Name for hash code property
+ * @private
+ */
+goog.HASH_CODE_PROPERTY_ = 'closure_hashCode_';
+
+
+/**
+ * @type {number} Counter for hash codes.
+ * @private
+ */
+goog.hashCodeCounter_ = 0;
+
+
+/**
+ * Clone an object/array (recursively)
+ * @param {Object} proto Object to clone.
+ * @return {Object} Clone of x;.
+ */
+goog.cloneObject = function(proto) {
+ var type = goog.typeOf(proto);
+ if (type == 'object' || type == 'array') {
+ if (proto.clone) {
+ return proto.clone.call(proto);
+ }
+ var clone = type == 'array' ? [] : {};
+ for (var key in proto) {
+ clone[key] = goog.cloneObject(proto[key]);
+ }
+ return clone;
+ }
+
+ return proto;
+};
+
+
+/**
+ * Forward declaration for the clone method. This is necessary until the
+ * compiler can better support duck-typing constructs as used in
+ * goog.cloneObject.
+ *
+ * @type {Function}
+ */
+Object.prototype.clone;
+
+
+/**
+ * Partially applies this function to a particular 'this object' and zero or
+ * more arguments. The result is a new function with some arguments of the first
+ * function pre-filled and the value of |this| 'pre-specified'.<br><br>
+ *
+ * Remaining arguments specified at call-time are appended to the pre-
+ * specified ones.<br><br>
+ *
+ * Also see: {@link #partial}.<br><br>
+ *
+ * Note that bind and partial are optimized such that repeated calls to it do
+ * not create more than one function object, so there is no additional cost for
+ * something like:<br>
+ *
+ * <pre>var g = bind(f, obj);
+ * var h = partial(g, 1, 2, 3);
+ * var k = partial(h, a, b, c);</pre>
+ *
+ * Usage:
+ * <pre>var barMethBound = bind(myFunction, myObj, 'arg1', 'arg2');
+ * barMethBound('arg3', 'arg4');</pre>
+ *
+ * @param {Function} fn A function to partially apply.
+ * @param {Object} selfObj Specifies the object which |this| should point to
+ * when the function is run. If the value is null or undefined, it will
+ * default to the global object.
+ * @param {Object} var_args Additional arguments that are partially
+ * applied to the function.
+ *
+ * @return {!Function} A partially-applied form of the function bind() was
+ * invoked as a method of.
+ */
+goog.bind = function(fn, selfObj, var_args) {
+ var boundArgs = fn.boundArgs_;
+
+ if (arguments.length > 2) {
+ var args = Array.prototype.slice.call(arguments, 2);
+ if (boundArgs) {
+ args.unshift.apply(args, boundArgs);
+ }
+ boundArgs = args;
+ }
+
+ selfObj = fn.boundSelf_ || selfObj;
+ fn = fn.boundFn_ || fn;
+
+ var newfn;
+ var context = selfObj || goog.global;
+
+ if (boundArgs) {
+ newfn = function() {
+ // Combine the static args and the new args into one big array
+ var args = Array.prototype.slice.call(arguments);
+ args.unshift.apply(args, boundArgs);
+ return fn.apply(context, args);
+ };
+ } else {
+ newfn = function() {
+ return fn.apply(context, arguments);
+ };
+ }
+
+ newfn.boundArgs_ = boundArgs;
+ newfn.boundSelf_ = selfObj;
+ newfn.boundFn_ = fn;
+
+ return newfn;
+};
+
+
+/**
+ * Like bind(), except that a 'this object' is not required. Useful when the
+ * target function is already bound.
+ *
+ * Usage:
+ * var g = partial(f, arg1, arg2);
+ * g(arg3, arg4);
+ *
+ * @param {Function} fn A function to partially apply.
+ * @param {Object} var_args Additional arguments that are partially
+ * applied to fn.
+ * @return {!Function} A partially-applied form of the function bind() was
+ * invoked as a method of.
+ */
+goog.partial = function(fn, var_args) {
+ var args = Array.prototype.slice.call(arguments, 1);
+ args.unshift(fn, null);
+ return goog.bind.apply(null, args);
+};
+
+
+/**
+ * Copies all the members of a source object to a target object.
+ * @param {Object} target Target.
+ * @param {Object} source Source.
+ * @deprecated Use goog.object.extend instead.
+ */
+goog.mixin = function(target, source) {
+ for (var x in source) {
+ target[x] = source[x];
+ }
+
+ // For IE the for-in-loop does not contain any properties that are not
+ // enumerable on the prototype object (for example, isPrototypeOf from
+ // Object.prototype) but also it will not include 'replace' on objects that
+ // extend String and change 'replace' (not that it is common for anyone to
+ // extend anything except Object).
+};
+
+
+/**
+ * A simple wrapper for new Date().getTime().
+ *
+ * @return {number} An integer value representing the number of milliseconds
+ * between midnight, January 1, 1970 and the current time.
+ */
+goog.now = Date.now || (function() {
+ return new Date().getTime();
+});
+
+
+/**
+ * Evals javascript in the global scope. In IE this uses execScript, other
+ * browsers use goog.global.eval. If goog.global.eval does not evaluate in the
+ * global scope (for example, in Safari), appends a script tag instead.
+ * Throws an exception if neither execScript or eval is defined.
+ * @param {string} script JavaScript string.
+ */
+goog.globalEval = function(script) {
+ if (goog.global.execScript) {
+ goog.global.execScript(script, 'JavaScript');
+ } else if (goog.global.eval) {
+ // Test to see if eval works
+ if (goog.evalWorksForGlobals_ == null) {
+ goog.global.eval('var _et_ = 1;');
+ if (typeof goog.global['_et_'] != 'undefined') {
+ delete goog.global['_et_'];
+ goog.evalWorksForGlobals_ = true;
+ } else {
+ goog.evalWorksForGlobals_ = false;
+ }
+ }
+
+ if (goog.evalWorksForGlobals_) {
+ goog.global.eval(script);
+ } else {
+ var doc = goog.global.document;
+ var scriptElt = doc.createElement('script');
+ scriptElt.type = 'text/javascript';
+ scriptElt.defer = false;
+ // Note(pupius): can't use .innerHTML since "t('<test>')" will fail and
+ // .text doesn't work in Safari 2. Therefore we append a text node.
+ scriptElt.appendChild(doc.createTextNode(script));
+ doc.body.appendChild(scriptElt);
+ doc.body.removeChild(scriptElt);
+ }
+ } else {
+ throw Error('goog.globalEval not available');
+ }
+};
+
+
+/**
+ * Forward declaration of a type name.
+ *
+ * A call of the form
+ * goog.declareType('goog.MyClass');
+ * tells JSCompiler "goog.MyClass is not a hard dependency of this file.
+ * But it may appear in the type annotations here. This is to assure
+ * you that the class does indeed exist, even if it's not declared in the
+ * final binary."
+ *
+ * In uncompiled code, does nothing.
+ * @param {string} typeName The name of the type.
+ */
+goog.declareType = function(typeName) {};
+
+
+/**
+ * A macro for defining composite types.
+ *
+ * By assigning goog.typedef to a name, this tells JSCompiler that this is not
+ * the name of a class, but rather it's the name of a composite type.
+ *
+ * For example,
+ * /** @type {Array|NodeList} / goog.ArrayLike = goog.typedef;
+ * will tell JSCompiler to replace all appearances of goog.ArrayLike in type
+ * definitions with the union of Array and NodeList.
+ *
+ * Does nothing in uncompiled code.
+ */
+goog.typedef = true;
+
+
+/**
+ * Handles strings that are intended to be used as CSS class names.
+ *
+ * Without JS Compiler the arguments are simple joined with a hyphen and passed
+ * through unaltered.
+ *
+ * With the JS Compiler the arguments are inlined, e.g:
+ * var x = goog.getCssName('foo');
+ * var y = goog.getCssName(this.baseClass, 'active');
+ * becomes:
+ * var x= 'foo';
+ * var y = this.baseClass + '-active';
+ *
+ * If a CSS renaming map is passed to the compiler it will replace symbols in
+ * the classname. If one argument is passed it will be processed, if two are
+ * passed only the modifier will be processed, as it is assumed the first
+ * argument was generated as a result of calling goog.getCssName.
+ *
+ * Names are split on 'hyphen' and processed in parts such that the following
+ * are equivalent:
+ * var base = goog.getCssName('baseclass');
+ * goog.getCssName(base, 'modifier');
+ * goog.getCSsName('baseclass-modifier');
+ *
+ * If any part does not appear in the renaming map a warning is logged and the
+ * original, unobfuscated class name is inlined.
+ *
+ * @param {string} className The class name.
+ * @param {string} opt_modifier A modifier to be appended to the class name.
+ * @return {string} The class name or the concatenation of the class name and
+ * the modifier.
+ */
+goog.getCssName = function(className, opt_modifier) {
+ return className + (opt_modifier ? '-' + opt_modifier : '');
+};
+
+
+/**
+ * Abstract implementation of goog.getMsg for use with localized messages.
+ * @param {string} str Translatable string, places holders in the form {$foo}.
+ * @param {Object} opt_values Map of place holder name to value.
+ * @return {string} message with placeholders filled.
+ */
+goog.getMsg = function(str, opt_values) {
+ var values = opt_values || {};
+ for (var key in values) {
+ str = str.replace(new RegExp('\\{\\$' + key + '\\}', 'gi'), values[key]);
+ }
+ return str;
+};
+
+
+/**
+ * Exposes an unobfuscated global namespace path for the given object.
+ * Note that fields of the exported object *will* be obfuscated,
+ * unless they are exported in turn via this function or
+ * goog.exportProperty
+ *
+ * <p>Also handy for making public items that are defined in anonymous
+ * closures.
+ *
+ * ex. goog.exportSymbol('Foo', Foo);
+ *
+ * ex. goog.exportSymbol('public.path.Foo.staticFunction',
+ * Foo.staticFunction);
+ * public.path.Foo.staticFunction();
+ *
+ * ex. goog.exportSymbol('public.path.Foo.prototype.myMethod',
+ * Foo.prototype.myMethod);
+ * new public.path.Foo().myMethod();
+ *
+ * @param {string} publicPath Unobfuscated name to export.
+ * @param {Object} object Object the name should point to.
+ * @param {Object} opt_objectToExportTo The object to add the path to; default
+ * is |goog.global|.
+ */
+goog.exportSymbol = function(publicPath, object, opt_objectToExportTo) {
+ goog.exportPath_(publicPath, object, opt_objectToExportTo);
+};
+
+
+/**
+ * Exports a property unobfuscated into the object's namespace.
+ * ex. goog.exportProperty(Foo, 'staticFunction', Foo.staticFunction);
+ * ex. goog.exportProperty(Foo.prototype, 'myMethod', Foo.prototype.myMethod);
+ * @param {Object} object Object whose static property is being exported.
+ * @param {string} publicName Unobfuscated name to export.
+ * @param {Object} symbol Object the name should point to.
+ */
+goog.exportProperty = function(object, publicName, symbol) {
+ object[publicName] = symbol;
+};
+
+
+/**
+ * Inherit the prototype methods from one constructor into another.
+ *
+ * Usage:
+ * <pre>
+ * function ParentClass(a, b) { }
+ * ParentClass.prototype.foo = function(a) { }
+ *
+ * function ChildClass(a, b, c) {
+ * ParentClass.call(this, a, b);
+ * }
+ *
+ * goog.inherits(ChildClass, ParentClass);
+ *
+ * var child = new ChildClass('a', 'b', 'see');
+ * child.foo(); // works
+ * </pre>
+ *
+ * In addition, a superclass' implementation of a method can be invoked
+ * as follows:
+ *
+ * <pre>
+ * ChildClass.prototype.foo = function(a) {
+ * ChildClass.superClass_.foo.call(this, a);
+ * // other code
+ * };
+ * </pre>
+ *
+ * @param {Function} childCtor Child class.
+ * @param {Function} parentCtor Parent class.
+ */
+goog.inherits = function(childCtor, parentCtor) {
+ /** @constructor */
+ function tempCtor() {};
+ tempCtor.prototype = parentCtor.prototype;
+ childCtor.superClass_ = parentCtor.prototype;
+ childCtor.prototype = new tempCtor();
+ childCtor.prototype.constructor = childCtor;
+};
+
+
+//==============================================================================
+// Extending Function
+//==============================================================================
+
+
+/**
+ * @define {boolean} Whether to extend Function.prototype.
+ * Use --define='goog.MODIFY_FUNCTION_PROTOTYPES=false' to change.
+ */
+goog.MODIFY_FUNCTION_PROTOTYPES = true;
+
+if (goog.MODIFY_FUNCTION_PROTOTYPES) {
+
+
+ /**
+ * An alias to the {@link goog.bind()} global function.
+ *
+ * Usage:
+ * var g = f.bind(obj, arg1, arg2);
+ * g(arg3, arg4);
+ *
+ * @param {Object} selfObj Specifies the object to which |this| should point
+ * when the function is run. If the value is null or undefined, it will
+ * default to the global object.
+ * @param {Object} var_args Additional arguments that are partially
+ * applied to fn.
+ * @return {!Function} A partially-applied form of the Function on which
+ * bind() was invoked as a method.
+ * @deprecated Use the static function goog.bind instead.
+ */
+ Function.prototype.bind = function(selfObj, var_args) {
+ if (arguments.length > 1) {
+ var args = Array.prototype.slice.call(arguments, 1);
+ args.unshift(this, selfObj);
+ return goog.bind.apply(null, args);
+ } else {
+ return goog.bind(this, selfObj);
+ }
+ };
+
+
+ /**
+ * An alias to the {@link goog.partial()} static function.
+ *
+ * Usage:
+ * var g = f.partial(arg1, arg2);
+ * g(arg3, arg4);
+ *
+ * @param {Object} var_args Additional arguments that are partially
+ * applied to fn.
+ * @return {!Function} A partially-applied form of the function partial() was
+ * invoked as a method of.
+ * @deprecated Use the static function goog.partial instead.
+ */
+ Function.prototype.partial = function(var_args) {
+ var args = Array.prototype.slice.call(arguments);
+ args.unshift(this, null);
+ return goog.bind.apply(null, args);
+ };
+
+
+ /**
+ * Inherit the prototype methods from one constructor into another.
+ * @param {Function} parentCtor Parent class.
+ * @see goog.inherits
+ * @deprecated Use the static function goog.inherits instead.
+ */
+ Function.prototype.inherits = function(parentCtor) {
+ goog.inherits(this, parentCtor);
+ };
+
+
+ /**
+ * Mixes in an object's properties and methods into the callee's prototype.
+ * Basically mixin based inheritance, thus providing an alternative method for
+ * adding properties and methods to a class' prototype.
+ *
+ * <pre>
+ * function X() {}
+ * X.mixin({
+ * one: 1,
+ * two: 2,
+ * three: 3,
+ * doit: function() { return this.one + this.two + this.three; }
+ * });
+ *
+ * function Y() { }
+ * Y.mixin(X.prototype);
+ * Y.prototype.four = 15;
+ * Y.prototype.doit2 = function() { return this.doit() + this.four; }
+ * });
+ *
+ * // or
+ *
+ * function Y() { }
+ * Y.inherits(X);
+ * Y.mixin({
+ * one: 10,
+ * four: 15,
+ * doit2: function() { return this.doit() + this.four; }
+ * });
+ * </pre>
+ *
+ * @param {Object} source from which to copy properties.
+ * @see goog.mixin
+ * @deprecated Use the static function goog.object.extend instead.
+ */
+ Function.prototype.mixin = function(source) {
+ goog.mixin(this.prototype, source);
+ };
+} \ No newline at end of file
diff --git a/ceee/ie/plugin/scripting/ceee_bootstrap.js b/ceee/ie/plugin/scripting/ceee_bootstrap.js
new file mode 100644
index 0000000..28a0a7d
--- /dev/null
+++ b/ceee/ie/plugin/scripting/ceee_bootstrap.js
@@ -0,0 +1,145 @@
+// 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.
+
+/**
+ * @fileoverview this file provides the bootstrap interface between the
+ * Chrome event and extension bindings JavaScript files, and the CEEE
+ * native IE interface, as well as initialization hooks for the native
+ * interface.
+ */
+
+
+// Console is diverted to nativeContentScriptApi.Log method.
+var console = console || {};
+
+// Any function declared native in the Chrome extension bindings files
+// is diverted to the ceee namespace to allow the IE JS engine
+// to grok the code.
+var ceee = ceee || {};
+
+(function () {
+// Keep a reference to the global environment in
+// effect during boostrap script parsing.
+var global = this;
+
+var chromeHidden = {};
+var nativeContentScriptApi = null;
+
+// Supply a JSON implementation by leeching off closure.
+global.JSON = goog.json;
+global.JSON.stringify = JSON.serialize;
+
+ceee.AttachEvent = function (eventName) {
+ nativeContentScriptApi.AttachEvent(eventName);
+};
+
+ceee.DetachEvent = function (eventName) {
+ nativeContentScriptApi.DetachEvent(eventName);
+};
+
+ceee.OpenChannelToExtension = function (sourceId, targetId, name) {
+ return nativeContentScriptApi.OpenChannelToExtension(sourceId,
+ targetId,
+ name);
+};
+
+ceee.CloseChannel = function (portId) {
+ return nativeContentScriptApi.CloseChannel(portId);
+};
+
+ceee.PortAddRef = function (portId) {
+ return nativeContentScriptApi.PortAddRef(portId);
+};
+
+ceee.PortRelease = function (portId) {
+ return nativeContentScriptApi.PortRelease(portId);
+};
+
+ceee.PostMessage = function (portId, msg) {
+ return nativeContentScriptApi.PostMessage(portId, msg);
+};
+
+ceee.GetChromeHidden = function () {
+ return chromeHidden;
+};
+
+// This function is invoked from the native CEEE implementation by name
+// to pass in the native CEEE interface implementation at the start of
+// script engine initialization. This allows us to provide logging
+// and any other required or convenient services during the initialization
+// of other boostrap scripts.
+ceee.startInit_ = function (nativenativeContentScriptApi, extensionId) {
+ nativeContentScriptApi = nativenativeContentScriptApi;
+};
+
+// Last uninitialization callback.
+ceee.onUnload_ = function () {
+ // Dispatch the onUnload event.
+ chromeHidden.dispatchOnUnload();
+
+ // Release the native API as very last act.
+ nativeContentScriptApi = null;
+};
+
+// This function is invoked from the native CEEE implementation by name
+// to pass in the extension ID, and to allow any final initialization of
+// the script environment before the content scripts themselves are loaded.
+ceee.endInit_ = function (nativenativeContentScriptApi, extensionId) {
+ chrome.initExtension(extensionId);
+
+ // Provide the native implementation with the the the
+ // event notification dispatchers.
+ nativeContentScriptApi.onLoad = chromeHidden.dispatchOnLoad;
+ nativeContentScriptApi.onUnload = ceee.onUnload_;
+
+ // And the port notification dispatchers.
+ // function(portId, channelName, tab, extensionId)
+ nativeContentScriptApi.onPortConnect = chromeHidden.Port.dispatchOnConnect;
+
+ // function(portId)
+ nativeContentScriptApi.onPortDisconnect =
+ chromeHidden.Port.dispatchOnDisconnect;
+ // function(msg, portId)
+ nativeContentScriptApi.onPortMessage =
+ chromeHidden.Port.dispatchOnMessage;
+
+ // TODO(siggi@chromium.org): If there is a different global
+ // environment at this point (i.e. we have cloned the scripting
+ // engine for a new window) this is where we can restore goog,
+ // JSON and chrome.
+
+ // Delete the ceee namespace from globals.
+ delete ceee;
+}
+
+console.log = console.log || function (msg) {
+ if (nativeContentScriptApi)
+ nativeContentScriptApi.Log("info", msg);
+};
+
+console.error = console.error || function (msg) {
+ if (nativeContentScriptApi)
+ nativeContentScriptApi.Log("error", msg);
+}
+
+// Provide an indexOf member for arrays if it's not already there
+// to satisfy the Chrome extension bindings expectations.
+if (!Array.prototype.indexOf) {
+ Array.prototype.indexOf = function(elt /*, from*/) {
+ var len = this.length >>> 0;
+
+ var from = Number(arguments[1]) || 0;
+ from = (from < 0) ? Math.ceil(from) : Math.floor(from);
+ if (from < 0)
+ from += len;
+
+ for (; from < len; from++) {
+ if (from in this && this[from] === elt)
+ return from;
+ }
+ return -1;
+ }
+};
+
+})();
diff --git a/ceee/ie/plugin/scripting/content_script_manager.cc b/ceee/ie/plugin/scripting/content_script_manager.cc
new file mode 100644
index 0000000..0080159
--- /dev/null
+++ b/ceee/ie/plugin/scripting/content_script_manager.cc
@@ -0,0 +1,384 @@
+// 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.
+//
+// @file
+// Content script manager implementation.
+#include "ceee/ie/plugin/scripting/content_script_manager.h"
+
+#include "ceee/ie/common/ceee_module_util.h"
+#include "ceee/ie/plugin/bho/dom_utils.h"
+#include "ceee/ie/plugin/bho/frame_event_handler.h"
+#include "ceee/ie/plugin/scripting/content_script_native_api.h"
+#include "base/logging.h"
+#include "base/resource_util.h"
+#include "base/string_util.h"
+#include "base/utf_string_conversions.h"
+#include "ceee/common/com_utils.h"
+
+#include "toolband.h" // NOLINT
+
+namespace {
+// The list of bootstrap scripts we need to parse in a new scripting engine.
+// We store our content scripts by name, in RT_HTML resources. This allows
+// referring them by res: URLs, which makes debugging easier.
+struct BootstrapScript {
+ const wchar_t* name;
+ // A named function to be called after the script is executed.
+ const wchar_t* function_name;
+ std::wstring url;
+ std::wstring content;
+};
+
+BootstrapScript bootstrap_scripts[] = {
+ { L"base.js", NULL },
+ { L"json.js", NULL },
+ { L"ceee_bootstrap.js", L"ceee.startInit_" },
+ { L"event_bindings.js", NULL },
+ { L"renderer_extension_bindings.js", L"ceee.endInit_" }
+};
+
+bool bootstrap_scripts_loaded = false;
+
+// Load the bootstrap javascript resources to our cache.
+bool EnsureBoostrapScriptsLoaded() {
+ if (bootstrap_scripts_loaded)
+ return true;
+
+ ceee_module_util::AutoLock lock;
+ if (bootstrap_scripts_loaded)
+ return true;
+
+ HMODULE module = _AtlBaseModule.GetResourceInstance();
+
+ // And construct the base URL.
+ std::wstring base_url(L"ceee-content://bootstrap/");
+
+ // Retrieve the resources one by one and convert them to Unicode.
+ for (int i = 0; i < arraysize(bootstrap_scripts); ++i) {
+ const wchar_t* name = bootstrap_scripts[i].name;
+ HRSRC hres_info = ::FindResource(module, name, MAKEINTRESOURCE(RT_HTML));
+ if (hres_info == NULL)
+ return false;
+
+ DWORD data_size = ::SizeofResource(module, hres_info);
+ HGLOBAL hres = ::LoadResource(module, hres_info);
+ if (!hres)
+ return false;
+
+ void* resource = ::LockResource(hres);
+ if (!resource)
+ return false;
+ bool converted = UTF8ToWide(reinterpret_cast<const char*>(resource),
+ data_size,
+ &bootstrap_scripts[i].content);
+ if (!converted)
+ return false;
+
+ bootstrap_scripts[i].url = StringPrintf(L"%ls%ls", base_url.c_str(), name);
+ }
+
+ bootstrap_scripts_loaded = true;
+ return true;
+}
+
+HRESULT InvokeNamedFunction(IScriptHost* script_host,
+ const wchar_t* function_name,
+ VARIANT* args,
+ size_t num_args) {
+ // Get the named function.
+ CComVariant function_var;
+ HRESULT hr = script_host->RunExpression(function_name, &function_var);
+ if (FAILED(hr))
+ return hr;
+
+ // And invoke it with the the params.
+ if (V_VT(&function_var) != VT_DISPATCH)
+ return E_UNEXPECTED;
+
+ // Take over the IDispatch pointer.
+ CComDispatchDriver function_disp;
+ function_disp.Attach(V_DISPATCH(&function_var));
+ V_VT(&function_var) = VT_EMPTY;
+ V_DISPATCH(&function_var) = NULL;
+
+ return function_disp.InvokeN(static_cast<DISPID>(DISPID_VALUE),
+ args,
+ num_args);
+}
+
+} // namespace
+
+ContentScriptManager::ContentScriptManager() : require_all_frames_(false) {
+}
+
+ContentScriptManager::~ContentScriptManager() {
+ // TODO(siggi@chromium.org): This mandates teardown prior to
+ // deletion, is that necessary?
+ DCHECK(script_host_ == NULL);
+}
+
+HRESULT ContentScriptManager::GetOrCreateScriptHost(
+ IHTMLDocument2* document, IScriptHost** host) {
+ if (script_host_ == NULL) {
+ HRESULT hr = CreateScriptHost(&script_host_);
+ if (FAILED(hr)) {
+ LOG(ERROR) << "Failed to create script host " << com::LogHr(hr);
+ return hr;
+ }
+
+ hr = InitializeScriptHost(document, script_host_);
+ if (FAILED(hr)) {
+ LOG(ERROR) << "Failed to initialize script host " << com::LogHr(hr);
+ script_host_.Release();
+ return hr;
+ }
+
+ CComQIPtr<IObjectWithSite> script_host_with_site(script_host_);
+ // Our implementation of script host must always implement IObjectWithSite.
+ DCHECK(script_host_with_site != NULL);
+ hr = script_host_with_site->SetSite(document);
+ DCHECK(SUCCEEDED(hr));
+ }
+
+ DCHECK(script_host_ != NULL);
+
+ return script_host_.CopyTo(host);
+}
+
+HRESULT ContentScriptManager::CreateScriptHost(IScriptHost** script_host) {
+ return ScriptHost::CreateInitializedIID(IID_IScriptHost, script_host);
+}
+
+HRESULT ContentScriptManager::InitializeScriptHost(
+ IHTMLDocument2* document, IScriptHost* script_host) {
+ DCHECK(document != NULL);
+ DCHECK(script_host != NULL);
+
+ CComPtr<IExtensionPortMessagingProvider> messaging_provider;
+ HRESULT hr = host_->GetExtensionPortMessagingProvider(&messaging_provider);
+ hr = ContentScriptNativeApi::CreateInitialized(messaging_provider,
+ &native_api_);
+ if (FAILED(hr))
+ return hr;
+
+ std::wstring extension_id;
+ host_->GetExtensionId(&extension_id);
+ DCHECK(extension_id.size()) <<
+ "Need to revisit async loading of enabled extension list.";
+
+ // Execute the bootstrap scripts.
+ hr = BootstrapScriptHost(script_host, native_api_, extension_id.c_str());
+
+ CComPtr<IHTMLWindow2> window;
+ hr = document->get_parentWindow(&window);
+ if (FAILED(hr))
+ return hr;
+
+ // Register the window object and make its members global.
+ hr = script_host->RegisterScriptObject(L"window", window, true);
+
+ return hr;
+}
+
+HRESULT ContentScriptManager::BootstrapScriptHost(IScriptHost* script_host,
+ IDispatch* native_api,
+ const wchar_t* extension_id) {
+ bool loaded = EnsureBoostrapScriptsLoaded();
+ if (!loaded) {
+ NOTREACHED() << "Unable to load bootstrap scripts";
+ return E_UNEXPECTED;
+ }
+
+ // Note args go in reverse order.
+ CComVariant args[] = {
+ extension_id,
+ native_api
+ };
+
+ // Run the bootstrap scripts.
+ for (int i = 0; i < arraysize(bootstrap_scripts); ++i) {
+ const wchar_t* url = bootstrap_scripts[i].url.c_str();
+ HRESULT hr = script_host->RunScript(url,
+ bootstrap_scripts[i].content.c_str());
+ if (FAILED(hr)) {
+ NOTREACHED() << "Bootstrap script \"" << url << "\" failed to load";
+ return hr;
+ }
+
+ // Execute the script's named function if it exists.
+ const wchar_t* function_name = bootstrap_scripts[i].function_name;
+ if (function_name) {
+ hr = InvokeNamedFunction(script_host, function_name, args,
+ arraysize(args));
+ if (FAILED(hr)) {
+ NOTREACHED() << "Named function \"" << function_name << "\" not called";
+ return hr;
+ }
+ }
+ }
+
+ return S_OK;
+}
+
+HRESULT ContentScriptManager::LoadCss(const GURL& match_url,
+ IHTMLDocument2* document) {
+ // Get the CSS content for all matching user scripts and inject it.
+ std::string css_content;
+ HRESULT hr = host_->GetMatchingUserScriptsCssContent(match_url,
+ require_all_frames_,
+ &css_content);
+ if (FAILED(hr)) {
+ LOG(ERROR) << "Failed to get script content " << com::LogHr(hr);
+ return hr;
+ }
+
+ if (!css_content.empty())
+ return InsertCss(CA2W(css_content.c_str()), document);
+
+ return S_OK;
+}
+
+HRESULT ContentScriptManager::LoadStartScripts(const GURL& match_url,
+ IHTMLDocument2* document) {
+ // Run the document end scripts.
+ return LoadScriptsImpl(match_url, document, UserScript::DOCUMENT_START);
+}
+
+HRESULT ContentScriptManager::LoadEndScripts(const GURL& match_url,
+ IHTMLDocument2* document) {
+ // Run the document end scripts.
+ return LoadScriptsImpl(match_url, document, UserScript::DOCUMENT_END);
+}
+
+HRESULT ContentScriptManager::LoadScriptsImpl(const GURL& match_url,
+ IHTMLDocument2* document,
+ UserScript::RunLocation when) {
+ // Run the document start scripts.
+ UserScriptsLibrarian::JsFileList js_file_list;
+ HRESULT hr = host_->GetMatchingUserScriptsJsContent(match_url,
+ when,
+ require_all_frames_,
+ &js_file_list);
+ if (FAILED(hr)) {
+ LOG(ERROR) << "Failed to get script content " << com::LogHr(hr);
+ return hr;
+ }
+
+ // Early out to avoid initializing scripting if we don't need it.
+ if (js_file_list.size() == 0)
+ return S_OK;
+
+ for (size_t i = 0; i < js_file_list.size(); ++i) {
+ hr = ExecuteScript(CA2W(js_file_list[i].content.c_str()),
+ js_file_list[i].file_path.c_str(),
+ document);
+ if (FAILED(hr)) {
+ LOG(ERROR) << "Failed to inject JS content into page " << com::LogHr(hr);
+ return hr;
+ }
+ }
+
+ return S_OK;
+}
+
+HRESULT ContentScriptManager::ExecuteScript(const wchar_t* code,
+ const wchar_t* file_path,
+ IHTMLDocument2* document) {
+ CComPtr<IScriptHost> script_host;
+ HRESULT hr = GetOrCreateScriptHost(document, &script_host);
+ if (FAILED(hr)) {
+ LOG(ERROR) << "Failed to retrieve script host " << com::LogHr(hr);
+ return hr;
+ }
+
+ hr = script_host->RunScript(file_path, code);
+ if (FAILED(hr)) {
+ if (hr == OLESCRIPT_E_SYNTAX) {
+ // This function is used to execute scripts from extensions. We log
+ // syntax and runtime errors but we don't return a failing HR as we are
+ // executing third party code. A syntax or runtime error already causes
+ // the script host to prompt the user to debug.
+ LOG(ERROR) << "A syntax or runtime error occured while executing " <<
+ "script " << com::LogHr(hr);
+ } else {
+ LOG(ERROR) << "Failed to execute script " << com::LogHr(hr);
+ return hr;
+ }
+ }
+
+ return S_OK;
+}
+
+HRESULT ContentScriptManager::InsertCss(const wchar_t* code,
+ IHTMLDocument2* document) {
+ CComPtr<IHTMLDOMNode> head_node;
+ HRESULT hr = GetHeadNode(document, &head_node);
+ if (FAILED(hr)) {
+ LOG(ERROR) << "Failed to retrieve document head node " << com::LogHr(hr);
+ return hr;
+ }
+
+ hr = InjectStyleTag(document, head_node, code);
+ if (FAILED(hr)) {
+ LOG(ERROR) << "Failed to inject CSS content into page "
+ << com::LogHr(hr);
+ return hr;
+ }
+
+ return S_OK;
+}
+
+HRESULT ContentScriptManager::GetHeadNode(IHTMLDocument* document,
+ IHTMLDOMNode** dom_head) {
+ return DomUtils::GetHeadNode(document, dom_head);
+}
+
+HRESULT ContentScriptManager::InjectStyleTag(IHTMLDocument2* document,
+ IHTMLDOMNode* head_node,
+ const wchar_t* code) {
+ return DomUtils::InjectStyleTag(document, head_node, code);
+}
+
+HRESULT ContentScriptManager::Initialize(IFrameEventHandlerHost* host,
+ bool require_all_frames) {
+ DCHECK(host != NULL);
+ DCHECK(host_ == NULL);
+ host_ = host;
+
+ require_all_frames_ = require_all_frames;
+
+ return S_OK;
+}
+
+HRESULT ContentScriptManager::TearDown() {
+ if (native_api_ != NULL) {
+ CComPtr<ICeeeContentScriptNativeApi> native_api;
+ native_api_.QueryInterface(&native_api);
+ if (native_api != NULL) {
+ ContentScriptNativeApi* implementation =
+ static_cast<ContentScriptNativeApi*>(native_api.p);
+ // Teardown will release references from ContentScriptNativeApi to
+ // objects blocking release of BHO. Somehow ContentScriptNativeApi is
+ // alive after IScriptHost::Close().
+ implementation->TearDown();
+ }
+ native_api_.Release();
+ }
+ HRESULT hr = S_OK;
+ if (script_host_ != NULL) {
+ hr = script_host_->Close();
+ LOG_IF(ERROR, FAILED(hr)) << "ScriptHost::Close failed " << com::LogHr(hr);
+
+ CComQIPtr<IObjectWithSite> script_host_with_site(script_host_);
+ DCHECK(script_host_with_site != NULL);
+ hr = script_host_with_site->SetSite(NULL);
+ DCHECK(SUCCEEDED(hr));
+ }
+
+ // TODO(siggi@chromium.org): Kill off open extension ports.
+
+ script_host_.Release();
+
+ return hr;
+}
diff --git a/ceee/ie/plugin/scripting/content_script_manager.h b/ceee/ie/plugin/scripting/content_script_manager.h
new file mode 100644
index 0000000..f5b64cb
--- /dev/null
+++ b/ceee/ie/plugin/scripting/content_script_manager.h
@@ -0,0 +1,112 @@
+// 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.
+//
+// @file
+// Content script manager declaration.
+
+#ifndef CEEE_IE_PLUGIN_SCRIPTING_CONTENT_SCRIPT_MANAGER_H_
+#define CEEE_IE_PLUGIN_SCRIPTING_CONTENT_SCRIPT_MANAGER_H_
+
+#include <mshtml.h>
+#include <string>
+#include "base/basictypes.h"
+#include "ceee/ie/plugin/scripting/script_host.h"
+#include "chrome/common/extensions/user_script.h"
+#include "googleurl/src/gurl.h"
+
+// Forward declaration.
+class IFrameEventHandlerHost;
+
+// The content script manager implements CSS and content script injection
+// for an extension in a frame.
+// It also manages the ScriptHost, its bootstrapping and injecting the
+// native API instance to the bootstrap JavaScript code.
+class ContentScriptManager {
+ public:
+ ContentScriptManager();
+ virtual ~ContentScriptManager();
+
+ // Initialize before first use.
+ // @param host the frame event handler host we delegate requests to.
+ // @param require_all_frames Whether to require the all_frames property of the
+ // matched user scripts to be true.
+ HRESULT Initialize(IFrameEventHandlerHost* host, bool require_all_frames);
+
+ // Inject CSS for @p match_url into @p document
+ // Needs to be invoked on READYSTATE_LOADED transition for @p document.
+ virtual HRESULT LoadCss(const GURL& match_url, IHTMLDocument2* document);
+
+ // Inject start scripts for @p match_url into @p document
+ // Needs to be invoked on READYSTATE_LOADED transition for @p document.
+ virtual HRESULT LoadStartScripts(const GURL& match_url,
+ IHTMLDocument2* document);
+
+ // Inject end scripts for @p match_url into @p document.
+ // Needs to be invoked on READYSTATE_COMPLETE transition for @p document.
+ virtual HRESULT LoadEndScripts(const GURL& match_url,
+ IHTMLDocument2* document);
+
+ // Run the given script code in the document. The @p file_path argument is
+ // more debugging information, providing context for where the code came
+ // from. If the given script code has a syntax or runtime error, this
+ // function will still return S_OK since it is used to run third party code.
+ virtual HRESULT ExecuteScript(const wchar_t* code,
+ const wchar_t* file_path,
+ IHTMLDocument2* document);
+
+ // Inject the given CSS code into the document.
+ virtual HRESULT InsertCss(const wchar_t* code, IHTMLDocument2* document);
+
+ // Release any resources we've acquired or created.
+ virtual HRESULT TearDown();
+
+ protected:
+ // Implementation of script loading Load{Start|End}Scripts.
+ virtual HRESULT LoadScriptsImpl(const GURL& match_url,
+ IHTMLDocument2* document,
+ UserScript::RunLocation when);
+
+ // Retrieves the script host, creating it if does not already exist.
+ virtual HRESULT GetOrCreateScriptHost(IHTMLDocument2* document,
+ IScriptHost** host);
+
+ // Create a script host, virtual to make a unittest seam.
+ virtual HRESULT CreateScriptHost(IScriptHost** host);
+
+ // Load and initialize bootstrap scripts into host.
+ virtual HRESULT BootstrapScriptHost(IScriptHost* host,
+ IDispatch* api,
+ const wchar_t* extension_id);
+
+ // Initializes a newly-created script host.
+ virtual HRESULT InitializeScriptHost(IHTMLDocument2* document,
+ IScriptHost* host);
+
+ // Testing seam.
+ virtual HRESULT GetHeadNode(IHTMLDocument* document,
+ IHTMLDOMNode** head_node);
+ virtual HRESULT InjectStyleTag(IHTMLDocument2* document,
+ IHTMLDOMNode* head_node,
+ const wchar_t* code);
+
+ // The script engine that hosts the content scripts.
+ CComPtr<IScriptHost> script_host_;
+
+ // TODO(siggi@chromium.org): Stash the PageApi instance here for teardown.
+
+ // This is where we get scripts and CSS to inject.
+ CComPtr<IFrameEventHandlerHost> host_;
+
+ // Whether to require all frames to be true when matching users scripts.
+ bool require_all_frames_;
+
+ private:
+ // API accessible from javascript. This reference is required to release
+ // some resources from ContentScriptManager::Teardown.
+ // Must be instance of ContentScriptNativeApi.
+ CComPtr<IDispatch> native_api_;
+ DISALLOW_COPY_AND_ASSIGN(ContentScriptManager);
+};
+
+#endif // CEEE_IE_PLUGIN_SCRIPTING_CONTENT_SCRIPT_MANAGER_H_
diff --git a/ceee/ie/plugin/scripting/content_script_manager.rc b/ceee/ie/plugin/scripting/content_script_manager.rc
new file mode 100644
index 0000000..7551e5ef
--- /dev/null
+++ b/ceee/ie/plugin/scripting/content_script_manager.rc
@@ -0,0 +1,19 @@
+// 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.
+//
+// @file
+// Content script manager resources.
+#include <winres.h>
+
+#ifdef APSTUDIO_INVOKED
+#error Please edit as text.
+#endif
+
+// These are included as HTML resources to allow referring them
+// by res: URLs, which helps debugging.
+base.js HTML "base.js"
+json.js HTML "json.js"
+ceee_bootstrap.js HTML "ceee_bootstrap.js"
+event_bindings.js HTML "event_bindings.js"
+renderer_extension_bindings.js HTML "renderer_extension_bindings.js"
diff --git a/ceee/ie/plugin/scripting/content_script_manager_unittest.cc b/ceee/ie/plugin/scripting/content_script_manager_unittest.cc
new file mode 100644
index 0000000..0384875
--- /dev/null
+++ b/ceee/ie/plugin/scripting/content_script_manager_unittest.cc
@@ -0,0 +1,489 @@
+// 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.
+//
+// Content script manager implementation unit tests.
+#include "ceee/ie/plugin/scripting/content_script_manager.h"
+
+#include "base/logging.h"
+#include "base/utf_string_conversions.h"
+#include "ceee/common/initializing_coclass.h"
+#include "ceee/ie/plugin/scripting/content_script_native_api.h"
+#include "ceee/ie/plugin/scripting/userscripts_librarian.h"
+#include "ceee/ie/testing/mock_frame_event_handler_host.h"
+#include "ceee/testing/utils/mock_com.h"
+#include "ceee/testing/utils/test_utils.h"
+#include "ceee/testing/utils/dispex_mocks.h"
+#include "ceee/testing/utils/instance_count_mixin.h"
+#include "ceee/testing/utils/mshtml_mocks.h"
+
+#include "gmock/gmock.h"
+#include "gtest/gtest.h"
+
+namespace {
+
+using testing::_;
+using testing::CopyInterfaceToArgument;
+using testing::CopyVariantToArgument;
+using testing::Return;
+using testing::SetArgumentPointee;
+using testing::StrEq;
+using testing::StrictMock;
+
+typedef UserScriptsLibrarian::JsFileList JsFileList;
+typedef UserScriptsLibrarian::JsFile JsFile;
+
+using testing::InstanceCountMixin;
+using testing::IActiveScriptSiteMockImpl;
+using testing::IActiveScriptSiteDebugMockImpl;
+
+// An arbitrary valid extension ID.
+const wchar_t kExtensionId[] = L"fepbkochiplomghbdfgekenppangbiap";
+
+class TestingContentScriptManager: public ContentScriptManager {
+ public:
+ // Expose public for testing.
+ using ContentScriptManager::LoadScriptsImpl;
+ using ContentScriptManager::GetOrCreateScriptHost;
+ using ContentScriptManager::InitializeScriptHost;
+
+ MOCK_METHOD1(CreateScriptHost, HRESULT(IScriptHost** host));
+ MOCK_METHOD2(GetHeadNode,
+ HRESULT(IHTMLDocument* document, IHTMLDOMNode** dom_head));
+ MOCK_METHOD3(InjectStyleTag,
+ HRESULT(IHTMLDocument2* document, IHTMLDOMNode* dom_head,
+ const wchar_t* code));
+ MOCK_METHOD2(InsertCss,
+ HRESULT(const wchar_t* code, IHTMLDocument2* document));
+};
+
+
+class IScriptHostMockImpl: public IScriptHost {
+ public:
+ MOCK_METHOD3(RegisterScriptObject,
+ HRESULT(const wchar_t* name, IDispatch* disp_obj, bool global));
+ MOCK_METHOD2(RunScript,
+ HRESULT(const wchar_t* file_path, const wchar_t* code));
+ MOCK_METHOD3(RunScript, HRESULT(const wchar_t* file_path,
+ size_t char_offset,
+ const wchar_t* code));
+ MOCK_METHOD2(RunExpression,
+ HRESULT(const wchar_t* code, VARIANT* result));
+ MOCK_METHOD0(Close, HRESULT());
+};
+
+class MockDomNode
+ : public CComObjectRootEx<CComSingleThreadModel>,
+ public InitializingCoClass<MockDomNode>,
+ public StrictMock<IHTMLDOMNodeMockImpl> {
+ BEGIN_COM_MAP(MockDomNode)
+ COM_INTERFACE_ENTRY(IHTMLDOMNode)
+ END_COM_MAP()
+
+ HRESULT Initialize() {
+ return S_OK;
+ }
+};
+
+class MockScriptHost
+ : public CComObjectRootEx<CComSingleThreadModel>,
+ public InitializingCoClass<MockScriptHost>,
+ public InstanceCountMixin<MockScriptHost>,
+ public StrictMock<IActiveScriptSiteMockImpl>,
+ public StrictMock<IActiveScriptSiteDebugMockImpl>,
+ public IObjectWithSiteImpl<MockScriptHost>,
+ public StrictMock<IScriptHostMockImpl> {
+ public:
+ BEGIN_COM_MAP(MockScriptHost)
+ COM_INTERFACE_ENTRY(IActiveScriptSite)
+ COM_INTERFACE_ENTRY(IActiveScriptSiteDebug)
+ COM_INTERFACE_ENTRY(IObjectWithSite)
+ COM_INTERFACE_ENTRY_IID(IID_IScriptHost, IScriptHost)
+ END_COM_MAP()
+
+ HRESULT Initialize(MockScriptHost** script_host) {
+ *script_host = this;
+ return S_OK;
+ }
+};
+
+class MockWindow
+ : public CComObjectRootEx<CComSingleThreadModel>,
+ public InitializingCoClass<MockWindow>,
+ public InstanceCountMixin<MockWindow>,
+ public StrictMock<IHTMLWindow2MockImpl> {
+ public:
+ BEGIN_COM_MAP(MockWindow)
+ COM_INTERFACE_ENTRY(IHTMLWindow2)
+ END_COM_MAP()
+
+ HRESULT Initialize(MockWindow** self) {
+ *self = this;
+ return S_OK;
+ }
+};
+
+class MockDispatch
+ : public CComObjectRootEx<CComSingleThreadModel>,
+ public InitializingCoClass<MockDispatch>,
+ public InstanceCountMixin<MockDispatch>,
+ public StrictMock<testing::IDispatchExMockImpl> {
+ public:
+ BEGIN_COM_MAP(MockDispatch)
+ COM_INTERFACE_ENTRY(IDispatch)
+ END_COM_MAP()
+
+ HRESULT Initialize(MockDispatch** self) {
+ *self = this;
+ return S_OK;
+ }
+};
+
+class MockDocument
+ : public CComObjectRootEx<CComSingleThreadModel>,
+ public InitializingCoClass<MockDocument>,
+ public InstanceCountMixin<MockDocument>,
+ public StrictMock<IHTMLDocument2MockImpl> {
+ public:
+ BEGIN_COM_MAP(MockDocument)
+ COM_INTERFACE_ENTRY(IHTMLDocument2)
+ END_COM_MAP()
+
+ HRESULT Initialize(MockDocument** self) {
+ *self = this;
+ return S_OK;
+ }
+};
+
+class MockFrameEventHandlerAsApiHost
+ : public testing::MockFrameEventHandlerHostBase<
+ MockFrameEventHandlerAsApiHost> {
+ public:
+ HRESULT Initialize(MockFrameEventHandlerAsApiHost** self) {
+ *self = this;
+ return S_OK;
+ }
+
+ // Always supply ourselves as native API host.
+ HRESULT GetExtensionPortMessagingProvider(
+ IExtensionPortMessagingProvider** messaging_provider) {
+ GetUnknown()->AddRef();
+ *messaging_provider = this;
+ return S_OK;
+ }
+};
+
+class ContentScriptManagerTest: public testing::Test {
+ public:
+ void SetUp() {
+ ASSERT_HRESULT_SUCCEEDED(
+ MockFrameEventHandlerAsApiHost::CreateInitializedIID(
+ &frame_host_, IID_IFrameEventHandlerHost, &frame_host_keeper_));
+
+ ASSERT_HRESULT_SUCCEEDED(
+ MockScriptHost::CreateInitializedIID(
+ &script_host_, IID_IScriptHost, &script_host_keeper_));
+
+ ASSERT_HRESULT_SUCCEEDED(
+ MockWindow::CreateInitialized(&window_, &window_keeper_));
+
+ ASSERT_HRESULT_SUCCEEDED(
+ MockDocument::CreateInitialized(&document_, &document_keeper_));
+
+ ASSERT_HRESULT_SUCCEEDED(
+ MockDispatch::CreateInitialized(&function_, &function_keeper_));
+
+ // Set the document up to return the content window if queried.
+ EXPECT_CALL(*document_, get_parentWindow(_))
+ .WillRepeatedly(
+ DoAll(
+ CopyInterfaceToArgument<0>(window_keeper_),
+ Return(S_OK)));
+ }
+
+ void TearDown() {
+ document_ = NULL;
+ document_keeper_.Release();
+
+ window_ = NULL;
+ window_keeper_.Release();
+
+ script_host_ = NULL;
+ script_host_keeper_.Release();
+
+ frame_host_ = NULL;
+ frame_host_keeper_.Release();
+
+ function_ = NULL;
+ function_keeper_.Release();
+
+ // Test for leakage.
+ EXPECT_EQ(0, testing::InstanceCountMixinBase::all_instance_count());
+ }
+
+ // Set up to expect scripting initialization.
+ void ExpectScriptInitialization() {
+ EXPECT_CALL(*script_host_,
+ RegisterScriptObject(StrEq(L"window"), _, true))
+ .WillOnce(Return(S_OK));
+
+ EXPECT_CALL(*script_host_,
+ RunScript(testing::StartsWith(L"ceee-content://"), _))
+ .Times(5)
+ .WillRepeatedly(Return(S_OK));
+
+ // Return the mock function for start/end init.
+ EXPECT_CALL(*script_host_, RunExpression(StrEq(L"ceee.startInit_"), _))
+ .WillOnce(
+ DoAll(
+ CopyVariantToArgument<1>(CComVariant(function_keeper_)),
+ Return(S_OK)));
+ EXPECT_CALL(*script_host_, RunExpression(StrEq(L"ceee.endInit_"), _))
+ .WillOnce(
+ DoAll(
+ CopyVariantToArgument<1>(CComVariant(function_keeper_)),
+ Return(S_OK)));
+
+ EXPECT_CALL(*frame_host_, GetExtensionId(_)).WillOnce(DoAll(
+ SetArgumentPointee<0>(std::wstring(kExtensionId)),
+ Return(S_OK)));
+
+ // And expect two invocations.
+ // TODO(siggi@chromium.org): be more specific?
+ EXPECT_CALL(*function_, Invoke(_, _, _, _, _, _, _, _))
+ .Times(2)
+ .WillRepeatedly(Return(S_OK));
+ }
+
+ void ExpectCreateScriptHost(TestingContentScriptManager* manager) {
+ EXPECT_CALL(*manager, CreateScriptHost(_))
+ .WillOnce(
+ DoAll(
+ CopyInterfaceToArgument<0>(script_host_keeper_),
+ Return(S_OK)));
+ }
+
+ // Set up to expect NO scripting initialization.
+ void ExpectNoScriptInitialization() {
+ EXPECT_CALL(*script_host_, RegisterScriptObject(_, _, _))
+ .Times(0);
+ }
+
+ // Set up to expect a query CSS content.
+ void ExpectCSSQuery(const GURL& url) {
+ // Expect CSS query.
+ EXPECT_CALL(*frame_host_,
+ GetMatchingUserScriptsCssContent(url, false, _)).
+ WillOnce(Return(S_OK));
+ }
+
+ void SetScriptQueryResults(const GURL& url,
+ UserScript::RunLocation location,
+ const JsFileList& js_file_list) {
+ EXPECT_CALL(*frame_host_,
+ GetMatchingUserScriptsJsContent(url, location, false, _))
+ .WillOnce(
+ DoAll(
+ SetArgumentPointee<3>(js_file_list),
+ Return(S_OK)));
+ }
+
+ protected:
+ TestingContentScriptManager manager;
+
+ MockFrameEventHandlerAsApiHost* frame_host_;
+ CComPtr<IFrameEventHandlerHost> frame_host_keeper_;
+
+ MockScriptHost* script_host_;
+ CComPtr<IScriptHost> script_host_keeper_;
+
+ MockWindow* window_;
+ CComPtr<IHTMLWindow2> window_keeper_;
+
+ MockDocument* document_;
+ CComPtr<IHTMLDocument2> document_keeper_;
+
+ // Standin for JS functions.
+ MockDispatch *function_;
+ CComPtr<IDispatch> function_keeper_;
+};
+
+TEST_F(ContentScriptManagerTest, InitializationAndTearDownSucceed) {
+ ContentScriptManager manager;
+
+ ASSERT_HRESULT_SUCCEEDED(
+ manager.Initialize(frame_host_keeper_, false));
+
+ manager.TearDown();
+}
+
+TEST_F(ContentScriptManagerTest, InitializeScripting) {
+ TestingContentScriptManager manager;
+
+ EXPECT_HRESULT_SUCCEEDED(
+ manager.Initialize(frame_host_keeper_, false));
+
+ ExpectScriptInitialization();
+ ASSERT_HRESULT_SUCCEEDED(
+ manager.InitializeScriptHost(document_, script_host_));
+}
+
+const GURL kTestUrl(
+ L"http://www.google.com/search?q=Google+Buys+Iceland");
+
+// Verify that we don't initialize scripting when there's nothing to inject.
+TEST_F(ContentScriptManagerTest, NoScriptInitializationOnEmptyScripts) {
+ TestingContentScriptManager manager;
+ ASSERT_HRESULT_SUCCEEDED(
+ manager.Initialize(frame_host_keeper_, false));
+
+ // No script host creation.
+ EXPECT_CALL(manager, CreateScriptHost(_)).Times(0);
+
+ SetScriptQueryResults(kTestUrl, UserScript::DOCUMENT_START, JsFileList());
+ ASSERT_HRESULT_SUCCEEDED(
+ manager.LoadStartScripts(kTestUrl, document_));
+
+ SetScriptQueryResults(kTestUrl, UserScript::DOCUMENT_END, JsFileList());
+ ASSERT_HRESULT_SUCCEEDED(
+ manager.LoadEndScripts(kTestUrl, document_));
+
+ ASSERT_HRESULT_SUCCEEDED(manager.TearDown());
+}
+
+const wchar_t kJsFilePath1[] = L"foo.js";
+const char kJsFileContent1[] = "window.alert('XSS!!');";
+const wchar_t kJsFilePath2[] = L"bar.js";
+const char kJsFileContent2[] =
+ "window.alert = function () { console.log('gotcha'); }";
+
+// Verify that we initialize scripting and inject when there's a URL match.
+TEST_F(ContentScriptManagerTest, ScriptInitializationOnUrlMatch) {
+ TestingContentScriptManager manager;
+ ASSERT_HRESULT_SUCCEEDED(
+ manager.Initialize(frame_host_keeper_, false));
+
+ JsFileList list;
+ list.push_back(JsFile());
+ JsFile& file1 = list.back();
+ file1.file_path = kJsFilePath1;
+ file1.content = kJsFileContent1;
+ list.push_back(JsFile());
+ JsFile& file2 = list.back();
+ file2.file_path = kJsFilePath2;
+ file2.content = kJsFileContent2;
+
+ SetScriptQueryResults(kTestUrl, UserScript::DOCUMENT_START, list);
+
+ ExpectScriptInitialization();
+ ExpectCreateScriptHost(&manager);
+ const std::wstring content1(UTF8ToWide(kJsFileContent1));
+ EXPECT_CALL(*script_host_,
+ RunScript(StrEq(kJsFilePath1), StrEq(content1.c_str())))
+ .WillOnce(Return(S_OK));
+
+ const std::wstring content2(UTF8ToWide(kJsFileContent2));
+ EXPECT_CALL(*script_host_,
+ RunScript(StrEq(kJsFilePath2), StrEq(content2.c_str())))
+ .WillOnce(Return(S_OK));
+
+ // This should initialize scripting and evaluate our script.
+ ASSERT_HRESULT_SUCCEEDED(
+ manager.LoadStartScripts(kTestUrl, document_));
+
+ // Drop the second script.
+ list.pop_back();
+ SetScriptQueryResults(kTestUrl, UserScript::DOCUMENT_END, list);
+
+ EXPECT_CALL(*script_host_,
+ RunScript(StrEq(kJsFilePath1), StrEq(content1.c_str())))
+ .WillOnce(Return(S_OK));
+
+ // This should only evaluate the script.
+ ASSERT_HRESULT_SUCCEEDED(
+ manager.LoadEndScripts(kTestUrl, document_));
+
+ // The script host needs to be shut down on teardown.
+ EXPECT_CALL(*script_host_, Close()).Times(1);
+ ASSERT_HRESULT_SUCCEEDED(manager.TearDown());
+}
+
+const wchar_t kCssContent[] = L".foo {};";
+// Verify that we inject CSS into the document.
+TEST_F(ContentScriptManagerTest, CssInjectionOnUrlMatch) {
+ TestingContentScriptManager manager;
+ ASSERT_HRESULT_SUCCEEDED(
+ manager.Initialize(frame_host_keeper_, false));
+
+ EXPECT_CALL(*frame_host_,
+ GetMatchingUserScriptsCssContent(kTestUrl, false, _))
+ .WillOnce(Return(S_OK));
+
+ // This should not cause any CSS injection.
+ ASSERT_HRESULT_SUCCEEDED(manager.LoadCss(kTestUrl, document_));
+
+ EXPECT_CALL(*frame_host_,
+ GetMatchingUserScriptsCssContent(kTestUrl, false, _))
+ .WillOnce(
+ DoAll(
+ SetArgumentPointee<2>(std::string(CW2A(kCssContent))),
+ Return(S_OK)));
+
+ EXPECT_CALL(manager,
+ InsertCss(StrEq(kCssContent), document_))
+ .WillOnce(Return(S_OK));
+
+ // We now expect to see the document injected.
+ ASSERT_HRESULT_SUCCEEDED(manager.LoadCss(kTestUrl, document_));
+}
+
+const wchar_t kTestCode[] = L"function foo {};";
+const wchar_t kTestFilePath[] = L"TestFilePath";
+TEST_F(ContentScriptManagerTest, ExecuteScript) {
+ TestingContentScriptManager manager;
+ ASSERT_HRESULT_SUCCEEDED(
+ manager.Initialize(frame_host_keeper_, false));
+
+ CComPtr<IHTMLDOMNode> head_node;
+ ASSERT_HRESULT_SUCCEEDED(MockDomNode::CreateInitialized(&head_node));
+
+ ExpectCreateScriptHost(&manager);
+ ExpectScriptInitialization();
+
+ EXPECT_CALL(*script_host_,
+ RunScript(kTestFilePath, kTestCode))
+ .WillOnce(Return(S_OK));
+
+ // We now expect to see the document injected.
+ ASSERT_HRESULT_SUCCEEDED(manager.ExecuteScript(kTestCode,
+ kTestFilePath,
+ document_));
+
+ // The script host needs to be shut down on teardown.
+ EXPECT_CALL(*script_host_, Close()).Times(1);
+ ASSERT_HRESULT_SUCCEEDED(manager.TearDown());
+}
+
+TEST_F(ContentScriptManagerTest, InsertCss) {
+ TestingContentScriptManager manager;
+ ASSERT_HRESULT_SUCCEEDED(
+ manager.Initialize(frame_host_keeper_, false));
+
+ CComPtr<IHTMLDOMNode> head_node;
+ ASSERT_HRESULT_SUCCEEDED(MockDomNode::CreateInitialized(&head_node));
+
+ EXPECT_CALL(manager, GetHeadNode(document_, _))
+ .WillOnce(
+ DoAll(
+ CopyInterfaceToArgument<1>(head_node),
+ Return(S_OK)));
+
+ EXPECT_CALL(manager,
+ InjectStyleTag(document_, head_node.p, StrEq(kCssContent)))
+ .WillOnce(Return(S_OK));
+
+ ASSERT_HRESULT_SUCCEEDED(
+ manager.ContentScriptManager::InsertCss(kCssContent, document_));
+}
+
+} // namespace
diff --git a/ceee/ie/plugin/scripting/content_script_native_api.cc b/ceee/ie/plugin/scripting/content_script_native_api.cc
new file mode 100644
index 0000000..a74d077
--- /dev/null
+++ b/ceee/ie/plugin/scripting/content_script_native_api.cc
@@ -0,0 +1,276 @@
+// 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.
+//
+// @file
+// Content script native API implementation.
+#include "ceee/ie/plugin/scripting/content_script_native_api.h"
+#include "base/basictypes.h"
+#include "base/logging.h"
+#include "base/string_util.h"
+#include "base/utf_string_conversions.h"
+#include "ceee/common/com_utils.h"
+
+namespace {
+
+// DISPID_VALUE is a macro defined to 0, which confuses overloaded Invoke-en.
+DISPID kDispidValue = DISPID_VALUE;
+
+} // namespace
+
+ContentScriptNativeApi::ContentScriptNativeApi()
+ : next_local_port_id_(kFirstPortId) {
+}
+
+ContentScriptNativeApi::LocalPort::LocalPort(LocalPortId id)
+ : state(PORT_UNINITIALIZED), local_id(id), remote_id(kInvalidPortId) {
+}
+
+HRESULT ContentScriptNativeApi::Initialize(
+ IExtensionPortMessagingProvider *messaging_provider) {
+ DCHECK(messaging_provider != NULL);
+ messaging_provider_ = messaging_provider;
+ return S_OK;
+}
+
+void ContentScriptNativeApi::FinalRelease() {
+ DCHECK(on_load_ == NULL);
+ DCHECK(on_unload_ == NULL);
+ DCHECK(on_port_connect_ == NULL);
+ DCHECK(on_port_disconnect_ == NULL);
+ DCHECK(on_port_message_ == NULL);
+}
+
+HRESULT ContentScriptNativeApi::TearDown() {
+ on_load_.Release();
+ on_unload_.Release();
+ on_port_connect_.Release();
+ on_port_disconnect_.Release();
+ on_port_message_.Release();
+ messaging_provider_.Release();
+
+ return S_OK;
+}
+
+STDMETHODIMP ContentScriptNativeApi::Log(BSTR level, BSTR message) {
+ const wchar_t* str_level = com::ToString(level);
+ size_t len = ::SysStringLen(level);
+ if (LowerCaseEqualsASCII(str_level, str_level + len, "info")) {
+ LOG(INFO) << com::ToString(message);
+ } else if (LowerCaseEqualsASCII(str_level, str_level + len, "error")) {
+ LOG(ERROR) << com::ToString(message);
+ } else {
+ LOG(WARNING) << com::ToString(message);
+ }
+
+ return S_OK;
+}
+
+
+STDMETHODIMP ContentScriptNativeApi::OpenChannelToExtension(BSTR source_id,
+ BSTR target_id,
+ BSTR name,
+ long* port_id) {
+ // TODO(siggi@chromium.org): handle connecting to other extensions.
+ // TODO(siggi@chromium.org): check for the correct source_id here.
+ if (0 != wcscmp(com::ToString(source_id), com::ToString(target_id)))
+ return E_UNEXPECTED;
+
+ LocalPortId id = GetNextLocalPortId();
+ std::pair<LocalPortMap::iterator, bool> inserted =
+ local_ports_.insert(std::make_pair(id, LocalPort(id)));
+ DCHECK(inserted.second && inserted.first != local_ports_.end());
+ // Get the port we just inserted.
+ LocalPort& port = inserted.first->second;
+ DCHECK_EQ(id, port.local_id);
+
+ std::string extension_id;
+ bool converted = WideToUTF8(com::ToString(source_id),
+ ::SysStringLen(source_id),
+ &extension_id);
+ DCHECK(converted);
+ std::string channel_name;
+ converted = WideToUTF8(com::ToString(name),
+ ::SysStringLen(name),
+ &channel_name);
+ DCHECK(converted);
+
+ // Send off the connection request with our local port ID as cookie.
+ HRESULT hr = messaging_provider_->OpenChannelToExtension(this,
+ extension_id,
+ channel_name,
+ port.local_id);
+ DCHECK(SUCCEEDED(hr));
+
+ port.state = PORT_CONNECTING;
+
+ // TODO(siggi@chromium.org): Clean up on failure.
+
+ *port_id = id;
+
+ return S_OK;
+}
+
+STDMETHODIMP ContentScriptNativeApi::CloseChannel(long port_id) {
+ // TODO(siggi@chromium.org): Writeme.
+ LOG(INFO) << "CloseChannel(" << port_id << ")";
+ return S_OK;
+}
+
+STDMETHODIMP ContentScriptNativeApi::PortAddRef(long port_id) {
+ // TODO(siggi@chromium.org): Writeme.
+ LOG(INFO) << "PortAddRef(" << port_id << ")";
+ return S_OK;
+}
+
+STDMETHODIMP ContentScriptNativeApi::PortRelease(long port_id) {
+ // TODO(siggi@chromium.org): Writeme.
+ LOG(INFO) << "PortRelease(" << port_id << ")";
+ return S_OK;
+}
+
+STDMETHODIMP ContentScriptNativeApi::PostMessage(long port_id, BSTR msg) {
+ LocalPortMap::iterator it(local_ports_.find(port_id));
+ // TODO(siggi@chromium.org): should I expect to get messages to
+ // defunct port ids?
+ DCHECK(it != local_ports_.end());
+ if (it == local_ports_.end())
+ return E_UNEXPECTED;
+ LocalPort& port = it->second;
+
+ std::string msg_str(WideToUTF8(com::ToString(msg)));
+ if (port.state == PORT_CONNECTED) {
+ messaging_provider_->PostMessage(port.remote_id, msg_str);
+ } else if (port.state == PORT_CONNECTING) {
+ port.pending_messages.push_back(msg_str);
+ } else {
+ LOG(ERROR) << "Unexpected PostMessage for port in state " << port.state;
+ }
+
+ return S_OK;
+}
+
+STDMETHODIMP ContentScriptNativeApi::AttachEvent(BSTR event_name) {
+ // TODO(siggi@chromium.org): Writeme.
+ LOG(INFO) << "AttachEvent(" << com::ToString(event_name) << ")";
+ return S_OK;
+}
+
+STDMETHODIMP ContentScriptNativeApi::DetachEvent(BSTR event_name) {
+ // TODO(siggi@chromium.org): Writeme.
+ LOG(INFO) << "DetachEvent(" << com::ToString(event_name) << ")";
+ return S_OK;
+}
+
+STDMETHODIMP ContentScriptNativeApi::put_onLoad(IDispatch* callback) {
+ on_load_ = callback;
+ return S_OK;
+}
+
+STDMETHODIMP ContentScriptNativeApi::put_onUnload(IDispatch* callback) {
+ on_unload_ = callback;
+ return S_OK;
+}
+
+STDMETHODIMP ContentScriptNativeApi::put_onPortConnect(IDispatch* callback) {
+ on_port_connect_ = callback;
+ return S_OK;
+}
+
+STDMETHODIMP ContentScriptNativeApi::put_onPortDisconnect(IDispatch* callback) {
+ on_port_disconnect_ = callback;
+ return S_OK;
+}
+
+STDMETHODIMP ContentScriptNativeApi::put_onPortMessage(IDispatch* callback) {
+ on_port_message_ = callback;
+ return S_OK;
+}
+
+void ContentScriptNativeApi::OnChannelOpened(int cookie,
+ int port_id) {
+ // The cookie is our local port ID.
+ LocalPortId local_id = cookie;
+ LocalPortMap::iterator it(local_ports_.find(local_id));
+ DCHECK(it != local_ports_.end());
+
+ LocalPort& port = it->second;
+ DCHECK_EQ(local_id, port.local_id);
+ port.remote_id = port_id;
+ port.state = PORT_CONNECTED;
+
+ // Remember the mapping so that we can find this port by remote_id.
+ remote_to_local_port_id_[port.remote_id] = port.local_id;
+
+ // Flush pending messages on this port.
+ if (port.pending_messages.size() > 0) {
+ MessageList::iterator it(port.pending_messages.begin());
+ MessageList::iterator end(port.pending_messages.end());
+
+ for (; it != end; ++it) {
+ messaging_provider_->PostMessage(port.remote_id, *it);
+ }
+ }
+}
+
+void ContentScriptNativeApi::OnPostMessage(int port_id,
+ const std::string& message) {
+ // Translate the remote port id to a local port id.
+ RemoteToLocalPortIdMap::iterator it(remote_to_local_port_id_.find(port_id));
+ DCHECK(it != remote_to_local_port_id_.end());
+
+ LocalPortId local_id = it->second;
+
+ // And push the message to the script.
+ std::wstring message_wide(UTF8ToWide(message));
+ CallOnPortMessage(message_wide.c_str(), local_id);
+}
+
+HRESULT ContentScriptNativeApi::CallOnLoad(const wchar_t* extension_id) {
+ if (on_load_ == NULL)
+ return E_UNEXPECTED;
+
+ return on_load_.Invoke1(kDispidValue, &CComVariant(extension_id));
+}
+
+HRESULT ContentScriptNativeApi::CallOnUnload() {
+ if (on_unload_ == NULL)
+ return E_UNEXPECTED;
+
+ return on_unload_.Invoke0(kDispidValue);
+}
+
+HRESULT ContentScriptNativeApi::CallOnPortConnect(
+ long port_id, const wchar_t* channel_name, const wchar_t* tab,
+ const wchar_t* source_extension_id, const wchar_t* target_extension_id) {
+ if (on_port_connect_ == NULL)
+ return E_UNEXPECTED;
+
+ // Note args go in reverse order of declaration for Invoke.
+ CComVariant args[] = {
+ target_extension_id,
+ source_extension_id,
+ tab,
+ channel_name,
+ port_id};
+
+ return on_port_connect_.InvokeN(kDispidValue, args, arraysize(args));
+}
+
+HRESULT ContentScriptNativeApi::CallOnPortDisconnect(long port_id) {
+ if (on_port_disconnect_ == NULL)
+ return E_UNEXPECTED;
+
+ return on_port_disconnect_.Invoke1(kDispidValue, &CComVariant(port_id));
+}
+
+HRESULT ContentScriptNativeApi::CallOnPortMessage(const wchar_t* msg,
+ long port_id) {
+ if (on_port_message_ == NULL)
+ return E_UNEXPECTED;
+
+ // Note args go in reverse order of declaration for Invoke.
+ CComVariant args[] = { port_id, msg };
+
+ return on_port_message_.InvokeN(kDispidValue, args, arraysize(args));
+}
diff --git a/ceee/ie/plugin/scripting/content_script_native_api.h b/ceee/ie/plugin/scripting/content_script_native_api.h
new file mode 100644
index 0000000..5c97184
--- /dev/null
+++ b/ceee/ie/plugin/scripting/content_script_native_api.h
@@ -0,0 +1,183 @@
+// 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.
+//
+// @file
+// Content script native API class declaration.
+
+#ifndef CEEE_IE_PLUGIN_SCRIPTING_CONTENT_SCRIPT_NATIVE_API_H_
+#define CEEE_IE_PLUGIN_SCRIPTING_CONTENT_SCRIPT_NATIVE_API_H_
+
+#include <map>
+#include <string>
+#include <vector>
+
+#include "ceee/common/initializing_coclass.h"
+#include "toolband.h" // NOLINT
+
+// Fwd.
+class IContentScriptNativeApi;
+
+class IExtensionPortMessagingProvider: public IUnknown {
+ public:
+ // Close all ports opening and opened for this instance.
+ virtual void CloseAll(IContentScriptNativeApi* instance) = 0;
+
+ // Initiates opening a channel to an extension.
+ // @param instance the PageApi instance requesting the channel.
+ // @param extension the id of the extension to open the channel to.
+ // @param cookie a caller-provided cookie associated with this request.
+ // @note in the fullness of time, the manager should call
+ // ChannelOpened, supplying the callback and the assigned port_id.
+ virtual HRESULT OpenChannelToExtension(IContentScriptNativeApi* instance,
+ const std::string& extension,
+ const std::string& channel_name,
+ int cookie) = 0;
+
+ // Posts a message from the page to the previously assigned channel
+ // corresponding to port_id.
+ virtual HRESULT PostMessage(int port_id, const std::string& message) = 0;
+};
+
+class IContentScriptNativeApi: public IUnknown {
+ public:
+ // Called by host to complete a channel open request.
+ // @param cookie the cookie previously passed to the host on an
+ // OpenChannelToExtension invocation.
+ // @param port_id the port ID assigned to the port by the host.
+ virtual void OnChannelOpened(int cookie, int port_id) = 0;
+
+ // Called by host on an incoming postMessage.
+ // @param port_id the host port ID of the destination port.
+ // @param message the message.
+ virtual void OnPostMessage(int port_id, const std::string& message) = 0;
+};
+
+// This class implements the native API provided to content scripts through
+// the ceee_bootstrap.js script. The functionality provided here has to be
+// safe for any page content to invoke.
+// For safety's sake, do not expose any IDispatch-derived interfaces on this
+// object other than ICeeeContentScriptNativeApi.
+class ContentScriptNativeApi
+ : public CComObjectRootEx<CComSingleThreadModel>,
+ public InitializingCoClass<ContentScriptNativeApi>,
+ public IContentScriptNativeApi,
+ public IDispatchImpl<ICeeeContentScriptNativeApi,
+ &IID_ICeeeContentScriptNativeApi,
+ &LIBID_ToolbandLib,
+ 0xFFFF, // Magic ATL incantation to load
+ 0xFFFF> { // typelib from our resource.
+ public:
+ BEGIN_COM_MAP(ContentScriptNativeApi)
+ COM_INTERFACE_ENTRY(IDispatch)
+ COM_INTERFACE_ENTRY(ICeeeContentScriptNativeApi)
+ END_COM_MAP()
+
+ ContentScriptNativeApi();
+
+ HRESULT Initialize(IExtensionPortMessagingProvider* messaging_provider);
+ void FinalRelease();
+
+ // @name ICeeeContentScriptNativeApi implementation
+ // This is the interface presented to the JavaScript
+ // code in ceee_bootstrap.js.
+ // @{
+ STDMETHOD(Log)(BSTR level, BSTR message);
+ STDMETHOD(OpenChannelToExtension)(BSTR source_id,
+ BSTR target_id,
+ BSTR name,
+ long* port_id);
+ STDMETHOD(CloseChannel)(long port_id);
+ STDMETHOD(PortAddRef)(long port_id);
+ STDMETHOD(PortRelease)(long port_id);
+ STDMETHOD(PostMessage)(long port_id, BSTR msg);
+ STDMETHOD(AttachEvent)(BSTR event_name);
+ STDMETHOD(DetachEvent)(BSTR event_name);
+ STDMETHOD(put_onLoad)(IDispatch* callback);
+ STDMETHOD(put_onUnload)(IDispatch* callback);
+ STDMETHOD(put_onPortConnect)(IDispatch* callback);
+ STDMETHOD(put_onPortDisconnect)(IDispatch* callback);
+ STDMETHOD(put_onPortMessage)(IDispatch* callback);
+ // @}
+
+
+ // @name IContentScriptNativeApi implementation
+ // @{
+ virtual void OnChannelOpened(int cookie, int port_id);
+ virtual void OnPostMessage(int port_id, const std::string& message);
+ // @}
+
+ // Typed wrapper functions to call on the respective callbacks.
+ // @{
+ HRESULT CallOnLoad(const wchar_t* extension_id);
+ HRESULT CallOnUnload();
+ HRESULT CallOnPortConnect(long port_id,
+ const wchar_t* channel_name,
+ const wchar_t* tab,
+ const wchar_t* source_extension_id,
+ const wchar_t* target_extension_id);
+ HRESULT CallOnPortDisconnect(long port_id);
+ HRESULT CallOnPortMessage(const wchar_t* msg, long port_id);
+ // @}
+
+ // Release all resources.
+ HRESULT TearDown();
+
+ private:
+ // Storage for our callback properties.
+ CComDispatchDriver on_load_;
+ CComDispatchDriver on_unload_;
+ CComDispatchDriver on_port_connect_;
+ CComDispatchDriver on_port_disconnect_;
+ CComDispatchDriver on_port_message_;
+
+ // The messaging provider takes care of communication transport for us.
+ CComPtr<IExtensionPortMessagingProvider> messaging_provider_;
+
+ typedef int PortId;
+ typedef PortId LocalPortId;
+ typedef PortId RemotePortId;
+ static const int kInvalidPortId = -1;
+ static const int kFirstPortId = 2;
+
+ enum LocalPortState {
+ PORT_UNINITIALIZED,
+ PORT_CONNECTING,
+ PORT_CONNECTED,
+ PORT_CLOSING,
+ PORT_CLOSED,
+ };
+
+ typedef std::vector<std::string> MessageList;
+
+ // State we maintain per port.
+ class LocalPort {
+ public:
+ explicit LocalPort(LocalPortId id);
+
+ LocalPortState state;
+ LocalPortId local_id;
+ RemotePortId remote_id;
+
+ // Messages waiting to be posted.
+ MessageList pending_messages;
+ };
+
+ LocalPortId GetNextLocalPortId() {
+ LocalPortId id = next_local_port_id_;
+ next_local_port_id_ += 2;
+ return id;
+ }
+
+ typedef std::map<LocalPortId, LocalPort> LocalPortMap;
+ typedef std::map<RemotePortId, LocalPortId> RemoteToLocalPortIdMap;
+
+ // Local state for the ports we handle.
+ LocalPortMap local_ports_;
+ // Maps
+ RemoteToLocalPortIdMap remote_to_local_port_id_;
+
+ LocalPortId next_local_port_id_;
+};
+
+#endif // CEEE_IE_PLUGIN_SCRIPTING_CONTENT_SCRIPT_NATIVE_API_H_
diff --git a/ceee/ie/plugin/scripting/content_script_native_api_unittest.cc b/ceee/ie/plugin/scripting/content_script_native_api_unittest.cc
new file mode 100644
index 0000000..48b6213
--- /dev/null
+++ b/ceee/ie/plugin/scripting/content_script_native_api_unittest.cc
@@ -0,0 +1,209 @@
+// 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.
+//
+// @file
+// Content script native API implementation.
+#include "ceee/ie/plugin/scripting/content_script_native_api.h"
+#include "ceee/ie/plugin/scripting/content_script_manager.h"
+#include "gtest/gtest.h"
+#include "gmock/gmock.h"
+#include "ceee/testing/utils/mock_com.h"
+#include "ceee/testing/utils/test_utils.h"
+#include "ceee/testing/utils/dispex_mocks.h"
+#include "ceee/testing/utils/instance_count_mixin.h"
+
+namespace {
+
+using testing::IDispatchExMockImpl;
+using testing::InstanceCountMixin;
+using testing::InstanceCountMixinBase;
+using testing::_;
+using testing::AllOf;
+using testing::DispParamArgEq;
+using testing::Field;
+using testing::Eq;
+using testing::Return;
+using testing::StrictMock;
+using testing::MockDispatchEx;
+
+class IExtensionPortMessagingProviderMockImpl
+ : public IExtensionPortMessagingProvider {
+ public:
+ MOCK_METHOD1(CloseAll, void(IContentScriptNativeApi* instance));
+ MOCK_METHOD4(OpenChannelToExtension, HRESULT(
+ IContentScriptNativeApi* instance,
+ const std::string& extension,
+ const std::string& channel_name,
+ int cookie));
+ MOCK_METHOD2(PostMessage, HRESULT(int port_id, const std::string& message));
+};
+
+class MockIExtensionPortMessagingProvider
+ : public CComObjectRootEx<CComSingleThreadModel>,
+ public InitializingCoClass<MockIExtensionPortMessagingProvider>,
+ public InstanceCountMixin<MockIExtensionPortMessagingProvider>,
+ public StrictMock<IExtensionPortMessagingProviderMockImpl> {
+ BEGIN_COM_MAP(MockIExtensionPortMessagingProvider)
+ END_COM_MAP()
+
+ HRESULT Initialize() { return S_OK; }
+};
+
+class TestingContentScriptNativeApi
+ : public ContentScriptNativeApi,
+ public InitializingCoClass<TestingContentScriptNativeApi> {
+ public:
+ // Disambiguate.
+ using InitializingCoClass<TestingContentScriptNativeApi>::CreateInitialized;
+ using ContentScriptNativeApi::Initialize;
+
+ HRESULT Initialize(TestingContentScriptNativeApi** self) {
+ *self = this;
+ return S_OK;
+ }
+};
+
+class ContentScriptNativeApiTest: public testing::Test {
+ public:
+ ContentScriptNativeApiTest() : api_(NULL), function_(NULL) {
+ }
+
+ void SetUp() {
+ ASSERT_HRESULT_SUCCEEDED(
+ TestingContentScriptNativeApi::CreateInitialized(&api_, &api_keeper_));
+ ASSERT_HRESULT_SUCCEEDED(
+ MockDispatchEx::CreateInitialized(&function_, &function_keeper_));
+ }
+
+ void TearDown() {
+ if (api_ != NULL)
+ api_->TearDown();
+
+ api_ = NULL;
+ api_keeper_.Release();
+
+ function_ = NULL;
+ function_keeper_.Release();
+
+ ASSERT_EQ(0, InstanceCountMixinBase::all_instance_count());
+ }
+
+ protected:
+ TestingContentScriptNativeApi* api_;
+ CComPtr<ICeeeContentScriptNativeApi> api_keeper_;
+
+ MockDispatchEx* function_;
+ CComPtr<IDispatch> function_keeper_;
+};
+
+TEST_F(ContentScriptNativeApiTest, ImplementsInterfaces) {
+ CComPtr<IDispatch> disp;
+ ASSERT_HRESULT_SUCCEEDED(
+ api_keeper_->QueryInterface(&disp));
+}
+
+int kPortId = 42;
+const wchar_t* kChannelName = L"Q92FM";
+const wchar_t* kTab = NULL;
+const wchar_t* kSourceExtensionId = L"fepbkochiplomghbdfgekenppangbiap";
+const wchar_t* kTargetExtensionId = L"kgeddobpkdopccblmihponcjlbdpmbod";
+const wchar_t* kWideMsg = L"\"JSONified string\"";
+const char* kMsg = "\"JSONified string\"";
+
+const wchar_t* kWideMsg2 = L"\"Other JSONified string\"";
+const char* kMsg2 = "\"Other JSONified string\"";
+
+TEST_F(ContentScriptNativeApiTest, CallUnsetCallbacks) {
+ ASSERT_HRESULT_FAILED(api_->CallOnLoad(kSourceExtensionId));
+ ASSERT_HRESULT_FAILED(api_->CallOnUnload());
+ ASSERT_HRESULT_FAILED(api_->CallOnPortConnect(kPortId,
+ kChannelName,
+ kTab,
+ kSourceExtensionId,
+ kTargetExtensionId));
+ ASSERT_HRESULT_FAILED(api_->CallOnPortDisconnect(kPortId));
+ ASSERT_HRESULT_FAILED(api_->CallOnPortMessage(kWideMsg, kPortId));
+}
+
+TEST_F(ContentScriptNativeApiTest, CallOnLoad) {
+ ASSERT_HRESULT_FAILED(api_->CallOnLoad(kSourceExtensionId));
+ ASSERT_HRESULT_SUCCEEDED(api_->put_onLoad(function_keeper_));
+ function_->ExpectInvoke(DISPID_VALUE, kSourceExtensionId);
+ ASSERT_HRESULT_SUCCEEDED(api_->CallOnLoad(kSourceExtensionId));
+}
+
+TEST_F(ContentScriptNativeApiTest, CallOnUnload) {
+ ASSERT_HRESULT_FAILED(api_->CallOnUnload());
+ ASSERT_HRESULT_SUCCEEDED(api_->put_onUnload(function_keeper_));
+ function_->ExpectInvoke(DISPID_VALUE);
+ ASSERT_HRESULT_SUCCEEDED(api_->CallOnUnload());
+}
+
+TEST_F(ContentScriptNativeApiTest, CallOnPortConnect) {
+ ASSERT_HRESULT_FAILED(api_->CallOnPortConnect(kPortId,
+ kChannelName,
+ kTab,
+ kSourceExtensionId,
+ kTargetExtensionId));
+ ASSERT_HRESULT_SUCCEEDED(api_->put_onPortConnect(function_keeper_));
+ function_->ExpectInvoke(DISPID_VALUE,
+ kPortId,
+ kChannelName,
+ kTab,
+ kSourceExtensionId,
+ kTargetExtensionId);
+ ASSERT_HRESULT_SUCCEEDED(api_->CallOnPortConnect(kPortId,
+ kChannelName,
+ kTab,
+ kSourceExtensionId,
+ kTargetExtensionId));
+}
+
+TEST_F(ContentScriptNativeApiTest, CallOnPortDisconnect) {
+ ASSERT_HRESULT_FAILED(api_->CallOnPortDisconnect(kPortId));
+ ASSERT_HRESULT_SUCCEEDED(api_->put_onPortDisconnect(function_keeper_));
+ function_->ExpectInvoke(DISPID_VALUE, kPortId);
+ ASSERT_HRESULT_SUCCEEDED(api_->CallOnPortDisconnect(kPortId));
+}
+
+TEST_F(ContentScriptNativeApiTest, CallOnPortMessage) {
+ ASSERT_HRESULT_FAILED(api_->CallOnPortMessage(kWideMsg, kPortId));
+ ASSERT_HRESULT_SUCCEEDED(api_->put_onPortMessage(function_keeper_));
+ function_->ExpectInvoke(DISPID_VALUE, kWideMsg, kPortId);
+ ASSERT_HRESULT_SUCCEEDED(api_->CallOnPortMessage(kWideMsg, kPortId));
+}
+
+TEST_F(ContentScriptNativeApiTest, OnPostMessage) {
+ MockIExtensionPortMessagingProvider* mock_provider;
+ ASSERT_HRESULT_SUCCEEDED(MockIExtensionPortMessagingProvider::
+ CreateInstance(&mock_provider));
+ CComPtr<IExtensionPortMessagingProvider> mock_provider_holder(mock_provider);
+ EXPECT_HRESULT_SUCCEEDED(api_->Initialize(mock_provider_holder.p));
+ // TODO(siggi@chromium.org): Expect the appropriate argument values.
+ EXPECT_CALL(*mock_provider, OpenChannelToExtension(api_, _, _, _))
+ .WillRepeatedly(Return(S_OK));
+ CComBSTR source_id(L"SourceId");
+ CComBSTR name(L"name");
+ long local_port_id1 = 0;
+ EXPECT_HRESULT_SUCCEEDED(api_->OpenChannelToExtension(
+ source_id, source_id, name, &local_port_id1));
+ long local_port_id2 = 0;
+ EXPECT_HRESULT_SUCCEEDED(api_->OpenChannelToExtension(
+ source_id, source_id, name, &local_port_id2));
+
+ // TODO(siggi@chromium.org): Test pending messages code.
+ static const int kRemotePortId1 = 42;
+ static const int kRemotePortId2 = 84;
+ api_->OnChannelOpened((int)local_port_id2, kRemotePortId2);
+ api_->OnChannelOpened((int)local_port_id1, kRemotePortId1);
+
+ EXPECT_HRESULT_SUCCEEDED(api_->put_onPortMessage(function_keeper_));
+ function_->ExpectInvoke(DISPID_VALUE, kWideMsg, local_port_id1);
+ api_->OnPostMessage(kRemotePortId1, kMsg);
+
+ function_->ExpectInvoke(DISPID_VALUE, kWideMsg2, local_port_id2);
+ api_->OnPostMessage(kRemotePortId2, kMsg2);
+}
+
+} // namespace
diff --git a/ceee/ie/plugin/scripting/json.js b/ceee/ie/plugin/scripting/json.js
new file mode 100644
index 0000000..0ef04e4
--- /dev/null
+++ b/ceee/ie/plugin/scripting/json.js
@@ -0,0 +1,318 @@
+// Copyright 2006 Google Inc.
+// All Rights Reserved.
+// Redistribution and use in source and binary forms, with or without
+// modification, are permitted provided that the following conditions
+// are met:
+//
+// * Redistributions of source code must retain the above copyright
+// notice, this list of conditions and the following disclaimer.
+// * Redistributions in binary form must reproduce the above copyright
+// notice, this list of conditions and the following disclaimer in
+// the documentation and/or other materials provided with the
+// distribution.
+//
+// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
+// FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
+// COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
+// INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
+// BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
+// LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+// CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+// LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN
+// ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+// POSSIBILITY OF SUCH DAMAGE.
+
+/**
+ * @fileoverview JSON utility functions.
+ */
+
+/*
+ * ORIGINAL VERSION: http://doctype.googlecode.com/svn/trunk/goog/json/json.js
+ *
+ * LOCAL CHANGES: None.
+ */
+
+goog.provide('goog.json');
+goog.provide('goog.json.Serializer');
+
+
+
+/**
+ * Tests if a string is an invalid JSON string. This only ensures that we are
+ * not using any invalid characters
+ * @param {string} s The string to test.
+ * @return {boolean} True if the input is a valid JSON string.
+ * @private
+ */
+goog.json.isValid_ = function(s) {
+ // All empty whitespace is not valid.
+ if (/^\s*$/.test(s)) {
+ return false;
+ }
+
+ // This is taken from http://www.json.org/json2.js which is released to the
+ // public domain.
+ // Changes: We dissallow \u2028 Line separator and \u2029 Paragraph separator
+ // inside strings. We also treat \u2028 and \u2029 as whitespace which they
+ // are in the RFC but IE and Safari does not match \s to these so we need to
+ // include them in the reg exps in all places where whitespace is allowed.
+ // We allowed \x7f inside strings because some tools don't escape it,
+ // e.g. http://www.json.org/java/org/json/JSONObject.java
+
+ // Parsing happens in three stages. In the first stage, we run the text
+ // against regular expressions that look for non-JSON patterns. We are
+ // especially concerned with '()' and 'new' because they can cause invocation,
+ // and '=' because it can cause mutation. But just to be safe, we want to
+ // reject all unexpected forms.
+
+ // We split the first stage into 4 regexp operations in order to work around
+ // crippling inefficiencies in IE's and Safari's regexp engines. First we
+ // replace all backslash pairs with '@' (a non-JSON character). Second, we
+ // replace all simple value tokens with ']' characters. Third, we delete all
+ // open brackets that follow a colon or comma or that begin the text. Finally,
+ // we look to see that the remaining characters are only whitespace or ']' or
+ // ',' or ':' or '{' or '}'. If that is so, then the text is safe for eval.
+
+ // Don't make these static since they have the global flag.
+ var backslashesRe = /\\["\\\/bfnrtu]/g;
+ var simpleValuesRe =
+ /"[^"\\\n\r\u2028\u2029\x00-\x1f\x80-\x9f]*"|true|false|null|-?\d+(?:\.\d*)?(?:[eE][+\-]?\d+)?/g;
+ var openBracketsRe = /(?:^|:|,)(?:[\s\u2028\u2029]*\[)+/g;
+ var remainderRe = /^[\],:{}\s\u2028\u2029]*$/;
+
+ return remainderRe.test(s.replace(backslashesRe, '@').
+ replace(simpleValuesRe, ']').
+ replace(openBracketsRe, ''));
+};
+
+
+/**
+ * Parses a JSON string and returns the result. This throws an exception if
+ * the string is an invalid JSON string.
+ *
+ * Note that this is very slow on large strings. If you trust the source of
+ * the string then you should use unsafeParse instead.
+ *
+ * @param {*} s The JSON string to parse.
+ * @return {Object} The object generated from the JSON string.
+ */
+goog.json.parse = function(s) {
+ var o = String(s);
+ if (goog.json.isValid_(o)) {
+ /** @preserveTry */
+ try {
+ return eval('(' + o + ')');
+ } catch (ex) {
+ }
+ }
+ throw Error('Invalid JSON string: ' + o);
+};
+
+
+/**
+ * Parses a JSON string and returns the result. This uses eval so it is open
+ * to security issues and it should only be used if you trust the source.
+ *
+ * @param {string} s The JSON string to parse.
+ * @return {Object} The object generated from the JSON string.
+ */
+goog.json.unsafeParse = function(s) {
+ return eval('(' + s + ')');
+};
+
+/**
+ * Serializes an object or a value to a JSON string.
+ *
+ * @param {Object} object The object to serialize.
+ * @throws Error if there are loops in the object graph.
+ * @return {string} A JSON string representation of the input.
+ */
+goog.json.serialize = function(object) {
+ return new goog.json.Serializer().serialize(object);
+};
+
+
+
+/**
+ * Class that is used to serialize JSON objects to a string.
+ * @constructor
+ */
+goog.json.Serializer = function() {
+};
+
+
+/**
+ * Serializes an object or a value to a JSON string.
+ *
+ * @param {Object?} object The object to serialize.
+ * @throws Error if there are loops in the object graph.
+ * @return {string} A JSON string representation of the input.
+ */
+goog.json.Serializer.prototype.serialize = function(object) {
+ var sb = [];
+ this.serialize_(object, sb);
+ return sb.join('');
+};
+
+
+/**
+ * Serializes a generic value to a JSON string
+ * @private
+ * @param {string|number|boolean|undefined|Object|Array} object The object to
+ * serialize.
+ * @param {Array} sb Array used as a string builder.
+ * @throws Error if there are loops in the object graph.
+ */
+goog.json.Serializer.prototype.serialize_ = function(object, sb) {
+ switch (typeof object) {
+ case 'string':
+ this.serializeString_((/** @type {string} */ object), sb);
+ break;
+ case 'number':
+ this.serializeNumber_((/** @type {number} */ object), sb);
+ break;
+ case 'boolean':
+ sb.push(object);
+ break;
+ case 'undefined':
+ sb.push('null');
+ break;
+ case 'object':
+ if (object == null) {
+ sb.push('null');
+ break;
+ }
+ if (goog.isArray(object)) {
+ this.serializeArray_(object, sb);
+ break;
+ }
+ // should we allow new String, new Number and new Boolean to be treated
+ // as string, number and boolean? Most implementations do not and the
+ // need is not very big
+ this.serializeObject_(object, sb);
+ break;
+ case 'function':
+ // Skip functions.
+ break;
+ default:
+ throw Error('Unknown type: ' + typeof object);
+ }
+};
+
+
+/**
+ * Character mappings used internally for goog.string.quote
+ * @private
+ * @type {Object}
+ */
+goog.json.Serializer.charToJsonCharCache_ = {
+ '\"': '\\"',
+ '\\': '\\\\',
+ '/': '\\/',
+ '\b': '\\b',
+ '\f': '\\f',
+ '\n': '\\n',
+ '\r': '\\r',
+ '\t': '\\t',
+
+ '\x0B': '\\u000b' // '\v' is not supported in JScript
+};
+
+
+/**
+ * Regular expression used to match characters that need to be replaced.
+ * The S60 browser has a bug where unicode characters are not matched by
+ * regular expressions. The condition below detects such behaviour and
+ * adjusts the regular expression accordingly.
+ * @private
+ * @type {RegExp}
+ */
+goog.json.Serializer.charsToReplace_ = /\uffff/.test('\uffff') ?
+ /[\\\"\x00-\x1f\x7f-\uffff]/g : /[\\\"\x00-\x1f\x7f-\xff]/g;
+
+
+/**
+ * Serializes a string to a JSON string
+ * @private
+ * @param {string} s The string to serialize.
+ * @param {Array} sb Array used as a string builder.
+ */
+goog.json.Serializer.prototype.serializeString_ = function(s, sb) {
+ // The official JSON implementation does not work with international
+ // characters.
+ sb.push('"', s.replace(goog.json.Serializer.charsToReplace_, function(c) {
+ // caching the result improves performance by a factor 2-3
+ if (c in goog.json.Serializer.charToJsonCharCache_) {
+ return goog.json.Serializer.charToJsonCharCache_[c];
+ }
+
+ var cc = c.charCodeAt(0);
+ var rv = '\\u';
+ if (cc < 16) {
+ rv += '000';
+ } else if (cc < 256) {
+ rv += '00';
+ } else if (cc < 4096) { // \u1000
+ rv += '0';
+ }
+ return goog.json.Serializer.charToJsonCharCache_[c] = rv + cc.toString(16);
+ }), '"');
+};
+
+
+/**
+ * Serializes a number to a JSON string
+ * @private
+ * @param {number} n The number to serialize.
+ * @param {Array} sb Array used as a string builder.
+ */
+goog.json.Serializer.prototype.serializeNumber_ = function(n, sb) {
+ sb.push(isFinite(n) && !isNaN(n) ? n : 'null');
+};
+
+
+/**
+ * Serializes an array to a JSON string
+ * @private
+ * @param {Array} arr The array to serialize.
+ * @param {Array} sb Array used as a string builder.
+ */
+goog.json.Serializer.prototype.serializeArray_ = function(arr, sb) {
+ var l = arr.length;
+ sb.push('[');
+ var sep = '';
+ for (var i = 0; i < l; i++) {
+ sb.push(sep)
+ this.serialize_(arr[i], sb);
+ sep = ',';
+ }
+ sb.push(']');
+};
+
+
+/**
+ * Serializes an object to a JSON string
+ * @private
+ * @param {Object} obj The object to serialize.
+ * @param {Array} sb Array used as a string builder.
+ */
+goog.json.Serializer.prototype.serializeObject_ = function(obj, sb) {
+ sb.push('{');
+ var sep = '';
+ for (var key in obj) {
+ if (obj.hasOwnProperty(key)) {
+ var value = obj[key];
+ // Skip functions.
+ if (typeof value != 'function') {
+ sb.push(sep);
+ this.serializeString_(key, sb);
+ sb.push(':');
+ this.serialize_(value, sb);
+ sep = ',';
+ }
+ }
+ }
+ sb.push('}');
+}; \ No newline at end of file
diff --git a/ceee/ie/plugin/scripting/renderer_extension_bindings_unittest.cc b/ceee/ie/plugin/scripting/renderer_extension_bindings_unittest.cc
new file mode 100644
index 0000000..4f441a9
--- /dev/null
+++ b/ceee/ie/plugin/scripting/renderer_extension_bindings_unittest.cc
@@ -0,0 +1,479 @@
+// 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.
+//
+// JS unittests for our extension API bindings.
+#include <iostream>
+
+#include "base/path_service.h"
+#include "base/file_path.h"
+#include "base/file_util.h"
+#include "base/string_util.h"
+
+#include "ceee/ie/plugin/scripting/content_script_manager.h"
+#include "ceee/ie/plugin/scripting/content_script_native_api.h"
+#include "ceee/ie/plugin/scripting/script_host.h"
+#include "ceee/common/com_utils.h"
+#include "ceee/common/initializing_coclass.h"
+#include "ceee/testing/utils/dispex_mocks.h"
+#include "ceee/testing/utils/instance_count_mixin.h"
+
+#include "gmock/gmock.h"
+#include "gtest/gtest.h"
+
+#include <initguid.h> // NOLINT
+
+namespace {
+
+using testing::_;
+using testing::DoAll;
+using testing::Return;
+using testing::SetArgumentPointee;
+using testing::StrEq;
+using testing::StrictMock;
+using testing::InstanceCountMixin;
+using testing::InstanceCountMixinBase;
+using testing::IDispatchExMockImpl;
+
+// This is the chromium buildbot extension id.
+const wchar_t kExtensionId[] = L"fepbkochiplomghbdfgekenppangbiap";
+// And the gmail checker.
+const wchar_t kAnotherExtensionId[] = L"kgeddobpkdopccblmihponcjlbdpmbod";
+const wchar_t kFileName[] = TEXT(__FILE__);
+
+// The class and macros belwo make it possible to execute and debug JavaScript
+// snippets interspersed with C++ code, which is a humane way to write, but
+// particularly to read and debug the unittests below.
+// To use this, declare a JavaScript block as follows:
+// <code>
+// BEGIN_SCRIPT_BLOCK(some_identifier) /*
+// window.alert('here I am!'
+// */ END_SCRIPT_BLOCK()
+//
+// HRESULT hr = some_identifier.Execute(script_host);
+// </code>
+#define BEGIN_SCRIPT_BLOCK(x) ScriptBlock x(TEXT(__FILE__), __LINE__);
+#define END_SCRIPT_BLOCK()
+class ScriptBlock {
+ public:
+ ScriptBlock(const wchar_t* file, size_t line) : file_(file), line_(line) {
+ }
+
+ HRESULT Execute(IScriptHost* script_host) {
+ FilePath self_path(file_);
+
+ if (!self_path.IsAbsolute()) {
+ // Construct the absolute path to this source file.
+ // The __FILE__ macro may expand to a solution-relative path to the file.
+ FilePath src_root;
+ EXPECT_TRUE(PathService::Get(base::DIR_SOURCE_ROOT, &src_root));
+
+ self_path = src_root.Append(L"ceee")
+ .Append(L"ie")
+ .Append(file_);
+ }
+
+ // Slurp the file.
+ std::string contents;
+ if (!file_util::ReadFileToString(self_path, &contents))
+ return E_UNEXPECTED;
+
+ // Walk the lines to ours.
+ std::string::size_type start_pos = 0;
+ for (size_t i = 0; i < line_; ++i) {
+ // Find the next newline.
+ start_pos = contents.find('\n', start_pos);
+ if (start_pos == contents.npos)
+ return E_UNEXPECTED;
+
+ // Walk past the newline char.
+ start_pos++;
+ }
+
+ // Now find the next occurrence of END_SCRIPT_BLOCK.
+ std::string::size_type end_pos = contents.find("END_SCRIPT_BLOCK",
+ start_pos);
+ if (end_pos == contents.npos)
+ return E_UNEXPECTED;
+
+ // And walk back to the start of that line.
+ end_pos = contents.rfind('\n', end_pos);
+ if (end_pos == contents.npos)
+ return E_UNEXPECTED;
+
+ CComPtr<IDebugDocumentHelper> doc;
+ ScriptHost* host = static_cast<ScriptHost*>(script_host);
+ host->AddDebugDocument(file_, CA2W(contents.c_str()), &doc);
+
+ std::string script = contents.substr(start_pos, end_pos - start_pos);
+ return host->RunScriptSnippet(start_pos, CA2W(script.c_str()), doc);
+ }
+
+ private:
+ const wchar_t* file_;
+ const size_t line_;
+};
+
+
+class TestingContentScriptNativeApi
+ : public ContentScriptNativeApi,
+ public InstanceCountMixin<TestingContentScriptNativeApi>,
+ public InitializingCoClass<TestingContentScriptNativeApi> {
+ public:
+ // Disambiguate.
+ using InitializingCoClass<TestingContentScriptNativeApi>::CreateInitialized;
+
+ HRESULT Initialize(TestingContentScriptNativeApi** self) {
+ *self = this;
+ return S_OK;
+ }
+
+ MOCK_METHOD2_WITH_CALLTYPE(__stdcall, Log,
+ HRESULT(BSTR level, BSTR message));
+ MOCK_METHOD4_WITH_CALLTYPE(__stdcall, OpenChannelToExtension,
+ HRESULT(BSTR source_id, BSTR target_id, BSTR name, LONG* port_id));
+ MOCK_METHOD1_WITH_CALLTYPE(__stdcall, CloseChannel,
+ HRESULT(LONG port_id));
+ MOCK_METHOD1_WITH_CALLTYPE(__stdcall, PortAddRef,
+ HRESULT(LONG port_id));
+ MOCK_METHOD1_WITH_CALLTYPE(__stdcall, PortRelease,
+ HRESULT(LONG port_id));
+ MOCK_METHOD2_WITH_CALLTYPE(__stdcall, PostMessage,
+ HRESULT(LONG port_id, BSTR msg));
+ MOCK_METHOD1_WITH_CALLTYPE(__stdcall, AttachEvent,
+ HRESULT(BSTR event_name));
+ MOCK_METHOD1_WITH_CALLTYPE(__stdcall, DetachEvent,
+ HRESULT(BSTR event_name));
+};
+
+class TestingContentScriptManager
+ : public ContentScriptManager {
+ public:
+ // Make accessible for testing.
+ using ContentScriptManager::BootstrapScriptHost;
+};
+
+class TestingScriptHost
+ : public ScriptHost,
+ public InitializingCoClass<TestingScriptHost>,
+ public InstanceCountMixin<TestingScriptHost> {
+ public:
+ using InitializingCoClass<TestingScriptHost>::CreateInitializedIID;
+
+ HRESULT Initialize(ScriptHost::DebugApplication* debug,
+ TestingScriptHost** self) {
+ *self = this;
+ return ScriptHost::Initialize(debug);
+ }
+};
+
+class RendererExtensionBindingsTest: public testing::Test {
+ public:
+ RendererExtensionBindingsTest() : api_(NULL), script_host_(NULL) {
+ }
+
+ static void SetUpTestCase() {
+ EXPECT_HRESULT_SUCCEEDED(::CoInitialize(NULL));
+ debug_.Initialize();
+ }
+
+ static void TearDownTestCase() {
+ debug_.Terminate();
+
+ ::CoUninitialize();
+ }
+
+ void SetUp() {
+ ASSERT_HRESULT_SUCCEEDED(::CoInitialize(NULL));
+
+ ASSERT_HRESULT_SUCCEEDED(
+ TestingScriptHost::CreateInitializedIID(&debug_,
+ &script_host_,
+ IID_IScriptHost,
+ &script_host_keeper_));
+ ASSERT_HRESULT_SUCCEEDED(
+ TestingContentScriptNativeApi::CreateInitialized(&api_, &api_keeper_));
+ }
+
+ void TearDown() {
+ if (api_) {
+ EXPECT_HRESULT_SUCCEEDED(api_->TearDown());
+ api_ = NULL;
+ api_keeper_.Release();
+ }
+
+ script_host_ = NULL;
+ if (script_host_keeper_ != NULL)
+ script_host_keeper_->Close();
+ script_host_keeper_.Release();
+
+ EXPECT_EQ(0, InstanceCountMixinBase::all_instance_count());
+ }
+
+ void Initialize() {
+ ASSERT_HRESULT_SUCCEEDED(
+ manager_.BootstrapScriptHost(script_host_keeper_,
+ api_keeper_,
+ kExtensionId));
+ }
+
+ void AssertNameExists(const wchar_t* name) {
+ CComVariant result;
+ ASSERT_HRESULT_SUCCEEDED(script_host_->RunExpression(name, &result));
+ ASSERT_NE(VT_EMPTY, V_VT(&result));
+ }
+
+ void ExpectFirstConnection() {
+ EXPECT_CALL(*api_, AttachEvent(StrEq(L"")))
+ .WillOnce(Return(S_OK));
+ }
+ void ExpectConnection(const wchar_t* src_extension_id,
+ const wchar_t* dst_extension_id,
+ const wchar_t* port_name,
+ LONG port_id) {
+ EXPECT_CALL(*api_,
+ OpenChannelToExtension(StrEq(src_extension_id),
+ StrEq(dst_extension_id),
+ StrEq(port_name),
+ _))
+ .WillOnce(
+ DoAll(
+ SetArgumentPointee<3>(port_id),
+ Return(S_OK)));
+
+ EXPECT_CALL(*api_, PortAddRef(port_id))
+ .WillOnce(Return(S_OK));
+ }
+
+ protected:
+ TestingContentScriptNativeApi* api_;
+ CComPtr<ICeeeContentScriptNativeApi> api_keeper_;
+ TestingContentScriptManager manager_;
+
+ TestingScriptHost* script_host_;
+ CComPtr<IScriptHost> script_host_keeper_;
+
+ static ScriptHost::DebugApplication debug_;
+};
+
+ScriptHost::DebugApplication
+ RendererExtensionBindingsTest::debug_(L"RendererExtensionBindingsTest");
+
+TEST_F(RendererExtensionBindingsTest, TestNamespace) {
+ Initialize();
+
+ AssertNameExists(L"chrome");
+ AssertNameExists(L"chrome.extension");
+ AssertNameExists(L"chrome.extension.connect");
+
+ AssertNameExists(L"JSON");
+ AssertNameExists(L"JSON.parse");
+}
+
+TEST_F(RendererExtensionBindingsTest, GetUrl) {
+ Initialize();
+
+ CComVariant result;
+
+ ASSERT_HRESULT_SUCCEEDED(
+ script_host_->RunExpression(
+ L"chrome.extension.getURL('foo')", &result));
+
+ ASSERT_EQ(VT_BSTR, V_VT(&result));
+ ASSERT_STREQ(StringPrintf(L"chrome-extension://%ls/foo",
+ kExtensionId).c_str(),
+ V_BSTR(&result));
+}
+
+TEST_F(RendererExtensionBindingsTest, PortConnectDisconnect) {
+ Initialize();
+ const LONG kPortId = 42;
+ ExpectConnection(kExtensionId, kExtensionId, L"", kPortId);
+ ExpectFirstConnection();
+
+ EXPECT_HRESULT_SUCCEEDED(script_host_->RunScript(
+ kFileName, L"port = chrome.extension.connect()"));
+}
+
+TEST_F(RendererExtensionBindingsTest, PortConnectWithName) {
+ Initialize();
+ const LONG kPortId = 42;
+ const wchar_t* kPortName = L"A Port Name";
+ ExpectConnection(kExtensionId, kExtensionId, kPortName, kPortId);
+ ExpectFirstConnection();
+
+ EXPECT_HRESULT_SUCCEEDED(script_host_->RunScript(
+ kFileName, StringPrintf(
+ L"port = chrome.extension.connect({name: \"%ls\"});",
+ kPortName).c_str()));
+}
+
+TEST_F(RendererExtensionBindingsTest, PortConnectToExtension) {
+ Initialize();
+ const LONG kPortId = 42;
+ ExpectConnection(kExtensionId, kAnotherExtensionId, L"", kPortId);
+ ExpectFirstConnection();
+
+ EXPECT_HRESULT_SUCCEEDED(script_host_->RunScript(
+ kFileName, StringPrintf(L"port = chrome.extension.connect(\"%ls\");",
+ kAnotherExtensionId).c_str()));
+}
+
+TEST_F(RendererExtensionBindingsTest, PostMessage) {
+ Initialize();
+ const LONG kPortId = 42;
+ ExpectConnection(kExtensionId, kAnotherExtensionId, L"", kPortId);
+ ExpectFirstConnection();
+
+ EXPECT_HRESULT_SUCCEEDED(script_host_->RunScript(
+ kFileName, StringPrintf(L"port = chrome.extension.connect(\"%ls\");",
+ kAnotherExtensionId).c_str()));
+
+ const wchar_t* kMsg = L"Message in a bottle, yeah!";
+ // Note the extra on-the-wire quotes, due to JSON encoding the input.
+ EXPECT_CALL(*api_,
+ PostMessage(kPortId,
+ StrEq(StringPrintf(L"\"%ls\"", kMsg).c_str())));
+
+ EXPECT_HRESULT_SUCCEEDED(script_host_->RunScript(
+ kFileName, StringPrintf(L"port.postMessage(\"%ls\");", kMsg).c_str()));
+}
+
+TEST_F(RendererExtensionBindingsTest, OnConnect) {
+ Initialize();
+ const LONG kPortId = 42;
+ const wchar_t kPortName[] = L"A port of call";
+ BEGIN_SCRIPT_BLOCK(script) /*
+ function onConnect(port) {
+ if (port.name == 'A port of call')
+ console.log('SUCCESS');
+ else
+ console.log(port.name);
+ };
+ chrome.extension.onConnect.addListener(onConnect);
+ */ END_SCRIPT_BLOCK()
+
+ EXPECT_CALL(*api_, AttachEvent(StrEq(L""))).
+ WillOnce(Return(S_OK));
+
+ EXPECT_HRESULT_SUCCEEDED(script.Execute(script_host_));
+
+ // A 'SUCCESS' log signals success.
+ EXPECT_CALL(*api_, Log(StrEq(L"info"), StrEq(L"SUCCESS"))).Times(1);
+
+ EXPECT_CALL(*api_, AttachEvent(StrEq(L""))).
+ WillOnce(Return(S_OK));
+ EXPECT_CALL(*api_, PortAddRef(kPortId)).
+ WillOnce(Return(S_OK));
+
+ EXPECT_HRESULT_SUCCEEDED(
+ api_->CallOnPortConnect(kPortId,
+ kPortName,
+ L"",
+ kExtensionId,
+ kExtensionId));
+}
+
+TEST_F(RendererExtensionBindingsTest, OnDisconnect) {
+ Initialize();
+ const LONG kPortId = 42;
+ ExpectConnection(kExtensionId, kExtensionId, L"", kPortId);
+ ExpectFirstConnection();
+
+ BEGIN_SCRIPT_BLOCK(script1) /*
+ var port = chrome.extension.connect()
+ */ END_SCRIPT_BLOCK()
+ EXPECT_HRESULT_SUCCEEDED(script1.Execute(script_host_));
+
+ BEGIN_SCRIPT_BLOCK(script2) /*
+ port.onDisconnect.addListener(function (port) {
+ console.log('SUCCESS');
+ });
+ */ END_SCRIPT_BLOCK()
+
+ EXPECT_CALL(*api_, AttachEvent(StrEq(L"")))
+ .WillOnce(Return(S_OK));
+ EXPECT_HRESULT_SUCCEEDED(script2.Execute(script_host_));
+
+ // A 'SUCCESS' log signals success.
+ EXPECT_CALL(*api_, Log(StrEq(L"info"), StrEq(L"SUCCESS"))).Times(1);
+
+ EXPECT_HRESULT_SUCCEEDED(api_->CallOnPortDisconnect(kPortId));
+}
+
+TEST_F(RendererExtensionBindingsTest, OnMessage) {
+ Initialize();
+ const LONG kPortId = 42;
+ ExpectConnection(kExtensionId, kExtensionId, L"", kPortId);
+ ExpectFirstConnection();
+
+ BEGIN_SCRIPT_BLOCK(connect_script) /*
+ // Connect to our extension.
+ var port = chrome.extension.connect();
+ */ END_SCRIPT_BLOCK()
+ EXPECT_HRESULT_SUCCEEDED(connect_script.Execute(script_host_));
+
+ BEGIN_SCRIPT_BLOCK(add_listener_script) /*
+ // Log the received message to console.
+ function onMessage(msg, port) {
+ console.log(msg);
+ };
+ port.onMessage.addListener(onMessage);
+ */ END_SCRIPT_BLOCK()
+
+ EXPECT_CALL(*api_, AttachEvent(StrEq(L"")))
+ .WillOnce(Return(S_OK));
+
+ EXPECT_HRESULT_SUCCEEDED(add_listener_script.Execute(script_host_));
+
+ const wchar_t kMessage[] = L"A message in a bottle, yeah!";
+ // The message logged signals success.
+ EXPECT_CALL(*api_, Log(StrEq(L"info"), StrEq(kMessage))).Times(1);
+
+ EXPECT_HRESULT_SUCCEEDED(
+ api_->CallOnPortMessage(StringPrintf(L"\"%ls\"", kMessage).c_str(),
+ kPortId));
+}
+
+TEST_F(RendererExtensionBindingsTest, OnLoad) {
+ Initialize();
+
+ BEGIN_SCRIPT_BLOCK(script) /*
+ var chromeHidden = ceee.GetChromeHidden();
+ function onLoad(extension_id) {
+ console.log(extension_id);
+ }
+ chromeHidden.onLoad.addListener(onLoad);
+ */ END_SCRIPT_BLOCK()
+
+ EXPECT_CALL(*api_, AttachEvent(StrEq(L"")));
+ EXPECT_HRESULT_SUCCEEDED(script.Execute(script_host_));
+
+ EXPECT_CALL(*api_, Log(StrEq(L"info"), StrEq(kExtensionId)))
+ .WillOnce(Return(S_OK));
+ EXPECT_HRESULT_SUCCEEDED(api_->CallOnLoad(kExtensionId));
+}
+
+TEST_F(RendererExtensionBindingsTest, OnUnload) {
+ Initialize();
+ const LONG kPort1Id = 42;
+ const LONG kPort2Id = 57;
+ ExpectConnection(kExtensionId, kExtensionId, L"port1", kPort1Id);
+ ExpectConnection(kExtensionId, kExtensionId, L"port2", kPort2Id);
+ ExpectFirstConnection();
+
+ BEGIN_SCRIPT_BLOCK(script) /*
+ var port1 = chrome.extension.connect({name: 'port1'});
+ var port2 = chrome.extension.connect({name: 'port2'});
+ */ END_SCRIPT_BLOCK()
+
+ EXPECT_HRESULT_SUCCEEDED(script.Execute(script_host_));
+
+ EXPECT_CALL(*api_, PortRelease(kPort1Id)).WillOnce(Return(S_OK));
+ EXPECT_CALL(*api_, PortRelease(kPort2Id)).WillOnce(Return(S_OK));
+ EXPECT_CALL(*api_, DetachEvent(StrEq(L""))).WillOnce(Return(S_OK));
+
+ EXPECT_HRESULT_SUCCEEDED(api_->CallOnUnload());
+}
+
+} // namespace
diff --git a/ceee/ie/plugin/scripting/renderer_extension_bindings_unittest.rc b/ceee/ie/plugin/scripting/renderer_extension_bindings_unittest.rc
new file mode 100644
index 0000000..1be0ca5
--- /dev/null
+++ b/ceee/ie/plugin/scripting/renderer_extension_bindings_unittest.rc
@@ -0,0 +1,12 @@
+// 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 <winres.h>
+
+#ifdef APSTUDIO_INVOKED
+#error Please edit as text.
+#endif
+
+// The test needs the CEEE TLB.
+1 TYPELIB "toolband.tlb"
diff --git a/ceee/ie/plugin/scripting/script_host.cc b/ceee/ie/plugin/scripting/script_host.cc
new file mode 100644
index 0000000..e2faf42
--- /dev/null
+++ b/ceee/ie/plugin/scripting/script_host.cc
@@ -0,0 +1,886 @@
+// 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 our scripting host.
+
+#include "ceee/ie/plugin/scripting/script_host.h"
+
+#include <dispex.h>
+#include <mshtml.h>
+#include <mshtmhst.h>
+#include <objsafe.h>
+
+#include "base/logging.h"
+#include "base/string_util.h"
+#include "ceee/common/com_utils.h"
+
+#ifndef CMDID_SCRIPTSITE_URL
+
+// These are documented in MSDN, but not declared in the platform SDK.
+// See [http://msdn.microsoft.com/en-us/library/aa769871(VS.85).aspx].
+#define CMDID_SCRIPTSITE_URL 0
+#define CMDID_SCRIPTSITE_HTMLDLGTRUST 1
+#define CMDID_SCRIPTSITE_SECSTATE 2
+#define CMDID_SCRIPTSITE_SID 3
+#define CMDID_SCRIPTSITE_TRUSTEDDOC 4
+#define CMDID_SCRIPTSITE_SECURITY_WINDOW 5
+#define CMDID_SCRIPTSITE_NAMESPACE 6
+#define CMDID_SCRIPTSITE_IURI 7
+
+const GUID CGID_ScriptSite = {
+ 0x3050F3F1, 0x98B5, 0x11CF, 0xBB, 0x82, 0x00, 0xAA, 0x00, 0xBD, 0xCE, 0x0B };
+#endif // CMDID_SCRIPTSITE_URL
+
+namespace {
+// This class is a necessary wrapper around a text string in
+// IDebugDocumentHelper, as one can really only use the deferred
+// text mode of the helper if one wants to satisfy the timing
+// requirements on notifications imposed by script debuggers.
+//
+// The timing requirement is this:
+//
+// It appears that for a debugger to successfully set a breakpoint in
+// an ActiveScript engine, the code in question has to have been parsed.
+// The VisualStudio and the IE8 debugger both appear to use the events
+// IDebugDocumentTextEvents as a trigger point to apply any pending
+// breakpoints to the engine. The problem here is that with naive usage of
+// the debug document helper, one would simply provide it with the
+// text contents of the document at creation, before ParseScriptText
+// is performed. This fires the text events too early, which means
+// the debugger will not find any code to set breakpoints on.
+// To ensure that the debugger finds something to grab on,
+// one needs to set or modify the debug document text after
+// ParseScriptText, but before execution of the text, such as e.g.
+// on the OnEnterScript event to the ActiveScript site.
+class DocHost
+ : public CComObjectRootEx<CComSingleThreadModel>,
+ public InitializingCoClass<DocHost>,
+ public IDebugDocumentHost {
+ public:
+ BEGIN_COM_MAP(DocHost)
+ COM_INTERFACE_ENTRY(IDebugDocumentHost)
+ END_COM_MAP()
+
+ HRESULT Initialize(const wchar_t* code) {
+ code_ = code;
+ return S_OK;
+ }
+
+ STDMETHOD(GetDeferredText)(DWORD cookie,
+ WCHAR* text,
+ SOURCE_TEXT_ATTR* text_attr,
+ ULONG* num_chars_returned,
+ ULONG max_chars) {
+ size_t num_chars = std::min(static_cast<size_t>(max_chars), code_.length());
+ *num_chars_returned = num_chars;
+ memcpy(text, code_.c_str(), num_chars * sizeof(wchar_t));
+
+ return S_OK;
+ }
+
+ STDMETHOD(GetScriptTextAttributes)(LPCOLESTR code,
+ ULONG num_code_chars,
+ LPCOLESTR delimiter,
+ DWORD flags,
+ SOURCE_TEXT_ATTR* attr) {
+ return E_NOTIMPL;
+ }
+
+ STDMETHOD(OnCreateDocumentContext)(IUnknown** outer) {
+ return E_NOTIMPL;
+ }
+
+ STDMETHOD(GetPathName)(BSTR *long_name, BOOL *is_original_file) {
+ return E_NOTIMPL;
+ }
+
+ STDMETHOD(GetFileName)(BSTR *short_name) {
+ return E_NOTIMPL;
+ }
+ STDMETHOD(NotifyChanged)(void) {
+ return E_NOTIMPL;
+ }
+ private:
+ std::wstring code_;
+};
+
+HRESULT GetUrlForDocument(IUnknown* unknown, VARIANT* url_out) {
+ CComQIPtr<IHTMLDocument2> document(unknown);
+ if (document == NULL)
+ return E_NOINTERFACE;
+
+ CComBSTR url;
+ HRESULT hr = document->get_URL(&url);
+ if (SUCCEEDED(hr)) {
+ url_out->vt = VT_BSTR;
+ url_out->bstrVal = url.Detach();
+ } else {
+ DLOG(ERROR) << "Failed to get security url " << com::LogHr(hr);
+ }
+
+ return hr;
+}
+
+HRESULT GetSIDForDocument(IUnknown* unknown, VARIANT* sid_out) {
+ CComQIPtr<IServiceProvider> sp(unknown);
+ if (sp == NULL)
+ return E_NOINTERFACE;
+
+ CComPtr<IInternetHostSecurityManager> security_manager;
+ HRESULT hr = sp->QueryService(SID_SInternetHostSecurityManager,
+ &security_manager);
+ if (FAILED(hr))
+ return hr;
+
+ // This is exactly mimicking observed behavior in IE.
+ CComBSTR security_id(MAX_SIZE_SECURITY_ID);
+ DWORD size = MAX_SIZE_SECURITY_ID;
+ hr = security_manager->GetSecurityId(
+ reinterpret_cast<BYTE*>(security_id.m_str), &size, 0);
+ if (SUCCEEDED(hr)) {
+ sid_out->vt = VT_BSTR;
+ sid_out->bstrVal = security_id.Detach();
+ } else {
+ DLOG(ERROR) << "Failed to get security manager " << com::LogHr(hr);
+ }
+
+ return hr;
+}
+
+HRESULT GetWindowForDocument(IUnknown* unknown, VARIANT* window_out) {
+ CComQIPtr<IServiceProvider> sp(unknown);
+ if (sp == NULL)
+ return E_NOINTERFACE;
+
+ CComPtr<IDispatch> window;
+ HRESULT hr = sp->QueryService(SID_SHTMLWindow, &window);
+ if (SUCCEEDED(hr)) {
+ window_out->vt = VT_DISPATCH;
+ window_out->pdispVal = window.Detach();
+ } else {
+ DLOG(ERROR) << "Failed to get window " << com::LogHr(hr);
+ }
+
+ return hr;
+}
+
+} // namespace
+
+// {58E6D2A5-4868-4E49-B3E9-072C845A014A}
+const GUID IID_IScriptHost =
+ { 0x58ECD2A5, 0x4868, 0x4E49,
+ { 0xB3, 0xE9, 0x07, 0x2C, 0x84, 0x5A, 0x01, 0x4A } };
+
+// {f414c260-6ac0-11cf-b6d1-00aa00bbbb58}
+const GUID CLSID_JS =
+ { 0xF414C260, 0x6AC0, 0x11CF,
+ { 0xB6, 0xD1, 0x00, 0xAA, 0x00, 0xBB, 0xBB, 0x58 } };
+
+
+ScriptHost::DebugApplication* ScriptHost::default_debug_application_;
+
+
+ScriptHost::ScriptHost() : debug_application_(NULL) {
+}
+
+HRESULT ScriptHost::Initialize(DebugApplication* debug_application) {
+ debug_application_ = debug_application;
+
+ HRESULT hr = CreateScriptEngine(&script_);
+ if (FAILED(hr)) {
+ NOTREACHED();
+ return hr;
+ }
+
+ if (FAILED(hr = script_->SetScriptSite(this))) {
+ NOTREACHED();
+ return hr;
+ }
+
+ // Get engine's IActiveScriptParse interface, initialize it
+ script_parse_ = script_;
+ if (!script_parse_) {
+ NOTREACHED();
+ return E_NOINTERFACE;
+ }
+
+ if (FAILED(hr = script_parse_->InitNew())) {
+ NOTREACHED();
+ return hr;
+ }
+
+ // Set the security options of the script engine so it
+ // queries us for IInternetHostSecurityManager, which
+ // we delegate to our site.
+ CComQIPtr<IObjectSafety> script_safety(script_);
+ if (script_safety == NULL) {
+ NOTREACHED() << "Script engine does not implement IObjectSafety";
+ return E_NOINTERFACE;
+ }
+
+ hr = script_safety->SetInterfaceSafetyOptions(
+ IID_IDispatch, INTERFACE_USES_SECURITY_MANAGER,
+ INTERFACE_USES_SECURITY_MANAGER);
+
+ // Set the script engine into a running state.
+ hr = script_->SetScriptState(SCRIPTSTATE_CONNECTED);
+ DCHECK(SUCCEEDED(hr));
+
+ return hr;
+}
+
+HRESULT ScriptHost::Initialize(DebugApplication* debug_application,
+ ScriptHost** self) {
+ *self = this;
+ return Initialize(debug_application);
+}
+
+HRESULT ScriptHost::Initialize() {
+ return Initialize(default_debug_application_);
+}
+
+void ScriptHost::FinalRelease() {
+ DCHECK(script_ == NULL);
+ debug_application_ = NULL;
+}
+
+HRESULT ScriptHost::RegisterScriptObject(const wchar_t* name,
+ IDispatch* disp_obj,
+ bool make_members_global) {
+ DCHECK(name);
+ DCHECK(disp_obj);
+ std::wstring wname = name;
+
+ // Check if the name already exists.
+ ScriptObjectMap::iterator iter = script_objects_.find(wname);
+ if (iter != script_objects_.end()) {
+ return E_ACCESSDENIED;
+ }
+
+ // Add to the script object map.
+ CComPtr<IDispatch> disp_obj_ptr(disp_obj);
+ CAdapt<CComPtr<IDispatch>> disp_obj_adapt(disp_obj_ptr);
+ script_objects_.insert(std::make_pair(wname, disp_obj_adapt));
+
+ // Add to the script engine.
+ DWORD flags = SCRIPTITEM_ISSOURCE | SCRIPTITEM_ISVISIBLE;
+ if (make_members_global) {
+ flags |= SCRIPTITEM_GLOBALMEMBERS;
+ }
+ script_->AddNamedItem(name, flags);
+
+ return S_OK;
+}
+
+HRESULT ScriptHost::RunScript(const wchar_t* file_path,
+ const wchar_t* code) {
+ DCHECK(file_path);
+ DCHECK(code);
+ if (!file_path || !code)
+ return E_POINTER;
+
+ DWORD source_context = 0;
+ HRESULT hr = GetSourceContext(file_path, code, &source_context);
+ if (FAILED(hr))
+ return hr;
+
+ ScopedExcepInfo ei;
+ hr = script_parse_->ParseScriptText(
+ code, NULL, NULL, NULL, source_context, 0,
+ SCRIPTTEXT_HOSTMANAGESSOURCE | SCRIPTTEXT_ISVISIBLE, NULL, &ei);
+ // A syntax error is not a CEEE error, so we don't log an error and we return
+ // it normally so that the caller knows what happened.
+ if (FAILED(hr) && hr != OLESCRIPT_E_SYNTAX) {
+ LOG(ERROR) << "Non-script error occurred while parsing script. "
+ << com::LogHr(hr);
+ NOTREACHED();
+ }
+
+ return hr;
+}
+
+HRESULT ScriptHost::RunExpression(const wchar_t* code, VARIANT* result) {
+ DCHECK(code);
+ if (!code)
+ return E_POINTER;
+
+ ScopedExcepInfo ei;
+ HRESULT hr = script_parse_->ParseScriptText(
+ code, NULL, NULL, NULL, 0, 0, SCRIPTTEXT_ISEXPRESSION, result, &ei);
+ // Ignore compilation and runtime errors in the script
+ if (FAILED(hr) && hr != OLESCRIPT_E_SYNTAX) {
+ LOG(ERROR) << "Non-script error occurred while parsing script. "
+ << com::LogHr(hr);
+ NOTREACHED();
+ }
+
+ return hr;
+}
+
+HRESULT ScriptHost::Close() {
+ // Close our script host.
+ HRESULT hr = S_OK;
+ if (script_) {
+ // Try to force garbage collection at this time so any objects holding
+ // reference to native components will be released immediately and dlls
+ // loaded can be unloaded quickly.
+ CComQIPtr<IActiveScriptGarbageCollector> script_gc(script_);
+ if (script_gc != NULL)
+ script_gc->CollectGarbage(SCRIPTGCTYPE_EXHAUSTIVE);
+
+ hr = script_->Close();
+ }
+
+ // Detach all debug documents.
+ DebugDocMap::iterator iter;
+ for (iter = debug_docs_.begin(); iter != debug_docs_.end(); iter++) {
+ // Note that this is IDebugDocumentHelper::Detach and not
+ // CComPtr::Detach
+ iter->second.document->Detach();
+ }
+ debug_docs_.clear();
+
+ script_.Release();
+
+ return hr;
+}
+
+STDMETHODIMP ScriptHost::GetItemInfo(LPCOLESTR item_name, DWORD return_mask,
+ IUnknown** item_unknown,
+ ITypeInfo** item_itypeinfo) {
+ DCHECK(!(return_mask & SCRIPTINFO_IUNKNOWN) || item_unknown);
+ DCHECK(!(return_mask & SCRIPTINFO_ITYPEINFO) || item_itypeinfo);
+
+ HRESULT hr = S_OK;
+
+ std::wstring wname = item_name;
+ ScriptObjectMap::iterator iter = script_objects_.find(wname);
+ if (iter != script_objects_.end()) {
+ CComPtr<IDispatch> disp_obj = iter->second.m_T;
+
+ CComPtr<IUnknown> unknown;
+ if (return_mask & SCRIPTINFO_IUNKNOWN) {
+ DCHECK(item_unknown);
+ hr = disp_obj.QueryInterface(&unknown);
+ }
+
+ CComPtr<ITypeInfo> typeinfo;
+ if (SUCCEEDED(hr) && return_mask & SCRIPTINFO_ITYPEINFO) {
+ DCHECK(item_itypeinfo);
+ hr = disp_obj->GetTypeInfo(0, LANG_NEUTRAL, &typeinfo);
+ }
+
+ // We have everything ready, return the out args on success.
+ if (SUCCEEDED(hr)) {
+ if (return_mask & SCRIPTINFO_IUNKNOWN) {
+ hr = unknown.CopyTo(item_unknown);
+ DCHECK(SUCCEEDED(hr));
+ }
+ if (return_mask & SCRIPTINFO_ITYPEINFO) {
+ hr = typeinfo.CopyTo(item_itypeinfo);
+ DCHECK(SUCCEEDED(hr));
+ }
+ }
+ } else {
+ hr = TYPE_E_ELEMENTNOTFOUND;
+ }
+
+ return hr;
+}
+
+STDMETHODIMP ScriptHost::GetLCID(LCID *plcid) {
+ return E_NOTIMPL;
+}
+
+STDMETHODIMP ScriptHost::GetDocVersionString(BSTR* version) {
+ return E_NOTIMPL;
+}
+
+STDMETHODIMP ScriptHost::OnEnterScript() {
+ DebugDocMap::iterator it(debug_docs_.begin());
+ DebugDocMap::iterator end(debug_docs_.end());
+
+ // It is necessary to defer the document size notifications
+ // below until after defining all relevant script blocks,
+ // in order to tickle the debugger at just the right time
+ // so it can turn around and set any pending breakpoints
+ // prior to first execution.
+ for (; it != end; ++it) {
+ DebugDocInfo& info = it->second;
+
+ if (info.is_new) {
+ info.is_new = false;
+ info.document->AddDeferredText(info.len, 0);
+ }
+ }
+
+ return S_OK;
+}
+
+STDMETHODIMP ScriptHost::OnLeaveScript() {
+ return S_OK;
+}
+
+STDMETHODIMP ScriptHost::OnStateChange(SCRIPTSTATE state) {
+ return S_OK;
+}
+
+STDMETHODIMP ScriptHost::OnScriptTerminate(const VARIANT* result,
+ const EXCEPINFO* excep_info) {
+ return S_OK;
+}
+
+STDMETHODIMP ScriptHost::OnScriptError(IActiveScriptError* script_error) {
+ ScopedExcepInfo ei;
+ HRESULT hr = script_error->GetExceptionInfo(&ei);
+ LOG_IF(ERROR, FAILED(hr)) << "Failed to GetExceptionInfo. " <<
+ com::LogHr(hr);
+
+ CComBSTR source_line;
+ hr = script_error->GetSourceLineText(&source_line);
+ LOG_IF(ERROR, FAILED(hr)) << "Failed to GetSourceLineText. " <<
+ com::LogHr(hr);
+
+ DWORD context = 0;
+ ULONG line_number = 0;
+ LONG char_pos = 0;
+ hr = script_error->GetSourcePosition(&context, &line_number, &char_pos);
+ LOG_IF(ERROR, FAILED(hr)) << "Failed to GetSourcePosition. " <<
+ com::LogHr(hr);
+
+ LOG(ERROR) << "Script error occurred: " <<
+ com::ToString(ei.bstrDescription) << ". Source Text: " <<
+ com::ToString(source_line) << ". Context: "<< context << ", line: " <<
+ line_number << ", char pos: " << char_pos;
+ return S_OK;
+}
+
+STDMETHODIMP ScriptHost::GetDocumentContextFromPosition(
+ DWORD source_context, ULONG char_offset, ULONG num_chars,
+ IDebugDocumentContext** debug_doc_context) {
+ LOG(INFO) << "GetDocumentContextFromPosition(" << source_context << ", "
+ << char_offset << ", " << num_chars << ")";
+
+ DebugDocMap::iterator iter;
+ iter = debug_docs_.find(source_context);
+ if (iter != debug_docs_.end()) {
+ DebugDocInfo& info = iter->second;
+ ULONG start_position = 0;
+ HRESULT hr = info.document->GetScriptBlockInfo(source_context,
+ NULL,
+ &start_position,
+ NULL);
+ if (FAILED(hr))
+ LOG(ERROR) << "GetScriptBlockInfo failed " << com::LogHr(hr);
+
+ if (SUCCEEDED(hr)) {
+ hr = info.document->CreateDebugDocumentContext(
+ start_position + char_offset, num_chars, debug_doc_context);
+ if (FAILED(hr))
+ LOG(ERROR) << "GetScriptBlockInfo failed " << com::LogHr(hr);
+ }
+
+ return hr;
+ }
+
+ LOG(ERROR) << "No debug document for context " << source_context;
+
+ return E_FAIL;
+}
+
+STDMETHODIMP ScriptHost::GetApplication(IDebugApplication** debug_app) {
+ if (debug_application_)
+ return debug_application_->GetDebugApplication(debug_app);
+
+ return E_NOTIMPL;
+}
+
+STDMETHODIMP ScriptHost::GetRootApplicationNode(
+ IDebugApplicationNode** debug_app_node) {
+ DCHECK(debug_app_node);
+ if (!debug_app_node)
+ return E_POINTER;
+
+ if (debug_application_ != NULL) {
+ return debug_application_->GetRootApplicationNode(debug_app_node);
+ } else {
+ *debug_app_node = NULL;
+ return S_OK;
+ }
+
+ NOTREACHED();
+}
+
+STDMETHODIMP ScriptHost::OnScriptErrorDebug(IActiveScriptErrorDebug* err,
+ BOOL* enter_debugger, BOOL* call_on_script_err_when_continuing) {
+ DCHECK(err);
+ DCHECK(enter_debugger);
+ DCHECK(call_on_script_err_when_continuing);
+ if (!err || !enter_debugger || !call_on_script_err_when_continuing)
+ return E_POINTER;
+
+ // TODO(ericdingle@chromium.org): internationalization
+ int ret = ::MessageBox(
+ NULL, L"A script error occured. Do you want to debug?",
+ L"Google Chrome Extensions Execution Environment",
+ MB_ICONERROR | MB_SETFOREGROUND | MB_TASKMODAL | MB_YESNO);
+ *enter_debugger = (ret == IDYES);
+ *call_on_script_err_when_continuing = FALSE;
+
+ return S_OK;
+}
+
+STDMETHODIMP ScriptHost::QueryStatus(const GUID* cmd_group, ULONG num_cmds,
+ OLECMD cmds[], OLECMDTEXT *cmd_text) {
+ LOG(WARNING) << "QueryStatus " <<
+ CComBSTR(cmd_group ? *cmd_group : GUID_NULL) << ", " << num_cmds;
+ // We're practically unimplemented.
+ DLOG(INFO) << "ScriptHost::QueryStatus called";
+ return OLECMDERR_E_UNKNOWNGROUP;
+};
+
+STDMETHODIMP ScriptHost::Exec(const GUID* cmd_group, DWORD cmd_id,
+ DWORD cmd_exec_opt, VARIANT *arg_in, VARIANT *arg_out) {
+ LOG(WARNING) << "Exec " << CComBSTR(cmd_group ? *cmd_group : GUID_NULL) <<
+ ", " << cmd_id;
+
+ if (cmd_group && *cmd_group == CGID_ScriptSite) {
+ switch (cmd_id) {
+ case CMDID_SCRIPTSITE_URL:
+ return GetUrlForDocument(m_spUnkSite, arg_out);
+ break;
+ case CMDID_SCRIPTSITE_HTMLDLGTRUST:
+ DLOG(INFO) << "CMDID_SCRIPTSITE_HTMLDLGTRUST";
+ break;
+ case CMDID_SCRIPTSITE_SECSTATE:
+ DLOG(INFO) << "CMDID_SCRIPTSITE_SECSTATE";
+ break;
+ case CMDID_SCRIPTSITE_SID:
+ return GetSIDForDocument(m_spUnkSite, arg_out);
+ break;
+ case CMDID_SCRIPTSITE_TRUSTEDDOC:
+ DLOG(INFO) << "CMDID_SCRIPTSITE_TRUSTEDDOC";
+ break;
+ case CMDID_SCRIPTSITE_SECURITY_WINDOW:
+ return GetWindowForDocument(m_spUnkSite, arg_out);
+ break;
+ case CMDID_SCRIPTSITE_NAMESPACE:
+ DLOG(INFO) << "CMDID_SCRIPTSITE_NAMESPACE";
+ break;
+ case CMDID_SCRIPTSITE_IURI:
+ DLOG(INFO) << "CMDID_SCRIPTSITE_IURI";
+ break;
+ default:
+ DLOG(INFO) << "ScriptHost::Exec unknown command " << cmd_id;
+ break;
+ }
+ }
+
+ return OLECMDERR_E_UNKNOWNGROUP;
+}
+
+HRESULT ScriptHost::CreateScriptEngine(IActiveScript** script) {
+ return script_.CoCreateInstance(CLSID_JS, NULL, CLSCTX_INPROC_SERVER);
+}
+
+HRESULT ScriptHost::GetSourceContext(const wchar_t* file_path,
+ const wchar_t* code,
+ DWORD* source_context) {
+ DCHECK(debug_application_ != NULL);
+ HRESULT hr = S_OK;
+ CComPtr<IDebugDocumentHelper> helper;
+ hr = debug_application_->CreateDebugDocumentHelper(file_path,
+ code,
+ 0,
+ &helper);
+ size_t len = lstrlenW(code);
+ if (SUCCEEDED(hr) && helper != NULL) {
+ hr = helper->DefineScriptBlock(0, len, script_, FALSE, source_context);
+ DCHECK(SUCCEEDED(hr));
+
+ DebugDocInfo info;
+ info.is_new = true;
+ info.len = len;
+ info.document = helper;
+ debug_docs_.insert(std::make_pair(*source_context, info));
+ }
+
+ return hr;
+}
+
+HRESULT ScriptHost::AddDebugDocument(const wchar_t* file_path,
+ const wchar_t* code,
+ IDebugDocumentHelper** doc) {
+ DCHECK(debug_application_ != NULL);
+ return debug_application_->CreateDebugDocumentHelper(file_path,
+ code,
+ TEXT_DOC_ATTR_READONLY,
+ doc);
+}
+
+HRESULT ScriptHost::RunScriptSnippet(size_t start_offset,
+ const wchar_t* code,
+ IDebugDocumentHelper* doc) {
+ DWORD source_context = 0;
+ if (doc) {
+ size_t len = lstrlenW(code);
+ HRESULT hr = doc->DefineScriptBlock(start_offset,
+ len,
+ script_,
+ FALSE,
+ &source_context);
+
+ if (SUCCEEDED(hr)) {
+ DebugDocInfo info;
+ info.document = doc;
+ info.len = start_offset + len;
+ info.is_new = true;
+
+ debug_docs_.insert(std::make_pair(source_context, info));
+ } else {
+ LOG(ERROR) << "Failed to define a script block " << com::LogHr(hr);
+ LOG(ERROR) << "Script: " << code;
+ }
+ }
+
+ ScopedExcepInfo ei;
+ HRESULT hr = script_parse_->ParseScriptText(
+ code, NULL, NULL, NULL, source_context, 0,
+ SCRIPTTEXT_HOSTMANAGESSOURCE | SCRIPTTEXT_ISVISIBLE, NULL, &ei);
+
+ // Ignore compilation and runtime errors in the script
+ if (FAILED(hr) && hr != OLESCRIPT_E_SYNTAX) {
+ LOG(ERROR) << "Non-script error occurred while parsing script. "
+ << com::LogHr(hr);
+ NOTREACHED();
+ }
+
+ return hr;
+}
+
+ScriptHost::DebugApplication::DebugApplication(const wchar_t* application_name)
+ : application_name_(application_name),
+ debug_app_cookie_(kInvalidDebugAppCookie),
+ initialization_count_(0) {
+}
+
+ScriptHost::DebugApplication::~DebugApplication() {
+ DCHECK(debug_manager_ == NULL);
+ DCHECK(debug_application_ == NULL);
+ DCHECK_EQ(0U, initialization_count_);
+}
+
+void ScriptHost::DebugApplication::RegisterDebugApplication() {
+ DCHECK(debug_manager_ == NULL);
+ DCHECK(debug_application_ == NULL);
+ DCHECK(debug_app_cookie_ == kInvalidDebugAppCookie);
+
+ // Don't need to lock as this MUST be single-threaded and first use.
+ CComPtr<IProcessDebugManager> manager;
+ HRESULT hr = CreateProcessDebugManager(&manager);
+
+ CComPtr<IDebugApplication> application;
+ if (SUCCEEDED(hr))
+ hr = manager->CreateApplication(&application);
+
+ if (SUCCEEDED(hr))
+ hr = application->SetName(application_name_);
+
+ DWORD cookie = 0;
+ if (SUCCEEDED(hr))
+ hr = manager->AddApplication(application, &cookie);
+
+ if (FAILED(hr)) {
+ LOG(INFO) << "ScriptHost debug initialization failed: " << com::LogHr(hr);
+ return;
+ }
+
+ debug_manager_ = manager;
+ debug_application_ = application;
+ debug_app_cookie_ = cookie;
+}
+
+void ScriptHost::DebugApplication::Initialize() {
+ AutoLock lock(lock_);
+
+ ++initialization_count_;
+
+ if (initialization_count_ == 1)
+ RegisterDebugApplication();
+}
+
+void ScriptHost::DebugApplication::Initialize(
+ IUnknown* debug_application_provider) {
+ AutoLock lock(lock_);
+
+ ++initialization_count_;
+
+ if (initialization_count_ == 1) {
+ DCHECK(debug_manager_ == NULL);
+ DCHECK(debug_application_ == NULL);
+ DCHECK(debug_app_cookie_ == kInvalidDebugAppCookie);
+
+ CComPtr<IDebugApplication> debug_app;
+ HRESULT hr = debug_application_provider->QueryInterface(&debug_app);
+ if (FAILED(hr) || debug_app == NULL) {
+ CComPtr<IServiceProvider> sp;
+ hr = debug_application_provider->QueryInterface(&sp);
+ if (SUCCEEDED(hr) && sp != NULL)
+ hr = sp->QueryService(IID_IDebugApplication, &debug_app);
+ }
+
+ if (debug_app != NULL)
+ debug_application_ = debug_app;
+ else
+ RegisterDebugApplication();
+ }
+}
+
+void ScriptHost::DebugApplication::Initialize(
+ IProcessDebugManager* manager, IDebugApplication* app) {
+ AutoLock lock(lock_);
+ // This function is exposed for testing only.
+ DCHECK_EQ(0U, initialization_count_);
+
+ DCHECK_EQ(static_cast<IUnknown*>(NULL), debug_manager_);
+ DCHECK_EQ(static_cast<IUnknown*>(NULL), debug_application_);
+ DCHECK_EQ(kInvalidDebugAppCookie, debug_app_cookie_);
+
+ ++initialization_count_;
+
+ debug_manager_ = manager;
+ debug_application_ = app;
+}
+
+
+void ScriptHost::DebugApplication::Terminate() {
+ AutoLock lock(lock_);
+ DCHECK_GT(initialization_count_, (size_t)0);
+ --initialization_count_;
+
+ if (initialization_count_ == 0) {
+ if (debug_manager_ != NULL) {
+ if (debug_app_cookie_ != kInvalidDebugAppCookie) {
+ HRESULT hr = debug_manager_->RemoveApplication(debug_app_cookie_);
+ DCHECK(SUCCEEDED(hr));
+ }
+ }
+
+ debug_manager_.Release();
+ debug_application_.Release();
+ debug_app_cookie_ = kInvalidDebugAppCookie;
+ }
+}
+
+HRESULT ScriptHost::DebugApplication::GetDebugApplication(
+ IDebugApplication** app) {
+ AutoLock lock(lock_);
+
+ if (debug_application_ == NULL)
+ return E_NOTIMPL;
+
+ return debug_application_.CopyTo(app);
+}
+
+HRESULT ScriptHost::DebugApplication::GetRootApplicationNode(
+ IDebugApplicationNode** debug_app_node) {
+ AutoLock lock(lock_);
+
+ if (debug_application_ == NULL) {
+ *debug_app_node = NULL;
+ return S_OK;
+ }
+
+ return debug_application_->GetRootNode(debug_app_node);
+}
+
+HRESULT ScriptHost::DebugApplication::CreateDebugDocumentHelper(
+ const wchar_t* long_name, const wchar_t* code, TEXT_DOC_ATTR attributes,
+ IDebugDocumentHelper** helper) {
+ AutoLock lock(lock_);
+
+ if (debug_application_ == NULL)
+ return S_OK;
+
+ // Find the last forward or backward slash in the long name,
+ // and construct a short name from the rest - the base name.
+ std::wstring name(long_name);
+ size_t pos = name.find_last_of(L"\\/");
+ const wchar_t* short_name = long_name;
+ if (pos != name.npos && long_name[pos + 1] != '\0')
+ short_name = long_name + pos + 1;
+
+ CComPtr<IDebugDocumentHelper> doc;
+ HRESULT hr = CreateDebugDocumentHelper(&doc);
+ if (SUCCEEDED(hr))
+ hr = doc->Init(debug_application_, short_name, long_name, attributes);
+
+ // Wrap the text in a document host.
+ CComPtr<IDebugDocumentHost> host;
+ if (SUCCEEDED(hr))
+ hr = DocHost::CreateInitialized(code, &host);
+ if (SUCCEEDED(hr))
+ hr = doc->SetDebugDocumentHost(host);
+ if (SUCCEEDED(hr))
+ hr = doc->Attach(NULL);
+
+ if (SUCCEEDED(hr)) {
+ DCHECK(doc != NULL);
+
+ *helper = doc.Detach();
+ }
+
+ return hr;
+}
+
+HRESULT ScriptHost::DebugApplication::CreateDebugDocumentHelper(
+ IDebugDocumentHelper** helper) {
+ // Create the debug document.
+ if (debug_manager_ != NULL)
+ return debug_manager_->CreateDebugDocumentHelper(NULL, helper);
+
+ // As it turns out, it's better to create a debug document
+ // from the same DLL server as issued the debug manager, so let's
+ // try and accomodate. Get the class object function from the
+ // in-process instance.
+ HMODULE pdm = ::GetModuleHandle(L"pdm.dll");
+ LPFNGETCLASSOBJECT pdm_get_class_object = NULL;
+ if (pdm != NULL)
+ pdm_get_class_object = reinterpret_cast<LPFNGETCLASSOBJECT>(
+ ::GetProcAddress(pdm, "DllGetClassObject"));
+
+ // Fallback to plain CoCreateInstance if we didn't get the function.
+ if (!pdm_get_class_object) {
+ LOG(WARNING) << "CreateDebugDocumentHelper falling back to "
+ "CoCreateInstance";
+ return ::CoCreateInstance(CLSID_CDebugDocumentHelper,
+ NULL,
+ CLSCTX_INPROC_SERVER,
+ IID_IDebugDocumentHelper,
+ reinterpret_cast<void**>(helper));
+ }
+
+ // Create a debug helper.
+ CComPtr<IClassFactory> factory;
+ HRESULT hr = pdm_get_class_object(CLSID_CDebugDocumentHelper,
+ IID_IClassFactory,
+ reinterpret_cast<void**>(&factory));
+ if (SUCCEEDED(hr)) {
+ DCHECK(factory != NULL);
+ hr = factory->CreateInstance(NULL,
+ IID_IDebugDocumentHelper,
+ reinterpret_cast<void**>(helper));
+ }
+
+ return hr;
+}
+
+HRESULT ScriptHost::DebugApplication::CreateProcessDebugManager(
+ IProcessDebugManager** manager) {
+ return ::CoCreateInstance(CLSID_ProcessDebugManager,
+ NULL,
+ CLSCTX_INPROC_SERVER,
+ IID_IProcessDebugManager,
+ reinterpret_cast<void**>(manager));
+}
diff --git a/ceee/ie/plugin/scripting/script_host.h b/ceee/ie/plugin/scripting/script_host.h
new file mode 100644
index 0000000..553d142
--- /dev/null
+++ b/ceee/ie/plugin/scripting/script_host.h
@@ -0,0 +1,317 @@
+// 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.
+//
+// A host for Microsoft's JScript engine that we use to create a separate
+// engine for our content scripts so that they do not run in the same
+// context/namespace as the scripts that are part of the page.
+
+#ifndef CEEE_IE_PLUGIN_SCRIPTING_SCRIPT_HOST_H_
+#define CEEE_IE_PLUGIN_SCRIPTING_SCRIPT_HOST_H_
+
+// The Platform SDK version of this header is newer than the
+// Active Script SDK, and we use some newer interfaces.
+#include <activscp.h>
+#include <atlbase.h>
+#include <atlcom.h>
+#include <dispex.h>
+#include <docobj.h>
+#include <map>
+#include <string>
+
+#include "base/lock.h"
+#include "base/logging.h"
+#include "third_party/activscp/activdbg.h"
+#include "ceee/common/initializing_coclass.h"
+
+#ifndef OLESCRIPT_E_SYNTAX
+#define OLESCRIPT_E_SYNTAX 0x80020101
+#endif
+
+extern const GUID IID_IScriptHost;
+
+// Interface for ScriptHost needed for unit testing.
+class IScriptHost : public IUnknown {
+ public:
+ // Registers an IDispatch script object with the script host.
+ // @param name The name the object will have inside the script host.
+ // @param disp_obj The IDispatch object to register.
+ // @param make_members_global Whether or not to make the object's members
+ // global.
+ virtual HRESULT RegisterScriptObject(const wchar_t* name,
+ IDispatch* disp_obj,
+ bool make_members_global) = 0;
+
+ // Run the specified script in the script host.
+ // @param file_path The name/path to the file for debugging.
+ // @param code The code to be executed.
+ virtual HRESULT RunScript(const wchar_t* file_path, const wchar_t* code) = 0;
+
+ // Run the specified expression in the script host and get its return value.
+ // @param code The code to be executed.
+ // @param result A variant to write the result to.
+ virtual HRESULT RunExpression(const wchar_t* code, VARIANT* result) = 0;
+
+ // Close the script host and release resources.
+ virtual HRESULT Close() = 0;
+};
+
+// Implementation of ScriptHost class.
+//
+// This implements the requisite IActiveScript{Debug} interfaces necessary
+// to host a active script engine and to integrate with script debugging.
+// It also exposes IServiceProvider and IOleCommandTarget, which serve the
+// purpose of declaring the script code origin to the IE DOM. When our scripts
+// invoke on the IE DOM through IDispatchEx::InvokeEx, the last parameter is
+// a service provider that leads back to this host. The IE DOM implementation
+// will query this service provider for SID_GetScriptSite, and acquire an
+// IOleCommandTarget on it, which in turn gets interrogated about the scripts
+// origin and security properties.
+class ATL_NO_VTABLE ScriptHost
+ : public CComObjectRootEx<CComSingleThreadModel>,
+ public InitializingCoClass<ScriptHost>,
+ public IActiveScriptSite,
+ public IActiveScriptSiteDebug,
+ public IServiceProviderImpl<ScriptHost>,
+ public IObjectWithSiteImpl<ScriptHost>,
+ public IOleCommandTarget,
+ public IScriptHost {
+ public:
+ BEGIN_COM_MAP(ScriptHost)
+ COM_INTERFACE_ENTRY(IActiveScriptSite)
+ COM_INTERFACE_ENTRY(IActiveScriptSiteDebug)
+ COM_INTERFACE_ENTRY(IServiceProvider)
+ COM_INTERFACE_ENTRY(IOleCommandTarget)
+ COM_INTERFACE_ENTRY(IObjectWithSite)
+ COM_INTERFACE_ENTRY_IID(IID_IScriptHost, IScriptHost)
+ END_COM_MAP()
+ BEGIN_SERVICE_MAP(ScriptHost)
+ SERVICE_ENTRY(SID_GetScriptSite)
+ // We delegate to our site object. This allows the site to provide
+ // SID_SInternetHostSecurityManager, which can govern ActiveX control
+ // creation and the like.
+ SERVICE_ENTRY_CHAIN(m_spUnkSite)
+ END_SERVICE_MAP()
+
+ DECLARE_PROTECT_FINAL_CONSTRUCT();
+
+ ScriptHost();
+
+ // Fwd.
+ class DebugApplication;
+
+ // Sets the default debug application script host instaces
+ // will use unless provided with another specific instance.
+ // Note: a DebugApplication instance set here must outlive all
+ // ScriptHost instances in this process.
+ static void set_default_debug_application(DebugApplication* debug) {
+ default_debug_application_ = debug;
+ }
+ static DebugApplication* default_debug_application() {
+ return default_debug_application_;
+ }
+
+ // Initialize with a debug application and return a pointer to self.
+ // @param debug_application the debug application on whose behalf we're
+ // running.
+ // @param self returns a referenceless pointer to new instance.
+ HRESULT Initialize(DebugApplication* debug_application, ScriptHost** self);
+
+ // Initialize with a debug application.
+ // @param debug_application the debug application on whose behalf we're
+ // running.
+ HRESULT Initialize(DebugApplication* debug_application);
+
+ HRESULT Initialize();
+
+ void FinalRelease();
+
+ // IScriptHost methods.
+ HRESULT RegisterScriptObject(const wchar_t* name, IDispatch* disp_obj,
+ bool make_members_global);
+ HRESULT RunScript(const wchar_t* file_path, const wchar_t* code);
+ HRESULT RunExpression(const wchar_t* code, VARIANT* result);
+ HRESULT Close();
+
+ // IActiveScriptSite methods.
+ STDMETHOD(GetItemInfo)(LPCOLESTR item_name, DWORD return_mask,
+ IUnknown** item_iunknown, ITypeInfo** item_itypeinfo);
+ STDMETHOD(GetLCID)(LCID *plcid);
+ STDMETHOD(GetDocVersionString)(BSTR* version);
+ STDMETHOD(OnEnterScript)();
+ STDMETHOD(OnLeaveScript)();
+ STDMETHOD(OnStateChange)(SCRIPTSTATE state);
+ STDMETHOD(OnScriptTerminate)(const VARIANT* result,
+ const EXCEPINFO* excep_info);
+ STDMETHOD(OnScriptError)(IActiveScriptError* script_error);
+
+ // IActiveScriptSiteDebug methods.
+ STDMETHOD(GetDocumentContextFromPosition)(DWORD source_context,
+ ULONG char_offset, ULONG num_chars,
+ IDebugDocumentContext** debug_doc_context);
+ STDMETHOD(GetApplication)(IDebugApplication** debug_app);
+ STDMETHOD(GetRootApplicationNode)(IDebugApplicationNode** debug_app_node);
+ STDMETHOD(OnScriptErrorDebug)(IActiveScriptErrorDebug* error_debug,
+ BOOL* enter_debugger, BOOL* call_on_script_err_when_continuing);
+
+
+ // @name IOleCommandTarget methods
+ // @{
+ STDMETHOD(QueryStatus)(const GUID* cmd_group,
+ ULONG num_cmds,
+ OLECMD cmds[],
+ OLECMDTEXT *cmd_text);
+ STDMETHOD(Exec)(const GUID* cmd_group,
+ DWORD cmd_id,
+ DWORD cmd_exec_opt,
+ VARIANT* arg_in,
+ VARIANT* arg_out);
+ // @}
+
+ // @name Debug-only functionality.
+ // @(
+ // Create a debug document for @p code, which originates from @p file
+ // and return it in @doc.
+ // @param file_path a file path or URL to code.
+ // @param code document code containing JavaScript snippets.
+ // @param doc on success returns the new debug document.
+ HRESULT AddDebugDocument(const wchar_t* file_path,
+ const wchar_t* code,
+ IDebugDocumentHelper** doc);
+
+ // Run a JavaScript snippet from a previously declared document.
+ // @param start_offset character offset from start of document to
+ // @p code.
+ // @param code a code snippet from a document previously declared
+ // to AddDebugDocument.
+ // @param doc a debug document previously received from AddDebugDocument.
+ HRESULT RunScriptSnippet(size_t start_offset,
+ const wchar_t* code,
+ IDebugDocumentHelper* doc);
+ // @}
+
+ protected:
+ // Virtual methods to inject dependencies for unit testing.
+ virtual HRESULT CreateScriptEngine(IActiveScript** script);
+
+ private:
+ struct ScopedExcepInfo : public EXCEPINFO {
+ public:
+ ScopedExcepInfo() {
+ bstrSource = NULL;
+ bstrDescription = NULL;
+ bstrHelpFile = NULL;
+ }
+ ~ScopedExcepInfo() {
+ if (bstrSource) {
+ ::SysFreeString(bstrSource);
+ bstrSource = NULL;
+ }
+ if (bstrDescription) {
+ ::SysFreeString(bstrDescription);
+ bstrDescription = NULL;
+ }
+ if (bstrHelpFile) {
+ ::SysFreeString(bstrHelpFile);
+ bstrHelpFile = NULL;
+ }
+ }
+ };
+
+ HRESULT GetSourceContext(const wchar_t* file_path,
+ const wchar_t* code,
+ DWORD* source_context);
+
+ // The JScript script engine. NULL until Initialize().
+ CComPtr<IActiveScript> script_;
+
+ // The JScript parser. NULL until Initialize().
+ CComQIPtr<IActiveScriptParse> script_parse_;
+
+ // A map of wstring to IDispatch pointers to hold registered script objects.
+ // Empty on Initialize(). Filled using calls to RegisterScriptObject().
+ typedef std::map<std::wstring, CAdapt<CComPtr<IDispatch> > > ScriptObjectMap;
+ ScriptObjectMap script_objects_;
+
+ // A map of source contexts to debug document helpers used for debugging.
+ // Empty on Initialize(). Filled using calls to CreateDebugDoc().
+ // Resources released and emptied on Close().
+ struct DebugDocInfo {
+ bool is_new;
+ size_t len;
+ CComPtr<IDebugDocumentHelper> document;
+ };
+ typedef std::map<DWORD, DebugDocInfo> DebugDocMap;
+ DebugDocMap debug_docs_;
+
+ // Our debug application state.
+ DebugApplication* debug_application_;
+
+ // Default debug application state, which is used if no other state is
+ // provided at initialization time.
+ static DebugApplication* default_debug_application_;
+};
+
+class ScriptHost::DebugApplication {
+ public:
+ DebugApplication(const wchar_t* application_name);
+ ~DebugApplication();
+
+ // Best-effort initialize process script debugging.
+ // Every call to this function must be matched with a call to Terminate().
+ void Initialize();
+
+ // Best-effort initialize process script debugging.
+ // @param manager_or_provider either implements IDebugApplication or
+ // IServiceProvider. Both methods will be tried in turn to aquire
+ // an IDebugApplication. If both methods fail, this will fall back
+ // to initialization by creating a new debug application.
+ void Initialize(IUnknown* debug_application_provider);
+
+ // Exposed for testing only, needs a corresponding call to Terminate.
+ void Initialize(IProcessDebugManager* manager, IDebugApplication* app);
+
+ // Terminate script debugging, call once for every call to Initialize().
+ void Terminate();
+
+ // Creates a debug document helper with the given long name, containing code.
+ HRESULT CreateDebugDocumentHelper(const wchar_t* long_name,
+ const wchar_t* code,
+ TEXT_DOC_ATTR attributes,
+ IDebugDocumentHelper** helper);
+ // Retrieve the debug application.
+ HRESULT GetDebugApplication(IDebugApplication** application);
+ // Retrieve root application node.
+ HRESULT GetRootApplicationNode(IDebugApplicationNode** debug_app_node);
+
+ private:
+ // Virtual for testing.
+ virtual HRESULT CreateProcessDebugManager(IProcessDebugManager** manager);
+ virtual HRESULT CreateDebugDocumentHelper(IDebugDocumentHelper** helper);
+
+ // Creates and registers a new debug application for us.
+ void RegisterDebugApplication(); // Under lock_.
+
+ // Protects all members below.
+ ::Lock lock_; // Our containing class has a Lock method.
+
+ // Number of initialization calls.
+ size_t initialization_count_;
+
+ // The debug manager, non-NULL only if we register
+ // our own debug application.
+ CComPtr<IProcessDebugManager> debug_manager_;
+
+ // Registration cookie for debug_manager_ registration of debug_application_.
+ DWORD debug_app_cookie_;
+
+ // The debug application to be used for debugging. NULL until Initialize().
+ CComPtr<IDebugApplication> debug_application_;
+
+ static const DWORD kInvalidDebugAppCookie = 0;
+
+ // The application name we register.
+ const wchar_t* application_name_;
+};
+
+#endif // CEEE_IE_PLUGIN_SCRIPTING_SCRIPT_HOST_H_
diff --git a/ceee/ie/plugin/scripting/script_host_unittest.cc b/ceee/ie/plugin/scripting/script_host_unittest.cc
new file mode 100644
index 0000000..4e52874
--- /dev/null
+++ b/ceee/ie/plugin/scripting/script_host_unittest.cc
@@ -0,0 +1,519 @@
+// 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.
+//
+// Script host implementation unit tests.
+
+#include "ceee/ie/plugin/scripting/script_host.h"
+
+#include "ceee/testing/utils/mock_com.h"
+#include "ceee/testing/utils/test_utils.h"
+#include "ceee/testing/utils/dispex_mocks.h"
+#include "ceee/testing/utils/mshtml_mocks.h"
+#include "ceee/testing/utils/instance_count_mixin.h"
+#include "gmock/gmock.h"
+#include "gtest/gtest.h"
+
+namespace {
+
+using testing::InstanceCountMixin;
+using testing::MockDispatchEx;
+using testing::MockIServiceProvider;
+
+using testing::_;
+using testing::AddRef;
+using testing::AllOf;
+using testing::CopyInterfaceToArgument;
+using testing::DispParamArgEq;
+using testing::DoAll;
+using testing::Eq;
+using testing::Return;
+using testing::SetArgumentPointee;
+using testing::StrictMock;
+using testing::StrEq;
+
+class MockIHTMLWindow2
+ : public CComObjectRootEx<CComSingleThreadModel>,
+ public StrictMock<IHTMLWindow2MockImpl>,
+ public InstanceCountMixin<MockIHTMLWindow2> {
+ public:
+ BEGIN_COM_MAP(MockIHTMLWindow2)
+ COM_INTERFACE_ENTRY(IDispatch)
+ COM_INTERFACE_ENTRY(IHTMLWindow2)
+ COM_INTERFACE_ENTRY(IHTMLFramesCollection2)
+ END_COM_MAP()
+
+ HRESULT Initialize(MockIHTMLWindow2** self) {
+ *self = this;
+ return S_OK;
+ }
+};
+
+class MockProcessDebugManager
+ : public CComObjectRootEx<CComSingleThreadModel>,
+ public StrictMock<testing::IProcessDebugManagerMockImpl>,
+ public InstanceCountMixin<MockProcessDebugManager> {
+ public:
+ BEGIN_COM_MAP(MockProcessDebugManager)
+ COM_INTERFACE_ENTRY(IProcessDebugManager)
+ END_COM_MAP()
+
+ HRESULT Initialize(MockProcessDebugManager** debug_manager) {
+ *debug_manager = this;
+ return S_OK;
+ }
+};
+
+class MockDebugApplication
+ : public CComObjectRootEx<CComSingleThreadModel>,
+ public StrictMock<testing::IDebugApplicationMockImpl>,
+ public InstanceCountMixin<MockDebugApplication> {
+ public:
+ BEGIN_COM_MAP(MockDebugApplication)
+ COM_INTERFACE_ENTRY(IDebugApplication)
+ END_COM_MAP()
+
+ HRESULT Initialize(MockDebugApplication** debug_application) {
+ *debug_application = this;
+ return S_OK;
+ }
+};
+
+class MockDebugDocumentHelper
+ : public CComObjectRootEx<CComSingleThreadModel>,
+ public StrictMock<testing::IDebugDocumentHelperMockImpl>,
+ public InstanceCountMixin<MockDebugDocumentHelper> {
+ public:
+ BEGIN_COM_MAP(MockDebugDocumentHelper)
+ COM_INTERFACE_ENTRY(IDebugDocumentHelper)
+ END_COM_MAP()
+
+ HRESULT Initialize(MockDebugDocumentHelper** debug_document) {
+ *debug_document = this;
+ return S_OK;
+ }
+};
+
+const wchar_t* kDebugApplicationName = L"ScriptHostTest";
+
+class TestingDebugApplication : public ScriptHost::DebugApplication {
+ public:
+ TestingDebugApplication()
+ : ScriptHost::DebugApplication(kDebugApplicationName) {
+ }
+
+ MOCK_METHOD1(CreateProcessDebugManager,
+ HRESULT(IProcessDebugManager** manager));
+};
+
+class MockIActiveScriptAndParse
+ : public CComObjectRootEx<CComSingleThreadModel>,
+ public StrictMock<testing::IActiveScriptMockImpl>,
+ public StrictMock<testing::IActiveScriptParseMockImpl>,
+ public StrictMock<testing::IObjectSafetyMockImpl>,
+ public InstanceCountMixin<MockIActiveScriptAndParse> {
+ public:
+ BEGIN_COM_MAP(MockIActiveScriptAndParse)
+ COM_INTERFACE_ENTRY(IActiveScript)
+ COM_INTERFACE_ENTRY(IActiveScriptParse)
+ COM_INTERFACE_ENTRY(IObjectSafety)
+ END_COM_MAP()
+
+ HRESULT Initialize(MockIActiveScriptAndParse** script) {
+ *script = this;
+ return S_OK;
+ }
+};
+
+class TestingScriptHost
+ : public ScriptHost,
+ public InstanceCountMixin<TestingScriptHost> {
+ public:
+ HRESULT Initialize(ScriptHost::DebugApplication* debug,
+ IActiveScript* script) {
+ my_script_ = script;
+ ScriptHost::Initialize(debug);
+ return S_OK;
+ }
+
+ private:
+ HRESULT CreateDebugManager(IProcessDebugManager** debug_manager) {
+ return my_debug_manager_.CopyTo(debug_manager);
+ }
+ HRESULT CreateScriptEngine(IActiveScript** script) {
+ return my_script_.CopyTo(script);
+ }
+
+ CComPtr<IProcessDebugManager> my_debug_manager_;
+ CComPtr<IActiveScript> my_script_;
+};
+
+const DISPID kDispId = 5;
+const wchar_t* kDispObjName = L"tpain";
+const wchar_t* kDispObjName2 = L"akon";
+const wchar_t* kJsFilePath = L"liljon.js";
+const wchar_t* kJsCode = L"alert('WWHHHATTT? OOOKKAAYY.')";
+const DWORD kAddFlags = SCRIPTITEM_ISSOURCE | SCRIPTITEM_ISVISIBLE;
+const DWORD kAddGlobalFlags = kAddFlags | SCRIPTITEM_GLOBALMEMBERS;
+const DWORD kSourceContext = 123456;
+const DWORD kUnknownSourceContext = 654321;
+const DWORD kNoSourceContext = 0;
+const DWORD kScriptFlags =
+ SCRIPTTEXT_HOSTMANAGESSOURCE | SCRIPTTEXT_ISVISIBLE;
+const DWORD kExpressionFlags = SCRIPTTEXT_ISEXPRESSION;
+const ULONG kCharOffset = 1234;
+const ULONG kNumChars = 4321;
+
+class ScriptHostTest : public testing::Test {
+ public:
+ ScriptHostTest() : mock_debug_manager_(NULL), mock_debug_application_(NULL),
+ mock_script_(NULL), debug_(kDebugApplicationName) {
+ }
+
+ void SetUp() {
+ ASSERT_HRESULT_SUCCEEDED(
+ InitializingCoClass<MockProcessDebugManager>::
+ CreateInitializedIID(&mock_debug_manager_,
+ IID_IProcessDebugManager,
+ &debug_manager_));
+ ASSERT_HRESULT_SUCCEEDED(
+ InitializingCoClass<MockDebugApplication>::
+ CreateInitializedIID(&mock_debug_application_,
+ IID_IDebugApplication,
+ &debug_application_));
+ ASSERT_HRESULT_SUCCEEDED(
+ InitializingCoClass<MockIActiveScriptAndParse>::CreateInitializedIID(
+ &mock_script_, IID_IActiveScript, &script_));
+
+ debug_.Initialize(debug_manager_, debug_application_);
+ }
+
+ void ExpectEngineInitialization() {
+ EXPECT_CALL(*mock_script_, SetScriptSite(_)).WillOnce(
+ Return(S_OK));
+ EXPECT_CALL(*mock_script_, InitNew()).WillOnce(Return(S_OK));
+ EXPECT_CALL(*mock_script_, SetScriptState(SCRIPTSTATE_CONNECTED)).WillOnce(
+ Return(S_OK));
+ EXPECT_CALL(*mock_script_, SetInterfaceSafetyOptions(
+ IID_IDispatch, INTERFACE_USES_SECURITY_MANAGER,
+ INTERFACE_USES_SECURITY_MANAGER)).WillOnce(Return(S_OK));
+ }
+
+ void TearDown() {
+ if (script_host_) {
+ EXPECT_CALL(*mock_script_, Close()).WillOnce(Return(S_OK));
+
+ script_host_->Close();
+ }
+
+ mock_debug_manager_ = NULL;
+ debug_manager_.Release();
+
+ mock_debug_application_ = NULL;
+ debug_application_.Release();
+
+ mock_script_ = NULL;
+ script_.Release();
+
+ script_host_.Release();
+
+ debug_.Terminate();
+
+ // Everything should have been relinquished.
+ ASSERT_EQ(0, testing::InstanceCountMixinBase::all_instance_count());
+ }
+
+ void CreateScriptHost() {
+ ExpectEngineInitialization();
+
+ ASSERT_HRESULT_SUCCEEDED(
+ InitializingCoClass<TestingScriptHost>::CreateInitializedIID(
+ &debug_, script_, IID_IScriptHost, &script_host_));
+ }
+
+ void ExpectCreateDebugDocumentHelper(
+ MockDebugDocumentHelper** mock_debug_document) {
+ CComPtr<IDebugDocumentHelper> debug_document;
+ ASSERT_HRESULT_SUCCEEDED(
+ InitializingCoClass<MockDebugDocumentHelper>::CreateInitialized(
+ mock_debug_document, &debug_document));
+
+ EXPECT_CALL(*mock_debug_manager_, CreateDebugDocumentHelper(NULL, _))
+ .WillOnce(DoAll(CopyInterfaceToArgument<1>(debug_document),
+ Return(S_OK)));
+
+ EXPECT_CALL(**mock_debug_document,
+ Init(Eq(debug_application_), StrEq(kJsFilePath), StrEq(kJsFilePath), _))
+ .WillOnce(Return(S_OK));
+ EXPECT_CALL(**mock_debug_document, Attach(NULL)).WillOnce(Return(S_OK));
+ EXPECT_CALL(**mock_debug_document, SetDebugDocumentHost(_)).WillOnce(
+ Return(S_OK));
+ EXPECT_CALL(**mock_debug_document,
+ DefineScriptBlock(_, _, Eq(script_), _, _))
+ .WillOnce(DoAll(SetArgumentPointee<4>(kSourceContext), Return(S_OK)));
+
+ // Detach will be called on this document when the script host is closed
+ EXPECT_CALL(**mock_debug_document, Detach()).WillOnce(Return(S_OK));
+ }
+
+ void ExpectParseScriptText(DWORD source_context, DWORD flags) {
+ EXPECT_CALL(*mock_script_, ParseScriptText(StrEq(kJsCode), NULL, NULL, NULL,
+ source_context, 0, flags, _, _))
+ .WillOnce(Return(S_OK));
+ }
+
+ MockProcessDebugManager* mock_debug_manager_;
+ CComPtr<IProcessDebugManager> debug_manager_;
+ MockDebugApplication* mock_debug_application_;
+ CComPtr<IDebugApplication> debug_application_;
+ MockIActiveScriptAndParse* mock_script_;
+ CComPtr<IActiveScript> script_;
+ CComPtr<IScriptHost> script_host_;
+
+ ScriptHost::DebugApplication debug_;
+};
+
+TEST_F(ScriptHostTest, DebugApplicationDefaultInitialize) {
+ TestingDebugApplication debug;
+
+ EXPECT_CALL(debug, CreateProcessDebugManager(_))
+ .WillOnce(
+ DoAll(CopyInterfaceToArgument<0>(debug_manager_),
+ Return(S_OK)));
+
+ EXPECT_CALL(*mock_debug_manager_, CreateApplication(_)).WillOnce(
+ DoAll(CopyInterfaceToArgument<0>(debug_application_), Return(S_OK)));
+ EXPECT_CALL(*mock_debug_application_, SetName(StrEq(kDebugApplicationName)))
+ .WillOnce(Return(S_OK));
+ EXPECT_CALL(*mock_debug_manager_, AddApplication(
+ static_cast<IDebugApplication*>(debug_application_), _))
+ .WillOnce(DoAll(SetArgumentPointee<1>(42), Return(S_OK)));
+
+ debug.Initialize();
+
+ // We expect the script host to undo debug app registration.
+ EXPECT_CALL(*mock_debug_manager_, RemoveApplication(42))
+ .WillOnce(Return(S_OK));
+ debug.Terminate();
+}
+
+// Test initializing debugging with a service provider.
+TEST_F(ScriptHostTest, DebugApplicationInitializeWithServiceProvider) {
+ TestingDebugApplication debug;
+
+ MockIServiceProvider* sp_keeper;
+ CComPtr<IServiceProvider> sp;
+ ASSERT_HRESULT_SUCCEEDED(
+ InitializingCoClass<MockIServiceProvider>::CreateInitialized(
+ &sp_keeper, &sp));
+
+
+ EXPECT_CALL(*sp_keeper,
+ QueryService(IID_IDebugApplication, IID_IDebugApplication, _))
+ .WillOnce(DoAll(
+ AddRef(debug_manager_.p),
+ SetArgumentPointee<2>(static_cast<void*>(debug_manager_)),
+ Return(S_OK)));
+
+ // Initialization with a service provider that yields
+ // a debug application should only query service, and
+ // not try to create a debug manager.
+ EXPECT_CALL(debug, CreateProcessDebugManager(_)).Times(0);
+
+ debug.Initialize(sp);
+
+ // And there should be no app unregistration.
+ debug.Terminate();
+}
+
+TEST_F(ScriptHostTest, DebugApplicationInitializeWithEmptyServiceProvider) {
+ TestingDebugApplication debug;
+
+ MockIServiceProvider* sp_keeper;
+ CComPtr<IServiceProvider> sp;
+ ASSERT_HRESULT_SUCCEEDED(
+ InitializingCoClass<MockIServiceProvider>::CreateInitialized(
+ &sp_keeper, &sp));
+
+
+ EXPECT_CALL(*sp_keeper,
+ QueryService(IID_IDebugApplication, IID_IDebugApplication, _))
+ .WillOnce(Return(E_NOINTERFACE));
+
+ // Initialization with a service provider that yields
+ // no debug application should go the default route.
+ EXPECT_CALL(debug, CreateProcessDebugManager(_))
+ .WillOnce(
+ DoAll(CopyInterfaceToArgument<0>(debug_manager_),
+ Return(S_OK)));
+
+ EXPECT_CALL(*mock_debug_manager_, CreateApplication(_)).WillOnce(
+ DoAll(CopyInterfaceToArgument<0>(debug_application_), Return(S_OK)));
+ EXPECT_CALL(*mock_debug_application_, SetName(StrEq(kDebugApplicationName)))
+ .WillOnce(Return(S_OK));
+ EXPECT_CALL(*mock_debug_manager_, AddApplication(
+ static_cast<IDebugApplication*>(debug_application_), _))
+ .WillOnce(DoAll(SetArgumentPointee<1>(42), Return(S_OK)));
+
+ debug.Initialize(sp);
+
+ // And the registration should be undone.
+ EXPECT_CALL(*mock_debug_manager_, RemoveApplication(42))
+ .WillOnce(Return(S_OK));
+ debug.Terminate();
+}
+
+TEST_F(ScriptHostTest, DebugApplicationInitFailure) {
+ TestingDebugApplication debug;
+
+ EXPECT_CALL(debug, CreateProcessDebugManager(_))
+ .WillOnce(Return(REGDB_E_CLASSNOTREG));
+
+ debug.Initialize();
+
+ CComPtr<IDebugDocumentHelper> helper;
+ EXPECT_HRESULT_SUCCEEDED(
+ debug.CreateDebugDocumentHelper(kJsFilePath,
+ kJsCode,
+ 0,
+ &helper));
+ EXPECT_TRUE(helper == NULL);
+
+ // This must fail when debugging is not present.
+ CComPtr<IDebugApplication> debug_app;
+ EXPECT_HRESULT_FAILED(debug.GetDebugApplication(&debug_app));
+ ASSERT_TRUE(debug_app == NULL);
+
+ // Whereas this should succeed, but return a NULL node.
+ CComPtr<IDebugApplicationNode> root_node;
+ EXPECT_HRESULT_SUCCEEDED(debug.GetRootApplicationNode(&root_node));
+ ASSERT_TRUE(root_node == NULL);
+
+ debug.Terminate();
+}
+
+TEST_F(ScriptHostTest, QueryInterface) {
+ CreateScriptHost();
+
+ CComQIPtr<IActiveScriptSite> script_site(script_host_);
+ ASSERT_TRUE(script_site != NULL);
+
+ CComQIPtr<IActiveScriptSiteDebug> script_site_debug(script_host_);
+ ASSERT_TRUE(script_site_debug != NULL);
+}
+
+TEST_F(ScriptHostTest, RegisterScriptObject) {
+ CreateScriptHost();
+
+ MockIHTMLWindow2* mock_dispatch_obj;
+ CComPtr<IHTMLWindow2> dispatch_obj;
+ ASSERT_HRESULT_SUCCEEDED(
+ InitializingCoClass<MockIHTMLWindow2>::CreateInitialized(
+ &mock_dispatch_obj, &dispatch_obj));
+
+ // Add a non global script object.
+ EXPECT_CALL(*mock_script_, AddNamedItem(StrEq(kDispObjName), kAddFlags))
+ .WillOnce(Return(S_OK));
+ HRESULT hr = script_host_->RegisterScriptObject(kDispObjName, dispatch_obj,
+ false);
+ ASSERT_HRESULT_SUCCEEDED(hr);
+
+ // Add a global script object.
+ EXPECT_CALL(*mock_script_,
+ AddNamedItem(StrEq(kDispObjName2), kAddGlobalFlags))
+ .WillOnce(Return(S_OK));
+ hr = script_host_->RegisterScriptObject(kDispObjName2, dispatch_obj,
+ true);
+ ASSERT_HRESULT_SUCCEEDED(hr);
+
+ // Add a duplicate named object.
+ hr = script_host_->RegisterScriptObject(kDispObjName, dispatch_obj, false);
+ EXPECT_EQ(hr, E_ACCESSDENIED);
+}
+
+TEST_F(ScriptHostTest, RegisterScriptObjectAndGetItemInfo) {
+ CreateScriptHost();
+
+ MockDispatchEx* mock_dispatch_obj;
+ CComPtr<IDispatchEx> dispatch_obj;
+ ASSERT_HRESULT_SUCCEEDED(
+ InitializingCoClass<MockDispatchEx>::CreateInitialized(
+ &mock_dispatch_obj, &dispatch_obj));
+
+ // Add a non global script object.
+ EXPECT_CALL(*mock_script_, AddNamedItem(StrEq(kDispObjName), kAddFlags))
+ .WillOnce(Return(S_OK));
+ HRESULT hr = script_host_->RegisterScriptObject(kDispObjName, dispatch_obj,
+ false);
+ ASSERT_HRESULT_SUCCEEDED(hr);
+
+ // Make sure we return the object when it's called for.
+ EXPECT_CALL(*mock_dispatch_obj, GetTypeInfo(0, LANG_NEUTRAL, _))
+ .WillOnce(Return(S_OK));
+ CComQIPtr<IActiveScriptSite> script_site(script_host_);
+ ASSERT_TRUE(script_site != NULL);
+
+ CComPtr<IUnknown> item_iunknown;
+ CComPtr<ITypeInfo> item_itypeinfo;
+ hr = script_site->GetItemInfo(kDispObjName,
+ SCRIPTINFO_IUNKNOWN | SCRIPTINFO_ITYPEINFO,
+ &item_iunknown, &item_itypeinfo);
+ ASSERT_HRESULT_SUCCEEDED(hr);
+ EXPECT_EQ(dispatch_obj, item_iunknown);
+}
+
+TEST_F(ScriptHostTest, RunScript) {
+ CreateScriptHost();
+
+ MockDebugDocumentHelper* mock_debug_document;
+ ExpectCreateDebugDocumentHelper(&mock_debug_document);
+ ExpectParseScriptText(kSourceContext, kScriptFlags);
+
+ HRESULT hr = script_host_->RunScript(kJsFilePath, kJsCode);
+ ASSERT_HRESULT_SUCCEEDED(hr);
+}
+
+TEST_F(ScriptHostTest, RunExpression) {
+ CreateScriptHost();
+
+ ExpectParseScriptText(kNoSourceContext, kExpressionFlags);
+
+ CComVariant dummy;
+ HRESULT hr = script_host_->RunExpression(kJsCode, &dummy);
+ ASSERT_HRESULT_SUCCEEDED(hr);
+}
+
+TEST_F(ScriptHostTest, RunScriptAndGetDocumentContextFromPosition) {
+ CreateScriptHost();
+
+ MockDebugDocumentHelper* mock_debug_document;
+ ExpectCreateDebugDocumentHelper(&mock_debug_document);
+ ExpectParseScriptText(kSourceContext, kScriptFlags);
+
+ script_host_->RunScript(kJsFilePath, kJsCode);
+
+ CComQIPtr<IActiveScriptSiteDebug> script_site_debug(script_host_);
+ ASSERT_TRUE(script_site_debug != NULL);
+
+ EXPECT_CALL(*mock_debug_document,
+ GetScriptBlockInfo(kSourceContext, NULL, _, NULL))
+ .WillOnce(DoAll(SetArgumentPointee<2>(kCharOffset), Return(S_OK)));
+ EXPECT_CALL(*mock_debug_document,
+ CreateDebugDocumentContext(2 * kCharOffset, kNumChars, _))
+ .WillOnce(Return(S_OK));
+
+
+ // Call with a known source context.
+ CComPtr<IDebugDocumentContext> dummy_debug_document_context;
+ HRESULT hr = script_site_debug->GetDocumentContextFromPosition(
+ kSourceContext, kCharOffset, kNumChars, &dummy_debug_document_context);
+ ASSERT_HRESULT_SUCCEEDED(hr);
+
+ // Call with an unknown source context.
+ hr = script_site_debug->GetDocumentContextFromPosition(
+ kUnknownSourceContext, kCharOffset, kNumChars,
+ &dummy_debug_document_context);
+ ASSERT_TRUE(hr == E_FAIL);
+}
+
+} // namespace
diff --git a/ceee/ie/plugin/scripting/scripting.gyp b/ceee/ie/plugin/scripting/scripting.gyp
new file mode 100644
index 0000000..d7593e7
--- /dev/null
+++ b/ceee/ie/plugin/scripting/scripting.gyp
@@ -0,0 +1,94 @@
+# 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.
+
+{
+ 'variables': {
+ 'chromium_code': 1,
+ },
+ 'includes': [
+ '../../../../build/common.gypi',
+ '../../../../ceee/common.gypi',
+ ],
+ 'targets': [
+ {
+ 'target_name': 'scripting',
+ 'type': 'static_library',
+ 'dependencies': [
+ 'javascript_bindings',
+ '../../common/common.gyp:ie_common',
+ '../../common/common.gyp:ie_common_settings',
+ '../toolband/toolband.gyp:toolband_idl',
+ '../../../../base/base.gyp:base',
+ '../../../../ceee/common/common.gyp:ceee_common',
+ ],
+ 'sources': [
+ 'base.js',
+ 'ceee_bootstrap.js',
+ 'json.js',
+ 'content_script_manager.cc',
+ 'content_script_manager.h',
+ 'content_script_manager.rc',
+ 'content_script_native_api.cc',
+ 'content_script_native_api.h',
+ '../../common/precompile.cc',
+ '../../common/precompile.h',
+ 'script_host.cc',
+ 'script_host.h',
+ 'userscripts_librarian.cc',
+ 'userscripts_librarian.h',
+ 'userscripts_docs.h',
+ ],
+ 'configurations': {
+ 'Debug': {
+ 'msvs_precompiled_source': '../../common/precompile.cc',
+ 'msvs_precompiled_header': '../../common/precompile.h',
+ },
+ },
+ },
+ {
+ 'target_name': 'javascript_bindings',
+ 'type': 'none',
+ 'variables': {
+ 'chrome_renderer_path' : '../../../../chrome/renderer',
+ 'input_js_files': [
+ '<(chrome_renderer_path)/resources/event_bindings.js',
+ '<(chrome_renderer_path)/resources/renderer_extension_bindings.js',
+ ],
+ 'output_js_files': [
+ '<(SHARED_INTERMEDIATE_DIR)/event_bindings.js',
+ '<(SHARED_INTERMEDIATE_DIR)/renderer_extension_bindings.js',
+ ],
+ },
+ 'sources': [
+ 'transform_native_js.py',
+ '<@(input_js_files)',
+ ],
+ 'actions': [
+ {
+ 'action_name': 'transform_native_js',
+ 'msvs_cygwin_shell': 0,
+ 'msvs_quote_cmd': 0,
+ 'inputs': [
+ '<@(_sources)',
+ ],
+ 'outputs': [
+ '<@(output_js_files)',
+ ],
+ 'action': [
+ '<@(python)',
+ 'transform_native_js.py',
+ '<@(input_js_files)',
+ '-o',
+ '<(SHARED_INTERMEDIATE_DIR)',
+ ],
+ },
+ ],
+ # Make sure our dependents can refer to the transformed
+ # files from their .rc file(s).
+ 'direct_dependent_settings': {
+ 'resource_include_dirs': ['<(SHARED_INTERMEDIATE_DIR)'],
+ },
+ },
+ ]
+}
diff --git a/ceee/ie/plugin/scripting/transform_native_js.py b/ceee/ie/plugin/scripting/transform_native_js.py
new file mode 100644
index 0000000..b77ffc7
--- /dev/null
+++ b/ceee/ie/plugin/scripting/transform_native_js.py
@@ -0,0 +1,53 @@
+# 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.
+'''A GYP script action file to transform native JS function declarations.
+
+Any line in the input script of the form
+ "native function <NAME>(<ARGS>);"
+will be transformed to
+ "var <NAME> = ceee.<NAME>; // function(<ARGS>)"
+'''
+
+import optparse
+import os.path
+import re
+import sys
+
+_NATIVE_FUNCTION_RE = re.compile(
+ 'native\s+function\s+(?P<name>\w+)\((?P<args>[^)]*)\)\s*;')
+
+
+def TransformFile(input_file, output_file):
+ '''Transform native functions in input_file, write to output_file'''
+ # Slurp the input file.
+ contents = open(input_file, "r").read()
+
+ repl = 'var \g<name> = ceee.\g<name>; // function(\g<args>)'
+ contents = _NATIVE_FUNCTION_RE.sub(repl, contents,)
+
+ # Write the output file.
+ open(output_file, "w").write(contents)
+
+
+def GetOptionParser():
+ parser = optparse.OptionParser(description=__doc__)
+ parser.add_option('-o', dest='output_dir',
+ help='Output directory')
+ return parser
+
+def Main():
+ parser = GetOptionParser()
+ (opts, args) = parser.parse_args()
+ if not opts.output_dir:
+ parser.error('You must provide an output directory')
+
+ for input_file in args:
+ output_file = os.path.join(opts.output_dir, os.path.basename(input_file))
+ TransformFile(input_file, output_file)
+
+ return 0
+
+
+if __name__ == '__main__':
+ sys.exit(Main())
diff --git a/ceee/ie/plugin/scripting/userscripts_docs.h b/ceee/ie/plugin/scripting/userscripts_docs.h
new file mode 100644
index 0000000..c9aded9
--- /dev/null
+++ b/ceee/ie/plugin/scripting/userscripts_docs.h
@@ -0,0 +1,66 @@
+// 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_SCRIPTING_USERSCRIPTS_DOCS_H_ // Mainly for lint
+#define CEEE_IE_PLUGIN_SCRIPTING_USERSCRIPTS_DOCS_H_
+
+/** @page UserScriptsDoc Detailed documentation of the UserScriptsLibrarian.
+
+@section UserScriptsLibrarianIntro Introduction
+
+We need to be able to load the user scripts information (including url pattern
+matching info) from an extension manifest file and make it available for
+insertion in a WEB page as needed.
+
+@section IE
+
+We already have a class (ExtensionManifest) that reads information from a
+manifest file that was first needed to load the toolstrip info. So we need to
+add code in there to load the information related to user scripts.
+
+Since this class has a one to one relationship with a manifest file, we need
+another one to hold on all of the scripts from all the extensions that we will
+eventually read from and make them accessible to the page API to load the ones
+that match the current URL.
+
+So this other class (UserScriptsLibrarian) has a method to add UserScripts and
+to retrieve the CSS and JavaScript content of the ones that match a given URL.
+It reuses the UserScript object.
+
+TODO(siggi@chromium.org): Unbranch this code; a lot of it was taken
+from (and adapted) the UserScriptSlave class which is used to apply
+user scripts to a given WebKit frame.
+
+Our version simply returns the JavaScript code and CSS as text and let the
+PageApi class take care of the insertion in the browser script engine.
+
+The PageAPI code must react to the earliest event after the creation of the
+HTML document so that we can inject the CSS content before the page is rendered.
+For the user script, we have not yet found a way to inject the scripts that
+specify the start location.
+
+@section Firefox
+
+TODO: The implementation has changed, so we should update this doc.
+
+@section IEFF For both IE and Firefox
+
+The JavaScript code returned by the functions extracting them from the user
+scripts contain the proper heading to emulate the Greasemonkey API
+(taken from greasemonkey_api.js) for running scripts marked as stand alone
+(i.e., an extension without a public key), and it also wraps them
+individually within an anonymous function.
+
+The code taking care of injecting the code in chromium (in UserScriptSlave)
+concatenate all the scripts of a dictionary entry in the
+manifest file, and executes them in a separate context.
+@note However, this is being changed: http://crbug.com/22110.
+
+We don't have this capacity yet but the code must be written in a way that it
+will be easy to add this later on, so the methods returning the script code
+should only concatenate together the scripts of a single dictionary entry at a
+time. The CSS styles can all be bundled up in a single string though.
+**/
+
+#endif // CEEE_IE_PLUGIN_SCRIPTING_USERSCRIPTS_DOCS_H_
diff --git a/ceee/ie/plugin/scripting/userscripts_librarian.cc b/ceee/ie/plugin/scripting/userscripts_librarian.cc
new file mode 100644
index 0000000..adeb4e3
--- /dev/null
+++ b/ceee/ie/plugin/scripting/userscripts_librarian.cc
@@ -0,0 +1,119 @@
+// 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.
+//
+// Utility class to manage user scripts.
+
+#include "ceee/ie/plugin/scripting/userscripts_librarian.h"
+
+#include "base/file_util.h"
+#include "base/logging.h"
+#include "base/string_util.h"
+
+UserScriptsLibrarian::UserScriptsLibrarian() {
+}
+
+UserScriptsLibrarian::~UserScriptsLibrarian() {
+}
+
+HRESULT UserScriptsLibrarian::AddUserScripts(
+ const UserScriptList& user_scripts) {
+ // Note that we read the userscript files from disk every time that a frame
+ // loads (one BHO and one librarian per frame). This offers us the advantage
+ // of not having to reload scripts into memory when an extension autoupdates
+ // in Chrome. If we decide to cache these scripts in memory, then we will need
+ // a Chrome extension autoupdate automation notification.
+
+ size_t i = user_scripts_.size();
+ user_scripts_.insert(user_scripts_.end(), user_scripts.begin(),
+ user_scripts.end());
+ for (; i < user_scripts_.size(); ++i) {
+ UserScript& user_script = user_scripts_[i];
+ UserScript::FileList& js_scripts = user_script.js_scripts();
+ LoadFiles(&js_scripts);
+ UserScript::FileList& css_scripts = user_script.css_scripts();
+ LoadFiles(&css_scripts);
+ }
+
+ return S_OK;
+}
+
+bool UserScriptsLibrarian::HasMatchingUserScripts(const GURL& url) const {
+ for (size_t i = 0; i < user_scripts_.size(); ++i) {
+ const UserScript& user_script = user_scripts_[i];
+ if (user_script.MatchesUrl(url))
+ return true;
+ }
+ return false;
+}
+
+HRESULT UserScriptsLibrarian::GetMatchingUserScriptsCssContent(
+ const GURL& url, bool require_all_frames, std::string* css_content) const {
+ DCHECK(css_content);
+ if (!css_content)
+ return E_POINTER;
+
+ for (size_t i = 0; i < user_scripts_.size(); ++i) {
+ const UserScript& user_script = user_scripts_[i];
+ if (!user_script.MatchesUrl(url) ||
+ (require_all_frames && !user_script.match_all_frames()))
+ continue;
+
+ for (size_t j = 0; j < user_script.css_scripts().size(); ++j) {
+ const UserScript::File& file = user_script.css_scripts()[j];
+ *css_content += file.GetContent().as_string();
+ }
+ }
+
+ return S_OK;
+}
+
+HRESULT UserScriptsLibrarian::GetMatchingUserScriptsJsContent(
+ const GURL& url, UserScript::RunLocation location, bool require_all_frames,
+ JsFileList* js_file_list) {
+ DCHECK(js_file_list);
+ if (!js_file_list)
+ return E_POINTER;
+
+ if (!user_scripts_.empty()) {
+ for (size_t i = 0; i < user_scripts_.size(); ++i) {
+ const UserScript& user_script = user_scripts_[i];
+
+ // TODO(ericdingle@chromium.org): Remove the fourth and fifth
+ // conditions once DOCUMENT_IDLE is supported.
+ if (!user_script.MatchesUrl(url) ||
+ (require_all_frames && !user_script.match_all_frames()) ||
+ user_script.run_location() != location &&
+ (user_script.run_location() != UserScript::DOCUMENT_IDLE ||
+ location != UserScript::DOCUMENT_END))
+ continue;
+
+ for (size_t j = 0; j < user_script.js_scripts().size(); ++j) {
+ const UserScript::File& file = user_script.js_scripts()[j];
+
+ js_file_list->push_back(JsFile());
+ JsFile& js_file = (*js_file_list)[js_file_list->size()-1];
+ js_file.file_path =
+ file.extension_root().Append(file.relative_path()).value();
+ js_file.content = file.GetContent().as_string();
+ }
+ }
+ }
+
+ return S_OK;
+}
+
+void UserScriptsLibrarian::LoadFiles(UserScript::FileList* file_list) {
+ for (size_t i = 0; i < file_list->size(); ++i) {
+ UserScript::File& script_file = (*file_list)[i];
+ // The content may have been set manually (e.g., for unittests).
+ // So we first check if they need to be loaded from the file, or not.
+ if (script_file.GetContent().empty()) {
+ std::string content;
+ file_util::ReadFileToString(
+ script_file.extension_root().Append(
+ script_file.relative_path()), &content);
+ script_file.set_content(content);
+ }
+ }
+}
diff --git a/ceee/ie/plugin/scripting/userscripts_librarian.h b/ceee/ie/plugin/scripting/userscripts_librarian.h
new file mode 100644
index 0000000..e554a08
--- /dev/null
+++ b/ceee/ie/plugin/scripting/userscripts_librarian.h
@@ -0,0 +1,73 @@
+// 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.
+//
+// Utility class to maintain user scripts and their matching URLs.
+
+#ifndef CEEE_IE_PLUGIN_SCRIPTING_USERSCRIPTS_LIBRARIAN_H_
+#define CEEE_IE_PLUGIN_SCRIPTING_USERSCRIPTS_LIBRARIAN_H_
+
+#include <windows.h>
+
+#include <string>
+#include <vector>
+
+#include "chrome/common/extensions/user_script.h"
+#include "googleurl/src/gurl.h"
+
+
+// The manager of UserScripts responsible for loading them from an extension
+// and then returning the ones that match a given URL.
+// TODO(mad@chromium.org): Find a way to reuse code from
+// chrome/renderer/user_script_slave.
+class UserScriptsLibrarian {
+ public:
+ typedef struct {
+ std::wstring file_path;
+ std::string content;
+ } JsFile;
+ typedef std::vector<JsFile> JsFileList;
+
+ UserScriptsLibrarian();
+ ~UserScriptsLibrarian();
+
+ // Adds a list of users scripts to our current list.
+ HRESULT AddUserScripts(const UserScriptList& user_scripts);
+
+ // Identifies if we have any userscript that would match the given URL.
+ bool HasMatchingUserScripts(const GURL& url) const;
+
+ // Retrieve the CSS content from user scripts that match the given URL.
+ // @param url The URL to match.
+ // @param require_all_frames Whether to require the all_frames property of the
+ // user script to be true.
+ // @param css_content The single stream of CSS content.
+ HRESULT GetMatchingUserScriptsCssContent(
+ const GURL& url,
+ bool require_all_frames,
+ std::string* css_content) const;
+
+ // Retrieve the JS content from user scripts that match the given URL.
+ // @param url The URL to match.
+ // @param location The location where the scripts will be run at.
+ // @param require_all_frames Whether to require the all_frames property of the
+ // user script to be true.
+ // @param js_file_list A map of file names to JavaScript content to allow the
+ // caller to apply them individually (e.g., each with their own script
+ // engine).
+ HRESULT GetMatchingUserScriptsJsContent(
+ const GURL& url,
+ UserScript::RunLocation location,
+ bool require_all_frames,
+ JsFileList* js_file_list);
+
+ private:
+ // A helper function to load the content of a script file if it has not
+ // already been done, or explicitly set.
+ void LoadFiles(UserScript::FileList* file_list);
+
+ UserScriptList user_scripts_;
+ DISALLOW_COPY_AND_ASSIGN(UserScriptsLibrarian);
+};
+
+#endif // CEEE_IE_PLUGIN_SCRIPTING_USERSCRIPTS_LIBRARIAN_H_
diff --git a/ceee/ie/plugin/scripting/userscripts_librarian_unittest.cc b/ceee/ie/plugin/scripting/userscripts_librarian_unittest.cc
new file mode 100644
index 0000000..0a3a22b
--- /dev/null
+++ b/ceee/ie/plugin/scripting/userscripts_librarian_unittest.cc
@@ -0,0 +1,332 @@
+// 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.
+//
+// Unit tests for ExtensionManifest.
+
+#include <atlconv.h>
+
+#include "base/file_path.h"
+#include "base/file_util.h"
+#include "base/json/json_writer.h"
+#include "base/logging.h"
+#include "base/values.h"
+#include "ceee/ie/plugin/scripting/userscripts_librarian.h"
+#include "ceee/testing/utils/test_utils.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+namespace {
+
+const char kExtensionId[] = "abcdefghijklmnopqabcdefghijklmno";
+const char kCssContent[] = "body:after {content: \"The End\";}";
+const char kJsContent[] = "alert('red');";
+const char kUrlPattern1[] = "http://madlymad.com/*";
+const char kUrlPattern2[] = "https://superdave.com/*";
+const char kUrl1[] = "http://madlymad.com/index.html";
+const char kUrl2[] = "https://superdave.com/here.blue";
+const char kUrl3[] = "http://not.here.com/there.where";
+const wchar_t kJsPath1[] = L"script1.js";
+const wchar_t kJsPath2[] = L"script2.js";
+const wchar_t kCssFileName[] = L"CssFile.css";
+
+TEST(UserScriptsLibrarianTest, Empty) {
+ testing::LogDisabler no_dchecks;
+
+ UserScriptsLibrarian librarian;
+ librarian.AddUserScripts(UserScriptList());
+
+ std::string css;
+ EXPECT_HRESULT_SUCCEEDED(librarian.GetMatchingUserScriptsCssContent(
+ GURL(), true, &css));
+ EXPECT_TRUE(css.empty());
+
+ UserScriptsLibrarian::JsFileList js;
+ EXPECT_HRESULT_SUCCEEDED(librarian.GetMatchingUserScriptsJsContent(
+ GURL(), UserScript::DOCUMENT_START, true, &js));
+ EXPECT_TRUE(js.empty());
+}
+
+TEST(UserScriptsLibrarianTest, SingleScript) {
+ testing::LogDisabler no_dchecks;
+
+ URLPattern url_pattern(UserScript::kValidUserScriptSchemes);
+ url_pattern.Parse(kUrlPattern1);
+
+ UserScript user_script;
+ user_script.add_url_pattern(url_pattern);
+ user_script.set_extension_id(kExtensionId);
+ user_script.set_run_location(UserScript::DOCUMENT_START);
+
+ user_script.css_scripts().push_back(UserScript::File());
+ UserScript::File& css_file = user_script.css_scripts().back();
+ css_file.set_content(kCssContent);
+
+ user_script.js_scripts().push_back(UserScript::File());
+ UserScript::File& js_file = user_script.js_scripts().back();
+ js_file.set_content(kJsContent);
+
+ UserScriptList user_script_list;
+ user_script_list.push_back(user_script);
+
+ // Set up the librarian.
+ UserScriptsLibrarian librarian;
+ librarian.AddUserScripts(user_script_list);
+
+ // Matching URL
+ std::string css_content;
+ EXPECT_HRESULT_SUCCEEDED(librarian.GetMatchingUserScriptsCssContent(
+ GURL(kUrl1), false, &css_content));
+ EXPECT_STREQ(kCssContent, css_content.c_str());
+
+ // Non matching URL
+ css_content.clear();
+ EXPECT_HRESULT_SUCCEEDED(librarian.GetMatchingUserScriptsCssContent(
+ GURL(kUrl2), false, &css_content));
+ EXPECT_TRUE(css_content.empty());
+
+ // Matching URL and run location.
+ UserScriptsLibrarian::JsFileList js_content_list;
+ EXPECT_HRESULT_SUCCEEDED(librarian.GetMatchingUserScriptsJsContent(
+ GURL(kUrl1), UserScript::DOCUMENT_START, false, &js_content_list));
+ ASSERT_EQ(1, js_content_list.size());
+ EXPECT_STREQ(kJsContent, js_content_list[0].content.c_str());
+
+ // Matching URL and non matching run location.
+ js_content_list.clear();
+ EXPECT_HRESULT_SUCCEEDED(librarian.GetMatchingUserScriptsJsContent(
+ GURL(kUrl1), UserScript::DOCUMENT_END, false, &js_content_list));
+ EXPECT_TRUE(js_content_list.empty());
+
+ // Non matching URL and matching run location.
+ js_content_list.clear();
+ EXPECT_HRESULT_SUCCEEDED(librarian.GetMatchingUserScriptsJsContent(
+ GURL(kUrl2), UserScript::DOCUMENT_START, false, &js_content_list));
+ EXPECT_TRUE(js_content_list.empty());
+
+ // Non matching URL and non matching run location.
+ js_content_list.clear();
+ EXPECT_HRESULT_SUCCEEDED(librarian.GetMatchingUserScriptsJsContent(
+ GURL(kUrl2), UserScript::DOCUMENT_END, false, &js_content_list));
+ EXPECT_TRUE(js_content_list.empty());
+}
+
+TEST(UserScriptsLibrarianTest, MultipleScript) {
+ testing::LogDisabler no_dchecks;
+
+ // Set up one UserScript object
+ URLPattern url_pattern1(UserScript::kValidUserScriptSchemes);
+ url_pattern1.Parse(kUrlPattern1);
+
+ UserScript user_script1;
+ user_script1.add_url_pattern(url_pattern1);
+ user_script1.set_extension_id(kExtensionId);
+
+ user_script1.css_scripts().push_back(UserScript::File());
+ UserScript::File& css_file = user_script1.css_scripts().back();
+ css_file.set_content(kCssContent);
+
+ user_script1.js_scripts().push_back(UserScript::File());
+ UserScript::File& js_file = user_script1.js_scripts().back();
+ js_file.set_content(kJsContent);
+
+ UserScriptList user_script_list1;
+ user_script_list1.push_back(user_script1);
+
+ // Set up a second UserScript object
+ URLPattern url_pattern2(UserScript::kValidUserScriptSchemes);
+ url_pattern2.Parse(kUrlPattern2);
+
+ UserScript user_script2;
+ user_script2.add_url_pattern(url_pattern2);
+ user_script2.set_extension_id(kExtensionId);
+
+ user_script2.js_scripts().push_back(UserScript::File());
+ UserScript::File& js_file2 = user_script2.js_scripts().back();
+ js_file2.set_content(kJsContent);
+
+ user_script_list1.push_back(user_script2);
+
+ // Set up the librarian.
+ UserScriptsLibrarian librarian;
+ librarian.AddUserScripts(user_script_list1);
+
+ // Matching URL
+ std::string css_content;
+ EXPECT_HRESULT_SUCCEEDED(librarian.GetMatchingUserScriptsCssContent(
+ GURL(kUrl1), false, &css_content));
+ EXPECT_STREQ(css_content.c_str(), kCssContent);
+
+ // Matching URL and non matching location
+ UserScriptsLibrarian::JsFileList js_content_list;
+ EXPECT_HRESULT_SUCCEEDED(librarian.GetMatchingUserScriptsJsContent(
+ GURL(kUrl1), UserScript::DOCUMENT_START, false, &js_content_list));
+ EXPECT_TRUE(js_content_list.empty());
+
+ // Matching URL and location
+ js_content_list.clear();
+ EXPECT_HRESULT_SUCCEEDED(librarian.GetMatchingUserScriptsJsContent(
+ GURL(kUrl1), UserScript::DOCUMENT_END, false, &js_content_list));
+ ASSERT_EQ(1, js_content_list.size());
+ EXPECT_STREQ(kJsContent, js_content_list[0].content.c_str());
+
+ // Matching URL and location, shouldn't have extension init
+ js_content_list.clear();
+ EXPECT_HRESULT_SUCCEEDED(librarian.GetMatchingUserScriptsJsContent(
+ GURL(kUrl2), UserScript::DOCUMENT_END, false, &js_content_list));
+ ASSERT_EQ(1, js_content_list.size());
+ EXPECT_STREQ(kJsContent, js_content_list[0].content.c_str());
+}
+
+TEST(UserScriptsLibrarianTest, SingleScriptFromFile) {
+ testing::LogDisabler no_dchecks;
+
+ URLPattern url_pattern(UserScript::kValidUserScriptSchemes);
+ url_pattern.Parse(kUrlPattern1);
+
+ UserScript user_script;
+ user_script.add_url_pattern(url_pattern);
+ user_script.set_extension_id(kExtensionId);
+ user_script.set_run_location(UserScript::DOCUMENT_START);
+
+ FilePath extension_folder;
+ EXPECT_TRUE(file_util::GetTempDir(&extension_folder));
+ FilePath css_path(extension_folder.Append(kCssFileName));
+
+ user_script.css_scripts().push_back(UserScript::File(
+ extension_folder, FilePath(kCssFileName), GURL()));
+
+ FILE* temp_file = file_util::OpenFile(css_path, "w");
+ EXPECT_TRUE(temp_file != NULL);
+
+ fwrite(kCssContent, ::strlen(kCssContent), 1, temp_file);
+ file_util::CloseFile(temp_file);
+ temp_file = NULL;
+
+ UserScriptList user_script_list;
+ user_script_list.push_back(user_script);
+
+ UserScriptsLibrarian librarian;
+ librarian.AddUserScripts(user_script_list);
+
+ EXPECT_TRUE(file_util::Delete(css_path, false));
+
+ std::string css_content;
+ EXPECT_HRESULT_SUCCEEDED(librarian.GetMatchingUserScriptsCssContent(
+ GURL(kUrl1), false, &css_content));
+ EXPECT_STREQ(kCssContent, css_content.c_str());
+
+ UserScriptsLibrarian::JsFileList js_content_list;
+ EXPECT_HRESULT_SUCCEEDED(librarian.GetMatchingUserScriptsJsContent(
+ GURL(kUrl1), UserScript::DOCUMENT_START, false, &js_content_list));
+ EXPECT_EQ(0, js_content_list.size());
+}
+
+TEST(UserScriptsLibrarianTest, MatchAllFramesFalse) {
+ // all_frames is set to false. We should only get user scripts back
+ // when we pass false for require_all_frames to GetMatching*.
+ testing::LogDisabler no_dchecks;
+
+ URLPattern url_pattern(UserScript::kValidUserScriptSchemes);
+ url_pattern.Parse(kUrlPattern1);
+
+ UserScript user_script;
+ user_script.add_url_pattern(url_pattern);
+ user_script.set_extension_id(kExtensionId);
+ user_script.set_run_location(UserScript::DOCUMENT_START);
+
+ user_script.css_scripts().push_back(UserScript::File());
+ UserScript::File& css_file = user_script.css_scripts().back();
+ css_file.set_content(kCssContent);
+
+ user_script.js_scripts().push_back(UserScript::File());
+ UserScript::File& js_file = user_script.js_scripts().back();
+ js_file.set_content(kJsContent);
+
+ UserScriptList user_script_list;
+ user_script_list.push_back(user_script);
+
+ UserScriptsLibrarian librarian;
+ librarian.AddUserScripts(user_script_list);
+
+ // Get CSS with require_all_frames set to false.
+ std::string css_content;
+ EXPECT_HRESULT_SUCCEEDED(librarian.GetMatchingUserScriptsCssContent(
+ GURL(kUrl1), false, &css_content));
+ EXPECT_STREQ(kCssContent, css_content.c_str());
+
+ // Get CSS with require_all_frames set to true.
+ css_content.clear();
+ EXPECT_HRESULT_SUCCEEDED(librarian.GetMatchingUserScriptsCssContent(
+ GURL(kUrl1), true, &css_content));
+ EXPECT_TRUE(css_content.empty());
+
+ // Get JS with require_all_frames set to false.
+ UserScriptsLibrarian::JsFileList js_content_list;
+ EXPECT_HRESULT_SUCCEEDED(librarian.GetMatchingUserScriptsJsContent(
+ GURL(kUrl1), UserScript::DOCUMENT_START, false, &js_content_list));
+ ASSERT_EQ(1, js_content_list.size());
+ EXPECT_STREQ(kJsContent, js_content_list[0].content.c_str());
+
+ // Get JS with require_all_frames set to true.
+ js_content_list.clear();
+ EXPECT_HRESULT_SUCCEEDED(librarian.GetMatchingUserScriptsJsContent(
+ GURL(kUrl1), UserScript::DOCUMENT_START, true, &js_content_list));
+ EXPECT_TRUE(js_content_list.empty());
+}
+
+TEST(UserScriptsLibrarianTest, MatchAllFramesTrue) {
+ // all_frames is set to true. We should get user scripts back
+ // when we pass any value for require_all_frames to GetMatching*.
+ testing::LogDisabler no_dchecks;
+
+ URLPattern url_pattern(UserScript::kValidUserScriptSchemes);
+ url_pattern.Parse(kUrlPattern1);
+
+ UserScript user_script;
+ user_script.add_url_pattern(url_pattern);
+ user_script.set_extension_id(kExtensionId);
+ user_script.set_run_location(UserScript::DOCUMENT_START);
+ user_script.set_match_all_frames(true);
+
+ user_script.css_scripts().push_back(UserScript::File());
+ UserScript::File& css_file = user_script.css_scripts().back();
+ css_file.set_content(kCssContent);
+
+ user_script.js_scripts().push_back(UserScript::File());
+ UserScript::File& js_file = user_script.js_scripts().back();
+ js_file.set_content(kJsContent);
+
+ UserScriptList user_script_list;
+ user_script_list.push_back(user_script);
+
+ UserScriptsLibrarian librarian;
+ librarian.AddUserScripts(user_script_list);
+
+ // Get CSS with require_all_frames set to false.
+ std::string css_content;
+ EXPECT_HRESULT_SUCCEEDED(librarian.GetMatchingUserScriptsCssContent(
+ GURL(kUrl1), false, &css_content));
+ EXPECT_STREQ(kCssContent, css_content.c_str());
+
+ // Get CSS with require_all_frames set to true.
+ css_content.clear();
+ EXPECT_HRESULT_SUCCEEDED(librarian.GetMatchingUserScriptsCssContent(
+ GURL(kUrl1), true, &css_content));
+ EXPECT_STREQ(kCssContent, css_content.c_str());
+
+ // Get JS with require_all_frames set to false.
+ UserScriptsLibrarian::JsFileList js_content_list;
+ EXPECT_HRESULT_SUCCEEDED(librarian.GetMatchingUserScriptsJsContent(
+ GURL(kUrl1), UserScript::DOCUMENT_START, false, &js_content_list));
+ ASSERT_EQ(1, js_content_list.size());
+ EXPECT_STREQ(kJsContent, js_content_list[0].content.c_str());
+
+ // Get JS with require_all_frames set to true.
+ js_content_list.clear();
+ EXPECT_HRESULT_SUCCEEDED(librarian.GetMatchingUserScriptsJsContent(
+ GURL(kUrl1), UserScript::DOCUMENT_START, true, &js_content_list));
+ ASSERT_EQ(1, js_content_list.size());
+ EXPECT_STREQ(kJsContent, js_content_list[0].content.c_str());
+}
+
+} // namespace
diff --git a/ceee/ie/plugin/toolband/resource.h b/ceee/ie/plugin/toolband/resource.h
new file mode 100644
index 0000000..d090e40
--- /dev/null
+++ b/ceee/ie/plugin/toolband/resource.h
@@ -0,0 +1,35 @@
+// 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.
+//
+// Resource constants for CEEE toolband.
+#ifndef CEEE_IE_PLUGIN_TOOLBAND_RESOURCE_H_
+#define CEEE_IE_PLUGIN_TOOLBAND_RESOURCE_H_
+
+
+//{{NO_DEPENDENCIES}}
+// Microsoft Visual C++ generated include file.
+// Used by toolband.rc
+//
+#define IDS_PROJNAME 100
+#define IDR_IE 101
+#define IDR_BROWSERHELPEROBJECT 102
+#define IDR_TOOL_BAND 103
+#define IDR_GREASEMONKEY_API_JS 105
+#define IDR_EXECUTOR 106
+#define IDR_EXECUTOR_CREATOR 107
+#define IDR_NO_EXTENSION 108
+
+// Next default values for new objects
+//
+#ifdef APSTUDIO_INVOKED
+#ifndef APSTUDIO_READONLY_SYMBOLS
+#define _APS_NEXT_RESOURCE_VALUE 109
+#define _APS_NEXT_COMMAND_VALUE 32768
+#define _APS_NEXT_CONTROL_VALUE 201
+#define _APS_NEXT_SYMED_VALUE 109
+#endif
+#endif
+
+
+#endif // CEEE_IE_PLUGIN_TOOLBAND_RESOURCE_H_
diff --git a/ceee/ie/plugin/toolband/tool_band.cc b/ceee/ie/plugin/toolband/tool_band.cc
new file mode 100644
index 0000000..b8dfcea
--- /dev/null
+++ b/ceee/ie/plugin/toolband/tool_band.cc
@@ -0,0 +1,628 @@
+// 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.
+//
+// IE toolband implementation.
+#include "ceee/ie/plugin/toolband/tool_band.h"
+
+#include <atlsafe.h>
+#include <atlstr.h>
+#include <shlguid.h>
+
+#include "base/debug/trace_event.h"
+#include "base/file_path.h"
+#include "base/logging.h"
+#include "base/string_util.h"
+#include "ceee/common/com_utils.h"
+#include "ceee/common/window_utils.h"
+#include "ceee/common/windows_constants.h"
+#include "ceee/ie/common/extension_manifest.h"
+#include "ceee/ie/common/ceee_module_util.h"
+#include "ceee/ie/plugin/bho/tool_band_visibility.h"
+#include "chrome/common/chrome_switches.h"
+#include "chrome/test/automation/automation_constants.h"
+#include "chrome_frame/com_message_event.h"
+
+_ATL_FUNC_INFO ToolBand::handler_type_idispatch_ =
+ { CC_STDCALL, VT_EMPTY, 1, { VT_DISPATCH } };
+_ATL_FUNC_INFO ToolBand::handler_type_long_ =
+ { CC_STDCALL, VT_EMPTY, 1, { VT_I4 } };
+_ATL_FUNC_INFO ToolBand::handler_type_idispatch_bstr_ =
+ { CC_STDCALL, VT_EMPTY, 2, { VT_DISPATCH, VT_BSTR } };
+_ATL_FUNC_INFO ToolBand::handler_type_bstr_i4_=
+ { CC_STDCALL, VT_EMPTY, 2, { VT_BSTR, VT_I4 } };
+_ATL_FUNC_INFO ToolBand::handler_type_bstrarray_=
+ { CC_STDCALL, VT_EMPTY, 1, { VT_ARRAY | VT_BSTR } };
+_ATL_FUNC_INFO ToolBand::handler_type_idispatch_variantref_ =
+ { CC_STDCALL, VT_EMPTY, 2, { VT_DISPATCH, VT_VARIANT | VT_BYREF } };
+
+ToolBand::ToolBand()
+ : already_tried_installing_(false),
+ own_line_flag_(false),
+ already_checked_own_line_flag_(false),
+ listening_to_browser_events_(false),
+ band_id_(0),
+ is_quitting_(false),
+ current_width_(64),
+ current_height_(25) {
+ TRACE_EVENT_BEGIN("ceee.toolband", this, "");
+}
+
+ToolBand::~ToolBand() {
+ TRACE_EVENT_END("ceee.toolband", this, "");
+}
+
+HRESULT ToolBand::FinalConstruct() {
+ return S_OK;
+}
+
+void ToolBand::FinalRelease() {
+}
+
+STDMETHODIMP ToolBand::SetSite(IUnknown* site) {
+ typedef IObjectWithSiteImpl<ToolBand> SuperSite;
+
+ // From experience we know the site may be set multiple times.
+ // Let's ignore second and subsequent set or unset.
+ if (NULL != site && NULL != m_spUnkSite.p ||
+ NULL == site && NULL == m_spUnkSite.p) {
+ // TODO(siggi@chromium.org) log this.
+ return S_OK;
+ }
+
+ if (NULL == site) {
+ // We're being torn down.
+ Teardown();
+ }
+
+ HRESULT hr = SuperSite::SetSite(site);
+ if (FAILED(hr))
+ return hr;
+
+ if (NULL != site) {
+ // We're being initialized.
+ hr = Initialize(site);
+
+ // Release the site in case of failure.
+ if (FAILED(hr))
+ SuperSite::SetSite(NULL);
+ }
+
+ return hr;
+}
+
+STDMETHODIMP ToolBand::ShowDW(BOOL show) {
+ ShowWindow(show ? SW_SHOW : SW_HIDE);
+ if (show) {
+ // Report that the toolband is being shown, so that the BHO
+ // knows it doesn't need to explicitly make it visible.
+ ToolBandVisibility::ReportToolBandVisible(web_browser_);
+ }
+ // Unless ShowDW changes are explicitly being ignored (e.g. if the
+ // BHO is forcing the toolband to be visible via
+ // ShowBrowserBar), or unless the toolband is closing on quit, then we assume
+ // a call to ShowDW reflects the user's toolband visibility choice, modifiable
+ // through the View -> Toolbars menu in IE. We track this choice here.
+ if (!ceee_module_util::GetIgnoreShowDWChanges() && !is_quitting_) {
+ ceee_module_util::SetOptionToolbandIsHidden(show == FALSE);
+ }
+ return S_OK;
+}
+
+STDMETHODIMP ToolBand::CloseDW(DWORD reserved) {
+ // Indicates to ShowDW() that the tool band is being closed, as opposed to
+ // being explicitly hidden by the user.
+ is_quitting_ = true;
+ return ShowDW(FALSE);
+}
+
+STDMETHODIMP ToolBand::ResizeBorderDW(LPCRECT border,
+ IUnknown* toolband_site,
+ BOOL reserved) {
+ DCHECK(FALSE); // Not used for toolbands.
+ return E_NOTIMPL;
+}
+
+STDMETHODIMP ToolBand::GetBandInfo(DWORD band_id,
+ DWORD view_mode,
+ DESKBANDINFO* deskband_info) {
+ band_id_ = band_id;
+
+ // We're only registered as a horizontal band.
+ DCHECK(view_mode == DBIF_VIEWMODE_NORMAL);
+
+ if (!deskband_info)
+ return E_POINTER;
+
+ if (deskband_info->dwMask & DBIM_MINSIZE) {
+ deskband_info->ptMinSize.x = current_width_;
+ deskband_info->ptMinSize.y = current_height_;
+ }
+
+ if (deskband_info->dwMask & DBIM_MAXSIZE) {
+ deskband_info->ptMaxSize.x = -1;
+ deskband_info->ptMaxSize.y = -1;
+ }
+
+ if (deskband_info->dwMask & DBIM_INTEGRAL) {
+ deskband_info->ptIntegral.x = 1;
+ deskband_info->ptIntegral.y = 1;
+ }
+
+ if (deskband_info->dwMask & DBIM_ACTUAL) {
+ // By not setting, we just use the default.
+ // deskband_info->ptActual.x = 7000;
+ deskband_info->ptActual.y = current_height_;
+ }
+
+ if (deskband_info->dwMask & DBIM_TITLE) {
+ // Title is empty.
+ deskband_info->wszTitle[0] = 0;
+ }
+
+ if (deskband_info->dwMask & DBIM_MODEFLAGS) {
+ deskband_info->dwModeFlags = DBIMF_NORMAL /* | DBIMF_TOPALIGN */;
+
+ if (ShouldForceOwnLine()) {
+ deskband_info->dwModeFlags |= DBIMF_BREAK;
+ }
+ }
+
+ if (deskband_info->dwMask & DBIM_BKCOLOR) {
+ // Use the default background color by removing this flag.
+ deskband_info->dwMask &= ~DBIM_BKCOLOR;
+ }
+ return S_OK;
+}
+
+STDMETHODIMP ToolBand::GetWindow(HWND* window) {
+ *window = m_hWnd;
+ return S_OK;
+}
+
+STDMETHODIMP ToolBand::ContextSensitiveHelp(BOOL enter_mode) {
+ LOG(INFO) << "ContextSensitiveHelp";
+ return E_NOTIMPL;
+}
+
+STDMETHODIMP ToolBand::GetClassID(CLSID* clsid) {
+ *clsid = GetObjectCLSID();
+ return S_OK;
+}
+
+STDMETHODIMP ToolBand::IsDirty() {
+ return S_FALSE; // Never dirty for now.
+}
+
+STDMETHODIMP ToolBand::Load(IStream* stream) {
+ return S_OK; // Loading is no-op.
+}
+
+STDMETHODIMP ToolBand::Save(IStream* stream, BOOL clear_dirty) {
+ return S_OK; // Saving is no-op.
+}
+
+STDMETHODIMP ToolBand::GetSizeMax(ULARGE_INTEGER* size) {
+ size->QuadPart = 0ULL; // We're frugal.
+ return S_OK;
+}
+
+STDMETHODIMP ToolBand::GetWantsPrivileged(boolean* wants_privileged) {
+ *wants_privileged = true;
+ return S_OK;
+}
+
+STDMETHODIMP ToolBand::GetChromeExtraArguments(BSTR* args) {
+ DCHECK(args);
+
+ // Extra arguments are passed on verbatim, so we add the -- prefix.
+ CComBSTR str = "--";
+ str.Append(switches::kEnableExperimentalExtensionApis);
+
+ *args = str.Detach();
+ return S_OK;
+}
+
+STDMETHODIMP ToolBand::GetChromeProfileName(BSTR* profile_name) {
+ *profile_name = ::SysAllocString(
+ ceee_module_util::GetBrokerProfileNameForIe());
+ return S_OK;
+}
+
+STDMETHODIMP ToolBand::GetExtensionApisToAutomate(BSTR* functions_enabled) {
+ *functions_enabled = NULL;
+ return S_FALSE;
+}
+
+HRESULT ToolBand::Initialize(IUnknown* site) {
+ TRACE_EVENT_INSTANT("ceee.toolband.initialize", this, "");
+
+ CComQIPtr<IServiceProvider> service_provider = site;
+ DCHECK(service_provider);
+ if (service_provider == NULL) {
+ return E_FAIL;
+ }
+
+ HRESULT hr = InitializeAndShowWindow(site);
+
+ if (FAILED(hr)) {
+ LOG(ERROR) << "Toolband failed to initalize its site window: " <<
+ com::LogHr(hr);
+ return hr;
+ }
+
+ // Store the web browser, used to report toolband visibility to
+ // the BHO. Also required to get navigate2 notification.
+ hr = service_provider->QueryService(
+ SID_SWebBrowserApp, IID_IWebBrowser2,
+ reinterpret_cast<void**>(&web_browser_));
+
+ DCHECK(SUCCEEDED(hr));
+
+ if (FAILED(hr)) {
+ LOG(ERROR) << "Failed to get web browser: 0x" << std::hex << hr;
+ return hr;
+ } else if (ShouldForceOwnLine()) {
+ // This may seem odd, but event subscription is required
+ // only to clear 'own line' flag later (see OnIeNavigateComplete2)
+ hr = HostingBrowserEvents::DispEventAdvise(web_browser_,
+ &DIID_DWebBrowserEvents2);
+ listening_to_browser_events_ = SUCCEEDED(hr);
+ DCHECK(SUCCEEDED(hr)) <<
+ "DispEventAdvise on web browser failed. Error: " << hr;
+ // Non-critical functionality. If fails in the field, just move on.
+ }
+
+ return S_OK;
+}
+
+HRESULT ToolBand::InitializeAndShowWindow(IUnknown* site) {
+ CComPtr<IOleWindow> site_window;
+ HRESULT hr = site->QueryInterface(&site_window);
+ if (FAILED(hr)) {
+ LOG(ERROR) << "Failed to get site window: " << com::LogHr(hr);
+ return hr;
+ }
+
+ DCHECK(NULL != site_window.p);
+ hr = site_window->GetWindow(&parent_window_.m_hWnd);
+ if (FAILED(hr)) {
+ LOG(ERROR) << "Failed to get parent window handle: " << com::LogHr(hr);
+ return hr;
+ }
+ DCHECK(parent_window_);
+ if (!parent_window_)
+ return E_FAIL;
+
+ if (NULL == Create(parent_window_))
+ return E_FAIL;
+
+ BOOL shown = ShowWindow(SW_SHOW);
+ DCHECK(shown);
+
+ return hr;
+}
+
+HRESULT ToolBand::Teardown() {
+ TRACE_EVENT_INSTANT("ceee.toolband.teardown", this, "");
+
+ if (IsWindow()) {
+ // Teardown the ActiveX host window.
+ CAxWindow host(m_hWnd);
+ CComPtr<IObjectWithSite> host_with_site;
+ HRESULT hr = host.QueryHost(&host_with_site);
+ if (SUCCEEDED(hr))
+ host_with_site->SetSite(NULL);
+
+ DestroyWindow();
+ }
+
+ if (chrome_frame_) {
+ ChromeFrameEvents::DispEventUnadvise(chrome_frame_);
+ }
+
+ if (web_browser_ && listening_to_browser_events_) {
+ HostingBrowserEvents::DispEventUnadvise(web_browser_,
+ &DIID_DWebBrowserEvents2);
+ }
+ listening_to_browser_events_ = false;
+
+ return S_OK;
+}
+
+void ToolBand::OnFinalMessage(HWND window) {
+ GetUnknown()->Release();
+}
+
+LRESULT ToolBand::OnCreate(LPCREATESTRUCT lpCreateStruct) {
+ // Grab a self-reference.
+ GetUnknown()->AddRef();
+
+ // Create a host window instance.
+ CComPtr<IAxWinHostWindow> host;
+ HRESULT hr = CAxHostWindow::CreateInstance(&host);
+ if (FAILED(hr)) {
+ LOG(ERROR) << "Failed to create ActiveX host window. " << com::LogHr(hr);
+ return 1;
+ }
+
+ // We're the site for the host window, this needs to be in place
+ // before we attach ChromeFrame to the ActiveX control window, so
+ // as to allow it to probe our service provider.
+ hr = SetChildSite(host);
+ DCHECK(SUCCEEDED(hr));
+
+ // Create the chrome frame instance.
+ hr = chrome_frame_.CoCreateInstance(L"ChromeTab.ChromeFrame");
+ if (FAILED(hr)) {
+ LOG(ERROR) << "Failed to create the Chrome Frame instance. " <<
+ com::LogHr(hr);
+ return 1;
+ }
+
+ // And attach it to our window.
+ hr = host->AttachControl(chrome_frame_, m_hWnd);
+ if (FAILED(hr)) {
+ LOG(ERROR) << "Failed to attach Chrome Frame to the host. " <<
+ com::LogHr(hr);
+ return 1;
+ }
+
+ // Hook up the chrome frame event listener.
+ hr = ChromeFrameEvents::DispEventAdvise(chrome_frame_);
+ if (FAILED(hr)) {
+ LOG(ERROR) << "Failed to hook up event sink. " << com::LogHr(hr);
+ }
+
+ return 0;
+}
+
+void ToolBand::OnPaint(CDCHandle dc) {
+ RECT rc = {};
+ if (GetUpdateRect(&rc, FALSE)) {
+ PAINTSTRUCT ps = {};
+ BeginPaint(&ps);
+
+ BOOL ret = GetClientRect(&rc);
+ DCHECK(ret);
+ CString text;
+ text.Format(L"Google CEEE. No Chrome Frame found. Instance: 0x%p. ID: %d!)",
+ this, band_id_);
+ ::DrawText(ps.hdc, text, -1, &rc, DT_SINGLELINE | DT_BOTTOM | DT_CENTER);
+
+ EndPaint(&ps);
+ }
+}
+
+void ToolBand::OnSize(UINT type, CSize size) {
+ LOG(INFO) << "ToolBand::OnSize(" << type << ", "
+ << size.cx << "x" << size.cy << ")";
+ CWindow chrome_window = ::GetWindow(m_hWnd, GW_CHILD);
+ if (!chrome_window) {
+ LOG(ERROR) << "Failed to retrieve Chrome Frame window";
+ return;
+ }
+
+ BOOL resized = chrome_window.ResizeClient(size.cx, size.cy);
+ DCHECK(resized);
+}
+
+STDMETHODIMP_(void) ToolBand::OnCfReadyStateChanged(LONG state) {
+ DLOG(INFO) << "OnCfReadyStateChanged(" << state << ")";
+
+ if (state == READYSTATE_COMPLETE) {
+ extension_path_ = ceee_module_util::GetExtensionPath();
+
+ if (ceee_module_util::IsCrxOrEmpty(extension_path_) &&
+ ceee_module_util::NeedToInstallExtension()) {
+ LOG(INFO) << "Installing extension: \"" << extension_path_ << "\"";
+ chrome_frame_->installExtension(CComBSTR(extension_path_.c_str()));
+ } else {
+ // In the case where we don't have a CRX (or we don't need to install it),
+ // we must ask for the currently enabled extension before we can decide
+ // what we need to do.
+ chrome_frame_->getEnabledExtensions();
+ }
+ }
+}
+
+STDMETHODIMP_(void) ToolBand::OnCfMessage(IDispatch* event) {
+ VARIANT origin = {VT_NULL};
+ HRESULT hr = event->Invoke(ComMessageEvent::DISPID_MESSAGE_EVENT_ORIGIN,
+ IID_NULL, 0, DISPATCH_PROPERTYGET, 0, &origin, 0, 0);
+ if (FAILED(hr) || origin.vt != VT_BSTR) {
+ DLOG(WARNING) << __FUNCTION__ << ": unable to discern message origin.";
+ return;
+ }
+
+ VARIANT data_bstr = {VT_NULL};
+ hr = event->Invoke(ComMessageEvent::DISPID_MESSAGE_EVENT_DATA,
+ IID_NULL, 0, DISPATCH_PROPERTYGET, 0, &data_bstr, 0, 0);
+ if (FAILED(hr) || data_bstr.vt != VT_BSTR) {
+ DLOG(INFO) << __FUNCTION__ << ": no message data. Origin:"
+ << origin.bstrVal;
+ return;
+ }
+ DLOG(INFO) << __FUNCTION__ << ": Origin: " << origin.bstrVal
+ << ", Data: " << data_bstr.bstrVal;
+ CString data(data_bstr);
+
+ // Handle CEEE-specific messages.
+ // TODO(skare@google.com): If we will need this for more than one
+ // message, consider making responses proper JSON.
+
+ // ceee_getCurrentWindowId: chrome.windows.getCurrent workaround.
+ CString message;
+ if (data == L"ceee_getCurrentWindowId") {
+ HWND browser_window = 0;
+ web_browser_->get_HWND(reinterpret_cast<long*>(&browser_window));
+ bool is_ieframe = window_utils::IsWindowClass(browser_window,
+ windows::kIeFrameWindowClass);
+ if (is_ieframe) {
+ message.Format(L"ceee_getCurrentWindowId %d", browser_window);
+ } else {
+ DCHECK(is_ieframe);
+ LOG(WARNING) << "Could not find IE Frame window.";
+ message = L"ceee_getCurrentWindowId -1";
+ }
+ }
+
+ if (!message.IsEmpty()) {
+ chrome_frame_->postMessage(CComBSTR(message), origin);
+ }
+}
+
+void ToolBand::StartExtension(const wchar_t* base_dir) {
+ if (!LoadManifestFile(base_dir, &extension_url_)) {
+ LOG(ERROR) << "No extension found";
+ } else {
+ HRESULT hr = chrome_frame_->put_src(CComBSTR(extension_url_.c_str()));
+ DCHECK(SUCCEEDED(hr));
+ LOG_IF(WARNING, FAILED(hr)) << "IChromeFrame::put_src returned: " <<
+ com::LogHr(hr);
+ }
+}
+
+STDMETHODIMP_(void) ToolBand::OnCfExtensionReady(BSTR path, int response) {
+ TRACE_EVENT_INSTANT("ceee.toolband.oncfextensionready", this, "");
+
+ if (ceee_module_util::IsCrxOrEmpty(extension_path_)) {
+ // If we get here, it's because we just did the first-time
+ // install, so save the installation path+time for future comparison.
+ ceee_module_util::SetInstalledExtensionPath(
+ FilePath(extension_path_));
+ }
+
+ // Now list enabled extensions so that we can properly start it whether
+ // it's a CRX file or an exploded folder.
+ //
+ // Note that we do this even if Chrome says installation failed,
+ // as that is the error code it uses when we try to install an
+ // older version of the extension than it already has, which happens
+ // on overinstall when Chrome has already auto-updated.
+ //
+ // If it turns out no extension is installed, we will handle that
+ // error in the OnCfGetEnabledExtensionsComplete callback.
+ chrome_frame_->getEnabledExtensions();
+}
+
+STDMETHODIMP_(void) ToolBand::OnCfGetEnabledExtensionsComplete(
+ SAFEARRAY* extension_directories) {
+ CComSafeArray<BSTR> directories;
+ directories.Attach(extension_directories); // MUST DETACH BEFORE RETURNING
+
+ // TODO(joi@chromium.org) Handle multiple extensions.
+ if (directories.GetCount() > 0) {
+ // If our extension_path is not a CRX, it MUST be the same as the installed
+ // extension path which would be an exploded extension.
+ // If you get this DCHECK, you may have changed your registry settings to
+ // debug with an exploded extension, but you didn't uninstall the previous
+ // extension, either via the Chrome UI or by simply wiping out your
+ // profile folder.
+ DCHECK(ceee_module_util::IsCrxOrEmpty(extension_path_) ||
+ extension_path_ == std::wstring(directories.GetAt(0)));
+ StartExtension(directories.GetAt(0));
+ } else if (!ceee_module_util::IsCrxOrEmpty(extension_path_)) {
+ // We have an extension path that isn't a CRX and we don't have any
+ // enabled extension, so we must load the exploded extension from this
+ // given path. WE MUST DO THIS BEFORE THE NEXT ELSE IF because it assumes
+ // a CRX file.
+ chrome_frame_->loadExtension(CComBSTR(extension_path_.c_str()));
+ } else if (!already_tried_installing_ && !extension_path_.empty()) {
+ // We attempt to install the .crx file from the CEEE folder; in the
+ // default case this will happen only once after installation.
+ // It may seem redundant with OnCfReadyStateChanged; this is in case the
+ // user deleted the extension but the registry stayed the same.
+ already_tried_installing_ = true;
+ chrome_frame_->installExtension(CComBSTR(extension_path_.c_str()));
+ } else {
+ // Hide the browser bar as fast as we can.
+ // Set the current height of the bar to 0, so that if the user manually
+ // shows the bar, it will not be visible on screen.
+ current_height_ = 0;
+
+ // Ask IE to reload all info for this toolband.
+ CComPtr<IOleCommandTarget> cmd_target;
+ HRESULT hr = GetSite(IID_IOleCommandTarget,
+ reinterpret_cast<void**>(&cmd_target));
+ if (SUCCEEDED(hr)) {
+ CComVariant band_id(static_cast<int>(band_id_));
+ hr = cmd_target->Exec(&CGID_DeskBand, DBID_BANDINFOCHANGED,
+ OLECMDEXECOPT_DODEFAULT, &band_id, NULL);
+ if (FAILED(hr)) {
+ LOG(ERROR) << "Failed to Execute DBID_BANDINFOCHANGED. Error Code: "
+ << com::LogHr(hr);
+ }
+ } else {
+ LOG(ERROR) << "Failed to obtain OleCommandTarget. Error Code: "
+ << com::LogHr(hr);
+ }
+ }
+ directories.Detach();
+}
+
+STDMETHODIMP_(void) ToolBand::OnIeNavigateComplete2(IDispatch* dispatch,
+ VARIANT* url) {
+ // The flag is cleared on navigation complete since at this point we are
+ // certain the process of placing the toolband has been completed.
+ // Doing it in GetBandInfo proved premature as many queries are expeced.
+ ClearForceOwnLineFlag();
+
+ // We need to clear that flag just once. Now that's done, unadvise.
+ DCHECK(web_browser_ != NULL);
+ if (web_browser_ && listening_to_browser_events_) {
+ HostingBrowserEvents::DispEventUnadvise(web_browser_,
+ &DIID_DWebBrowserEvents2);
+ listening_to_browser_events_ = false;
+ }
+}
+
+bool ToolBand::LoadManifestFile(const std::wstring& base_dir,
+ std::string* toolband_url) {
+ DCHECK(toolband_url);
+ FilePath toolband_extension_path;
+ toolband_extension_path = FilePath(base_dir);
+
+ if (toolband_extension_path.empty()) {
+ // Expected case if no extensions registered/found.
+ return false;
+ }
+
+ ExtensionManifest manifest;
+ HRESULT hr = manifest.ReadManifestFile(toolband_extension_path, true);
+ if (FAILED(hr)) {
+ LOG(ERROR) << "Failed to read manifest at \"" <<
+ toolband_extension_path.value() << "\", error " << com::LogHr(hr);
+ return false;
+ }
+
+ const std::vector<std::string>& toolstrip_names(
+ manifest.GetToolstripFileNames());
+ if (!toolstrip_names.empty()) {
+ *toolband_url = "chrome-extension://";
+ *toolband_url += manifest.extension_id();
+ *toolband_url += "/";
+ // TODO(mad@chromium.org): For now we only load the first one we
+ // find, we may want to stack them at one point...
+ *toolband_url += toolstrip_names[0];
+ }
+
+ return true;
+}
+
+bool ToolBand::ShouldForceOwnLine() {
+ if (!already_checked_own_line_flag_) {
+ own_line_flag_ = ceee_module_util::GetOptionToolbandForceReposition();
+ already_checked_own_line_flag_ = true;
+ }
+
+ return own_line_flag_;
+}
+
+void ToolBand::ClearForceOwnLineFlag() {
+ if (own_line_flag_ || !already_checked_own_line_flag_) {
+ own_line_flag_ = false;
+ already_checked_own_line_flag_ = true;
+ ceee_module_util::SetOptionToolbandForceReposition(false);
+ }
+}
diff --git a/ceee/ie/plugin/toolband/tool_band.h b/ceee/ie/plugin/toolband/tool_band.h
new file mode 100644
index 0000000..33ad61c
--- /dev/null
+++ b/ceee/ie/plugin/toolband/tool_band.h
@@ -0,0 +1,247 @@
+// 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.
+//
+// IE toolband implementation.
+#ifndef CEEE_IE_PLUGIN_TOOLBAND_TOOL_BAND_H_
+#define CEEE_IE_PLUGIN_TOOLBAND_TOOL_BAND_H_
+
+#include <atlbase.h>
+#include <atlapp.h> // Must be included AFTER base.
+#include <atlcom.h>
+#include <atlcrack.h>
+#include <atlgdi.h>
+#include <atlwin.h>
+#include <atlmisc.h>
+#include <exdispid.h>
+#include <shobjidl.h>
+#include <list>
+#include <string>
+
+#include "base/basictypes.h"
+#include "base/scoped_ptr.h"
+#include "ceee/ie/plugin/toolband/resource.h"
+
+#include "chrome_tab.h" // NOLINT
+#include "toolband.h" // NOLINT
+
+class DictionaryValue;
+class PageApi;
+class ToolBand;
+class DictionaryValue;
+class Value;
+
+typedef IDispEventSimpleImpl<0, ToolBand, &DIID_DIChromeFrameEvents>
+ ChromeFrameEvents;
+
+typedef IDispEventSimpleImpl<1, ToolBand, &DIID_DWebBrowserEvents2>
+ HostingBrowserEvents;
+
+// Implements an IE toolband which gets instantiated for every IE browser tab
+// and renders by hosting chrome frame as an ActiveX control.
+class ATL_NO_VTABLE ToolBand : public CComObjectRootEx<CComSingleThreadModel>,
+ public CComCoClass<ToolBand, &CLSID_ToolBand>,
+ public IObjectWithSiteImpl<ToolBand>,
+ public IServiceProviderImpl<ToolBand>,
+ public IChromeFramePrivileged,
+ public IDeskBand,
+ public IPersistStream,
+ public ChromeFrameEvents,
+ public HostingBrowserEvents,
+ public CWindowImpl<ToolBand> {
+ public:
+ ToolBand();
+ ~ToolBand();
+
+ DECLARE_REGISTRY_RESOURCEID(IDR_TOOL_BAND)
+
+ BEGIN_COM_MAP(ToolBand)
+ COM_INTERFACE_ENTRY(IDeskBand)
+ COM_INTERFACE_ENTRY(IDockingWindow)
+ COM_INTERFACE_ENTRY(IOleWindow)
+ COM_INTERFACE_ENTRY(IPersist)
+ COM_INTERFACE_ENTRY(IPersistStream)
+ COM_INTERFACE_ENTRY(IObjectWithSite)
+ COM_INTERFACE_ENTRY(IServiceProvider)
+ COM_INTERFACE_ENTRY(IChromeFramePrivileged)
+ END_COM_MAP()
+
+ BEGIN_SERVICE_MAP(ToolBand)
+ SERVICE_ENTRY(SID_ChromeFramePrivileged)
+ SERVICE_ENTRY_CHAIN(m_spUnkSite)
+ END_SERVICE_MAP()
+
+
+ BEGIN_SINK_MAP(ToolBand)
+ SINK_ENTRY_INFO(0, DIID_DIChromeFrameEvents,
+ CF_EVENT_DISPID_ONREADYSTATECHANGED,
+ OnCfReadyStateChanged, &handler_type_long_)
+ SINK_ENTRY_INFO(0, DIID_DIChromeFrameEvents,
+ CF_EVENT_DISPID_ONEXTENSIONREADY,
+ OnCfExtensionReady, &handler_type_bstr_i4_)
+ SINK_ENTRY_INFO(0, DIID_DIChromeFrameEvents,
+ CF_EVENT_DISPID_ONGETENABLEDEXTENSIONSCOMPLETE,
+ OnCfGetEnabledExtensionsComplete, &handler_type_bstrarray_)
+ SINK_ENTRY_INFO(0, DIID_DIChromeFrameEvents,
+ CF_EVENT_DISPID_ONMESSAGE,
+ OnCfMessage, &handler_type_idispatch_)
+ SINK_ENTRY_INFO(1, DIID_DWebBrowserEvents2,
+ DISPID_NAVIGATECOMPLETE2,
+ OnIeNavigateComplete2, &handler_type_idispatch_variantref_)
+ END_SINK_MAP()
+
+ DECLARE_PROTECT_FINAL_CONSTRUCT()
+
+ HRESULT FinalConstruct();
+ void FinalRelease();
+
+ BEGIN_MSG_MAP(ToolBand)
+ MSG_WM_CREATE(OnCreate)
+ MSG_WM_PAINT(OnPaint)
+ MSG_WM_SIZE(OnSize)
+ END_MSG_MAP()
+
+ // @name IObjectWithSite overrides.
+ STDMETHOD(SetSite)(IUnknown *site);
+
+ // @name IDockingWindow implementation.
+ // @{
+ STDMETHOD(ShowDW)(BOOL show);
+ STDMETHOD(CloseDW)(DWORD reserved);
+ STDMETHOD(ResizeBorderDW)(LPCRECT border, IUnknown *toolband_site,
+ BOOL reserved);
+ // @}
+
+ // @name IDeskBand implementation.
+ STDMETHOD(GetBandInfo)(DWORD band_id, DWORD view_mode,
+ DESKBANDINFO *deskband_info);
+
+ // @name IOleWindow implementation.
+ // @{
+ STDMETHOD(GetWindow)(HWND *window);
+ STDMETHOD(ContextSensitiveHelp)(BOOL enter_mode);
+ // @}
+
+ // @name IPersist implementation.
+ STDMETHOD(GetClassID)(CLSID *clsid);
+
+ // @name IPersistStream implementation.
+ // @{
+ STDMETHOD(IsDirty)();
+ STDMETHOD(Load)(IStream *stream);
+ STDMETHOD(Save)(IStream *stream, BOOL clear_dirty);
+ STDMETHOD(GetSizeMax)(ULARGE_INTEGER *size);
+ // @}
+
+
+ // @name IChromeFramePrivileged implementation.
+ // @{
+ STDMETHOD(GetWantsPrivileged)(boolean *wants_privileged);
+ STDMETHOD(GetChromeExtraArguments)(BSTR *args);
+ STDMETHOD(GetChromeProfileName)(BSTR *args);
+ STDMETHOD(GetExtensionApisToAutomate)(BSTR *args);
+ // @}
+
+
+ // @name ChromeFrame event handlers
+ // @{
+ STDMETHOD_(void, OnCfReadyStateChanged)(LONG state);
+ STDMETHOD_(void, OnCfExtensionReady)(BSTR path, int response);
+ STDMETHOD_(void, OnCfGetEnabledExtensionsComplete)(
+ SAFEARRAY* extension_directories);
+ STDMETHOD_(void, OnCfMessage)(IDispatch* event);
+ STDMETHOD_(void, OnIeNavigateComplete2)(IDispatch* dispatch, VARIANT* url);
+ // @}
+
+ protected:
+ // Our window maintains a refcount on us for the duration of its lifetime.
+ // The self-reference is managed with those two methods.
+ virtual void OnFinalMessage(HWND window);
+ LRESULT OnCreate(LPCREATESTRUCT lpCreateStruct);
+
+ // Loads the manifest from file and retrieves the URL to the extension.
+ // @returns true on success, false on failure to read the manifest or URL.
+ bool LoadManifestFile(const std::wstring& base_dir,
+ std::string* toolband_url);
+
+ // @name Message handlers.
+ // @{
+ void OnPaint(CDCHandle dc);
+ void OnSize(UINT type, CSize size);
+ // @}
+
+ private:
+ // Initializes the toolband to the given site.
+ // Called from SetSite.
+ HRESULT Initialize(IUnknown *site);
+ // Tears down an initialized toolband.
+ // Called from SetSite.
+ HRESULT Teardown();
+
+ // Handles the dispatching of command received from the User/UI context.
+ HRESULT DispatchUserCommand(const DictionaryValue& dict,
+ scoped_ptr<Value>* return_value);
+
+ // Parses the manifest and navigates CF to the toolband URL.
+ void StartExtension(const wchar_t* base_dir);
+
+ // Subroutine of general initialization. Extracted to make testable.
+ virtual HRESULT InitializeAndShowWindow(IUnknown* site);
+
+ // The OwnLine flag indicates that the toolband should request from the IE
+ // host to put it in its own space (and not behind whatever toolband might
+ // have been installed first).
+ bool ShouldForceOwnLine();
+ void ClearForceOwnLineFlag();
+
+ // The web browser that initialized this toolband.
+ CComPtr<IWebBrowser2> web_browser_;
+ // Our parent window, yielded by our site's IOleWindow.
+ CWindow parent_window_;
+ // Our band id, provided by GetBandInfo.
+ DWORD band_id_;
+
+ // The minimum size the toolband should take.
+ LONG current_width_;
+ LONG current_height_;
+
+ // The URL to our extension.
+ std::string extension_url_;
+
+ // Our Chrome frame instance.
+ CComPtr<IChromeFrame> chrome_frame_;
+
+ // Indicates whether CloseDW() is being called on this tool band.
+ bool is_quitting_;
+
+ // True if we noticed that no extensions are enabled and requested
+ // to install one.
+ bool already_tried_installing_;
+
+ // Flag purpose: see comments to ShouldForceOwnLine
+ // for efficiency we read only once (thus the second flag).
+ bool own_line_flag_;
+ bool already_checked_own_line_flag_;
+
+ // Listening to DIID_DWebBrowserEvents2 is optional. For registration
+ // purposes we have to know if we are listening, though.
+ bool listening_to_browser_events_;
+
+ // Filesystem path to the .crx we will install, or the empty string, or
+ // (if not ending in .crx) the path to an exploded extension directory to
+ // load.
+ std::wstring extension_path_;
+
+ // Function info objects describing our message handlers.
+ // Effectively const but can't make const because of silly ATL macro problem.
+ static _ATL_FUNC_INFO handler_type_idispatch_;
+ static _ATL_FUNC_INFO handler_type_long_;
+ static _ATL_FUNC_INFO handler_type_idispatch_bstr_;
+ static _ATL_FUNC_INFO handler_type_bstr_i4_;
+ static _ATL_FUNC_INFO handler_type_bstrarray_;
+ static _ATL_FUNC_INFO handler_type_idispatch_variantref_;
+
+ DISALLOW_COPY_AND_ASSIGN(ToolBand);
+};
+
+#endif // CEEE_IE_PLUGIN_TOOLBAND_TOOL_BAND_H_
diff --git a/ceee/ie/plugin/toolband/tool_band.rgs b/ceee/ie/plugin/toolband/tool_band.rgs
new file mode 100644
index 0000000..0a7ec34
--- /dev/null
+++ b/ceee/ie/plugin/toolband/tool_band.rgs
@@ -0,0 +1,20 @@
+HKCR {
+ NoRemove CLSID {
+ ForceRemove '{2F1A2D6B-55F6-4B63-8C37-F698D28FDC2B}' = s 'Google Chrome Extensions Execution Environment' {
+ InprocServer32 = s '%MODULE%' {
+ val ThreadingModel = s 'Apartment'
+ }
+ }
+ }
+}
+HKLM {
+ NoRemove Software {
+ NoRemove Microsoft {
+ NoRemove 'Internet Explorer' {
+ NoRemove Toolbar {
+ val '{2F1A2D6B-55F6-4B63-8C37-F698D28FDC2B}' = s 'Google Chrome Extensions Execution Environment'
+ }
+ }
+ }
+ }
+}
diff --git a/ceee/ie/plugin/toolband/tool_band_unittest.cc b/ceee/ie/plugin/toolband/tool_band_unittest.cc
new file mode 100644
index 0000000..cc8c3ca
--- /dev/null
+++ b/ceee/ie/plugin/toolband/tool_band_unittest.cc
@@ -0,0 +1,363 @@
+// 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.
+//
+// IE toolband unit tests.
+#include "ceee/ie/plugin/toolband/tool_band.h"
+
+#include <exdisp.h>
+#include <shlguid.h>
+
+#include "ceee/common/initializing_coclass.h"
+#include "ceee/ie/common/mock_ceee_module_util.h"
+#include "ceee/ie/testing/mock_browser_and_friends.h"
+#include "ceee/testing/utils/dispex_mocks.h"
+#include "ceee/testing/utils/instance_count_mixin.h"
+#include "ceee/testing/utils/test_utils.h"
+#include "gmock/gmock.h"
+#include "gtest/gtest.h"
+
+#include "broker_lib.h" // NOLINT
+
+namespace {
+
+using testing::GetConnectionCount;
+using testing::InstanceCountMixin;
+using testing::MockDispatchEx;
+using testing::Return;
+using testing::StrictMock;
+using testing::TestBrowser;
+using testing::TestBrowserSite;
+
+// Makes ToolBand testable - circumvents InitializeAndShowWindow.
+class TestingToolBand
+ : public ToolBand,
+ public InstanceCountMixin<TestingToolBand>,
+ public InitializingCoClass<TestingToolBand> {
+ public:
+ HRESULT Initialize(TestingToolBand** self) {
+ *self = this;
+ return S_OK;
+ }
+ private:
+ virtual HRESULT InitializeAndShowWindow(IUnknown* site) {
+ return S_OK; // This aspect is not tested.
+ }
+};
+
+class ToolBandTest: public testing::Test {
+ public:
+ ToolBandTest() : tool_band_(NULL), site_(NULL), browser_(NULL) {
+ }
+
+ ~ToolBandTest() {
+ }
+
+ virtual void SetUp() {
+ // Create the instance to test.
+ ASSERT_HRESULT_SUCCEEDED(
+ TestingToolBand::CreateInitialized(&tool_band_, &tool_band_with_site_));
+ tool_band_with_site_ = tool_band_;
+
+ ASSERT_TRUE(tool_band_with_site_ != NULL);
+ }
+
+ virtual void TearDown() {
+ tool_band_ = NULL;
+ tool_band_with_site_.Release();
+
+ site_ = NULL;
+ site_keeper_.Release();
+
+ browser_ = NULL;
+ browser_keeper_.Release();
+
+ // Everything should have been relinquished.
+ ASSERT_EQ(0, testing::InstanceCountMixinBase::all_instance_count());
+ }
+
+ void CreateSite() {
+ ASSERT_HRESULT_SUCCEEDED(
+ TestBrowserSite::CreateInitialized(&site_, &site_keeper_));
+ }
+
+ void CreateBrowser() {
+ ASSERT_HRESULT_SUCCEEDED(
+ TestBrowser::CreateInitialized(&browser_, &browser_keeper_));
+
+ if (site_)
+ site_->browser_ = browser_keeper_;
+ }
+
+ bool ToolbandHasSite() {
+ // Check whether ToolBand has a site set.
+ CComPtr<IUnknown> site;
+ if (SUCCEEDED(tool_band_with_site_->GetSite(
+ IID_IUnknown, reinterpret_cast<void**>(&site)))) {
+ return true;
+ }
+
+ // If GetSite failed and site != NULL, we are seeing things.
+ DCHECK(site == NULL);
+ return false;
+ }
+
+ static void PrepareDeskBandInfo(DESKBANDINFO* pdinfo_for_test) {
+ memset(pdinfo_for_test, 0, sizeof(*pdinfo_for_test));
+
+ // What I really care in this test is DBIM_MODEFLAGS, but if there
+ // are weird interactions here, we want to be warned.
+ pdinfo_for_test->dwMask = DBIM_MODEFLAGS | DBIM_MAXSIZE | DBIM_MINSIZE |
+ DBIM_TITLE | DBIM_INTEGRAL;
+ }
+
+ static const wchar_t* kUrl1;
+
+ testing::TestBrowserSite* site_;
+ CComPtr<IUnknown> site_keeper_;
+
+ TestBrowser* browser_;
+ CComPtr<IWebBrowser2> browser_keeper_;
+
+ TestingToolBand* tool_band_;
+ CComPtr<IObjectWithSite> tool_band_with_site_;
+
+ // the purpose of this mock is to redirect registry calls
+ StrictMock<testing::MockCeeeModuleUtils> ceee_module_utils_;
+};
+
+const wchar_t* ToolBandTest::kUrl1 = L"http://www.google.com";
+
+
+// Setting the ToolBand site with a non-service provider fails.
+TEST_F(ToolBandTest, SetSiteWithNoServiceProviderFails) {
+ testing::LogDisabler no_dchecks;
+
+ // Create an object that doesn't implement IServiceProvider.
+ MockDispatchEx* site = NULL;
+ CComPtr<IUnknown> site_keeper;
+ ASSERT_HRESULT_SUCCEEDED(
+ InitializingCoClass<MockDispatchEx>::CreateInitialized(&site,
+ &site_keeper));
+ // Setting a site that doesn't implement IServiceProvider fails.
+ ASSERT_HRESULT_FAILED(tool_band_with_site_->SetSite(site_keeper));
+ ASSERT_FALSE(ToolbandHasSite());
+}
+
+// Setting the ToolBand site with no browser fails.
+TEST_F(ToolBandTest, SetSiteWithNullBrowserFails) {
+ testing::LogDisabler no_dchecks;
+
+ CreateSite();
+ ASSERT_HRESULT_FAILED(tool_band_with_site_->SetSite(site_keeper_));
+ ASSERT_FALSE(ToolbandHasSite());
+}
+
+// Setting the ToolBand site with a non-browser fails.
+TEST_F(ToolBandTest, SetSiteWithNonBrowserFails) {
+ testing::LogDisabler no_dchecks;
+
+ CreateSite();
+ // Endow the site with a non-browser service.
+ MockDispatchEx* mock_non_browser = NULL;
+ ASSERT_HRESULT_SUCCEEDED(
+ InitializingCoClass<MockDispatchEx>::CreateInitialized(&mock_non_browser,
+ &site_->browser_));
+ ASSERT_HRESULT_FAILED(tool_band_with_site_->SetSite(site_keeper_));
+ ASSERT_FALSE(ToolbandHasSite());
+}
+
+// Setting the ToolBand site with a browser that doesn't implement the
+// DIID_DWebBrowserEvents2 still works.
+TEST_F(ToolBandTest, SetSiteWithNoEventsWorksAnyway) {
+ // We need to quash dcheck here, too (see: ToolBand::Initialize).
+ testing::LogDisabler no_dchecks;
+ CreateSite();
+ CreateBrowser();
+
+ // Disable the connection point.
+ browser_->no_events_ = true;
+
+ // Successful SetSite always calls GetOptionToolbandForceReposition
+ EXPECT_CALL(ceee_module_utils_, GetOptionToolbandForceReposition())
+ .WillOnce(Return(false));
+
+ ASSERT_HRESULT_SUCCEEDED(tool_band_with_site_->SetSite(site_keeper_));
+ ASSERT_TRUE(ToolbandHasSite());
+}
+
+TEST_F(ToolBandTest, SetSiteWithBrowserSucceeds) {
+ CreateSite();
+ CreateBrowser();
+
+ size_t num_connections = 0;
+ ASSERT_HRESULT_SUCCEEDED(GetConnectionCount(browser_keeper_,
+ DIID_DWebBrowserEvents2,
+ &num_connections));
+ ASSERT_EQ(0, num_connections);
+
+ EXPECT_CALL(ceee_module_utils_, GetOptionToolbandForceReposition())
+ .WillOnce(Return(false));
+
+ ASSERT_HRESULT_SUCCEEDED(tool_band_with_site_->SetSite(site_keeper_));
+
+ // Check that the we have not set the connection if not strictly required.
+ ASSERT_HRESULT_SUCCEEDED(GetConnectionCount(browser_keeper_,
+ DIID_DWebBrowserEvents2,
+ &num_connections));
+ ASSERT_EQ(0, num_connections);
+
+ // Check the site's retained.
+ CComPtr<IUnknown> set_site;
+ ASSERT_HRESULT_SUCCEEDED(tool_band_with_site_->GetSite(
+ IID_IUnknown, reinterpret_cast<void**>(&set_site)));
+ ASSERT_TRUE(set_site.IsEqualObject(site_keeper_));
+
+ ASSERT_HRESULT_SUCCEEDED(tool_band_with_site_->SetSite(NULL));
+}
+
+TEST_F(ToolBandTest, SetSiteEstablishesConnectionWhenRequired) {
+ CreateSite();
+ CreateBrowser();
+
+ size_t num_connections = 0;
+ ASSERT_HRESULT_SUCCEEDED(GetConnectionCount(browser_keeper_,
+ DIID_DWebBrowserEvents2,
+ &num_connections));
+ ASSERT_EQ(0, num_connections);
+
+ EXPECT_CALL(ceee_module_utils_, GetOptionToolbandForceReposition())
+ .WillOnce(Return(true));
+
+ ASSERT_HRESULT_SUCCEEDED(tool_band_with_site_->SetSite(site_keeper_));
+
+ // Check that the we have not set the connection if not strictly required.
+ ASSERT_HRESULT_SUCCEEDED(GetConnectionCount(browser_keeper_,
+ DIID_DWebBrowserEvents2,
+ &num_connections));
+ ASSERT_EQ(1, num_connections);
+
+ // Check the site's retained.
+ CComPtr<IUnknown> set_site;
+ ASSERT_HRESULT_SUCCEEDED(tool_band_with_site_->GetSite(
+ IID_IUnknown, reinterpret_cast<void**>(&set_site)));
+ ASSERT_TRUE(set_site.IsEqualObject(site_keeper_));
+
+ ASSERT_HRESULT_SUCCEEDED(tool_band_with_site_->SetSite(NULL));
+
+ // And check that the connection was severed.
+ ASSERT_HRESULT_SUCCEEDED(GetConnectionCount(browser_keeper_,
+ DIID_DWebBrowserEvents2,
+ &num_connections));
+ ASSERT_EQ(0, num_connections);
+}
+
+TEST_F(ToolBandTest, NavigationCompleteResetsFlagAndUnadvises) {
+ CreateSite();
+ CreateBrowser();
+
+ size_t num_connections = 0;
+ ASSERT_HRESULT_SUCCEEDED(GetConnectionCount(browser_keeper_,
+ DIID_DWebBrowserEvents2,
+ &num_connections));
+ ASSERT_EQ(0, num_connections);
+
+ EXPECT_CALL(ceee_module_utils_, GetOptionToolbandForceReposition())
+ .WillOnce(Return(true));
+
+ ASSERT_HRESULT_SUCCEEDED(tool_band_with_site_->SetSite(site_keeper_));
+
+ // Check that the we have not set the connection if not strictly required.
+ ASSERT_HRESULT_SUCCEEDED(GetConnectionCount(browser_keeper_,
+ DIID_DWebBrowserEvents2,
+ &num_connections));
+ ASSERT_EQ(1, num_connections);
+
+ EXPECT_CALL(ceee_module_utils_,
+ SetOptionToolbandForceReposition(false)).Times(1);
+
+ // First navigation triggers (single) registry check and unadivising.
+ // After that things stay quiet.
+ browser_->FireOnNavigateComplete(browser_, &CComVariant(kUrl1));
+
+ ASSERT_HRESULT_SUCCEEDED(GetConnectionCount(browser_keeper_,
+ DIID_DWebBrowserEvents2,
+ &num_connections));
+ ASSERT_EQ(0, num_connections);
+
+ browser_->FireOnNavigateComplete(browser_, &CComVariant(kUrl1));
+
+ ASSERT_HRESULT_SUCCEEDED(tool_band_with_site_->SetSite(NULL));
+}
+
+TEST_F(ToolBandTest, NormalRunDoesntTriggerLineBreak) {
+ CreateSite();
+ CreateBrowser();
+
+ // Expected sequence of actions:
+ // 1) initialization will trigger registry check
+ // 2) invocations if GetBandInfo do not trigger registry check
+ // 3) since spoofed registry says 'do not reposition', there should be no
+ // DBIMF_BREAK flag set in the structure.
+ EXPECT_CALL(ceee_module_utils_, GetOptionToolbandForceReposition())
+ .WillOnce(Return(false));
+
+ ASSERT_HRESULT_SUCCEEDED(tool_band_with_site_->SetSite(site_keeper_));
+
+ DESKBANDINFO dinfo_for_test;
+ PrepareDeskBandInfo(&dinfo_for_test);
+
+ ASSERT_HRESULT_SUCCEEDED(tool_band_->GetBandInfo(42, DBIF_VIEWMODE_NORMAL,
+ &dinfo_for_test));
+
+ ASSERT_FALSE(dinfo_for_test.dwModeFlags & DBIMF_BREAK);
+
+ // Take another pass and result should be the same.
+ PrepareDeskBandInfo(&dinfo_for_test);
+
+ ASSERT_HRESULT_SUCCEEDED(tool_band_->GetBandInfo(42, DBIF_VIEWMODE_NORMAL,
+ &dinfo_for_test));
+
+ ASSERT_FALSE(dinfo_for_test.dwModeFlags & DBIMF_BREAK);
+
+ ASSERT_HRESULT_SUCCEEDED(tool_band_with_site_->SetSite(NULL));
+}
+
+TEST_F(ToolBandTest, NewInstallationTriggersLineBreak) {
+ CreateSite();
+ CreateBrowser();
+
+ EXPECT_CALL(ceee_module_utils_, GetOptionToolbandForceReposition())
+ .WillOnce(Return(true));
+
+ ASSERT_HRESULT_SUCCEEDED(tool_band_with_site_->SetSite(site_keeper_));
+
+ DESKBANDINFO dinfo_for_test;
+
+ // Expected sequence of actions:
+ // 1) invocation of 'GetBandInfo' will trigger registry check.
+ // 2) subsequent invocations do not trigger registry check, but the answer
+ // should also be 'line break' until navigation is completed;
+ // navigation completed is emulated by a call to FireOnNavigateComplete
+ // after that, the break flag is not returned.
+
+ PrepareDeskBandInfo(&dinfo_for_test);
+ ASSERT_HRESULT_SUCCEEDED(tool_band_->GetBandInfo(42, DBIF_VIEWMODE_NORMAL,
+ &dinfo_for_test));
+
+ EXPECT_CALL(ceee_module_utils_,
+ SetOptionToolbandForceReposition(false)).Times(1);
+
+ ASSERT_TRUE(dinfo_for_test.dwModeFlags & DBIMF_BREAK);
+
+ browser_->FireOnNavigateComplete(browser_, &CComVariant(kUrl1));
+
+ PrepareDeskBandInfo(&dinfo_for_test);
+ ASSERT_HRESULT_SUCCEEDED(tool_band_->GetBandInfo(42, DBIF_VIEWMODE_NORMAL,
+ &dinfo_for_test));
+ ASSERT_FALSE(dinfo_for_test.dwModeFlags & DBIMF_BREAK);
+
+ ASSERT_HRESULT_SUCCEEDED(tool_band_with_site_->SetSite(NULL));
+}
+
+} // namespace
diff --git a/ceee/ie/plugin/toolband/toolband.def b/ceee/ie/plugin/toolband/toolband.def
new file mode 100644
index 0000000..9c9cc5b
--- /dev/null
+++ b/ceee/ie/plugin/toolband/toolband.def
@@ -0,0 +1,13 @@
+; 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.
+;
+; ie.def : Declares the module exports.
+
+LIBRARY "ceee_ie.DLL"
+
+EXPORTS
+ DllCanUnloadNow PRIVATE
+ DllGetClassObject PRIVATE
+ DllRegisterServer PRIVATE
+ DllUnregisterServer PRIVATE
diff --git a/ceee/ie/plugin/toolband/toolband.gyp b/ceee/ie/plugin/toolband/toolband.gyp
new file mode 100644
index 0000000..551092d
--- /dev/null
+++ b/ceee/ie/plugin/toolband/toolband.gyp
@@ -0,0 +1,140 @@
+# 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.
+
+{
+ 'variables': {
+ 'chromium_code': 1,
+ },
+ 'includes': [
+ '../../../../build/common.gypi',
+ ],
+ 'targets': [
+ {
+ # This target builds Chrome Frame's IDL file to our
+ # shared intermediate directory
+ 'target_name': 'chrome_tab_idl',
+ 'type': 'none',
+ 'msvs_settings': {
+ 'VCMIDLTool': {
+ 'OutputDirectory': '<(SHARED_INTERMEDIATE_DIR)',
+ },
+ },
+ 'sources': [
+ '../../../../chrome_frame/chrome_tab.idl',
+ ],
+ # Add the output dir for those who depend on us.
+ 'direct_dependent_settings': {
+ 'include_dirs': ['<(SHARED_INTERMEDIATE_DIR)'],
+ },
+ },
+ {
+ 'target_name': 'ceee_ie_lib',
+ 'type': 'static_library',
+ 'dependencies': [
+ 'chrome_tab_idl',
+ '../../common/common.gyp:ie_common_settings',
+ '../../../../base/base.gyp:base',
+ '../../../../ceee/common/common.gyp:ceee_common',
+ ],
+ 'sources': [
+ '../../common/precompile.cc',
+ '../../common/precompile.h',
+ 'tool_band.cc',
+ 'tool_band.h',
+ ],
+ 'libraries': [
+ 'oleacc.lib',
+ 'iepmapi.lib',
+ ],
+ 'configurations': {
+ 'Debug': {
+ 'msvs_precompiled_source': '../../common/precompile.cc',
+ 'msvs_precompiled_header': '../../common/precompile.h',
+ },
+ },
+ },
+ {
+ 'target_name': 'ceee_ie',
+ 'type': 'shared_library',
+ 'dependencies': [
+ 'ceee_ie_lib',
+ 'ie_toolband_common',
+ 'toolband_idl',
+ '../bho/bho.gyp:bho',
+ '../scripting/scripting.gyp:scripting',
+ '../../common/common.gyp:ie_common_settings',
+ '../../common/common.gyp:ie_guids',
+ '../../../../base/base.gyp:base',
+ '../../../../breakpad/breakpad.gyp:breakpad_handler',
+ '../../../../ceee/common/common.gyp:ceee_common',
+ '<(DEPTH)/chrome/chrome.gyp:chrome_version_header',
+ ],
+ 'sources': [
+ '../../common/precompile.cc',
+ '../../common/precompile.h',
+ 'resource.h',
+ 'tool_band.rgs',
+ 'toolband.def',
+ 'toolband.rc',
+ 'toolband_module.cc',
+ '../bho/browser_helper_object.rgs',
+ '../executor.rgs',
+ '../executor_creator.rgs',
+ '../scripting/content_script_manager.rc',
+ ],
+ 'libraries': [
+ 'oleacc.lib',
+ 'iepmapi.lib',
+ ],
+ 'include_dirs': [
+ # Allows us to include .tlb and .h files generated
+ # from our .idl without undue trouble
+ '$(IntDir)',
+ ],
+ 'msvs_settings': {
+ 'VCLinkerTool': {
+ 'OutputFile': '$(OutDir)/servers/$(ProjectName).dll',
+ },
+ },
+ 'configurations': {
+ 'Debug': {
+ 'msvs_precompiled_source': '../../common/precompile.cc',
+ 'msvs_precompiled_header': '../../common/precompile.h',
+ },
+ },
+ },
+ {
+ 'target_name': 'ie_toolband_common',
+ 'type': 'static_library',
+ 'dependencies': [
+ '../../../../chrome_frame/crash_reporting/'
+ 'crash_reporting.gyp:crash_report',
+ '../../../../base/base.gyp:base',
+ '../../../../breakpad/breakpad.gyp:breakpad_handler',
+ ],
+ 'sources': [
+ 'toolband_module_reporting.cc',
+ 'toolband_module_reporting.h',
+ ],
+ },
+ {
+ 'target_name': 'toolband_idl',
+ 'type': 'none',
+ 'sources': [
+ '../../broker/broker_lib.idl',
+ 'toolband.idl',
+ ],
+ 'msvs_settings': {
+ 'VCMIDLTool': {
+ 'OutputDirectory': '<(SHARED_INTERMEDIATE_DIR)',
+ 'DLLDataFileName': '$(InputName)_dlldata.c',
+ },
+ },
+ # Add the output dir for those who depend on us.
+ 'direct_dependent_settings': {
+ 'include_dirs': ['<(SHARED_INTERMEDIATE_DIR)'],
+ },
+ },
+ ]
+}
diff --git a/ceee/ie/plugin/toolband/toolband.idl b/ceee/ie/plugin/toolband/toolband.idl
new file mode 100644
index 0000000..4164945
--- /dev/null
+++ b/ceee/ie/plugin/toolband/toolband.idl
@@ -0,0 +1,370 @@
+// 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.
+//
+// @file
+// Interface and object declarations for CEEE IE toolband.
+import "oaidl.idl";
+import "ocidl.idl";
+
+[
+ object,
+ uuid(07995791-0923-4c4e-B861-AA3B933A31CC),
+ dual,
+ local, // no marshaling for this interface
+ nonextensible,
+ pointer_default(unique)
+]
+// Native interface to content scripts. This interface is exposed to
+// ceee_boostrap.js, and is used to satisfy the same contract as
+// Chrome's native functions declared in event_bindings.js and
+// renderer_extension_bindings.js. Additionally this interface exposes
+// properties for the dispatch functions declared in the same files.
+// At any given time, this interface should to be synonymous with what you'd
+// see by searching the two above files for the strings "dispatchOn" and
+// "native function".
+interface ICeeeContentScriptNativeApi : IDispatch {
+ // Supports JS console.log and console.error.
+ HRESULT Log([in] BSTR level, [in] BSTR message);
+
+ // Port-related functions.
+ HRESULT OpenChannelToExtension([in] BSTR source_id,
+ [in] BSTR target_id,
+ [in] BSTR name,
+ [out, retval] long* port_id);
+ HRESULT CloseChannel([in] long port_id);
+ HRESULT PortAddRef([in] long port_id);
+ HRESULT PortRelease([in] long port_id);
+ HRESULT PostMessage([in] long port_id, [in] BSTR msg);
+
+ // Event-related functions.
+ HRESULT AttachEvent([in] BSTR event_name);
+ HRESULT DetachEvent([in] BSTR event_name);
+
+ // Event notification callbacks to script.
+ [propput] HRESULT onLoad(IDispatch* callback);
+ [propput] HRESULT onUnload(IDispatch* callback);
+
+ // Port notification callbacks.
+ [propput] HRESULT onPortConnect(IDispatch* callback);
+ [propput] HRESULT onPortDisconnect(IDispatch* callback);
+ [propput] HRESULT onPortMessage(IDispatch* callback);
+};
+
+// An invalid tab Id. This is declared here so that both the BHO and the broker
+// knows about it.
+const int kInvalidChromeSessionId = -1;
+
+typedef long CeeeWindowHandle;
+
+typedef enum tagCeeeTabStatus {
+ kCeeeTabStatusLoading = 0,
+ kCeeeTabStatusComplete = 1,
+} CeeeTabStatus;
+
+// Information about a tab.
+typedef struct tagCeeeTabInfo {
+ BSTR url;
+ BSTR title;
+ CeeeTabStatus status;
+ BSTR fav_icon_url;
+} CeeeTabInfo;
+
+typedef enum tagCeeeTabCodeType {
+ kCeeeTabCodeTypeCss = 0,
+ kCeeeTabCodeTypeJs = 1,
+} CeeeTabCodeType;
+
+// Information about a window.
+typedef struct {
+ BOOL focused;
+ RECT rect;
+ // We use a BSTR to dynamically allocate a list of couple (HWND, index).
+ // These are stored as a JSON list of longs, the even indexes are for ids
+ // and their associated odd numbers are the tab index.
+ // This value can be NULL if the caller didn't request to populate tabs.
+ BSTR tab_list;
+} CeeeWindowInfo;
+
+// Information about an HTTP cookie.
+typedef struct {
+ BSTR name;
+ BSTR value;
+ BSTR domain;
+ BOOL host_only;
+ BSTR path;
+ BOOL secure;
+ BOOL http_only;
+ BOOL session;
+ double expiration_date;
+ BSTR store_id;
+} CeeeCookieInfo;
+
+[
+ object,
+ uuid(8DEEECC5-7B49-482d-99F8-2109EA5F2618),
+ 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 {
+ // Initializes the executor to work with the given CeeeWindowHandle.
+ //
+ // @param hwnd The HWND of the window the executor represents.
+ HRESULT Initialize([in] CeeeWindowHandle hwnd);
+
+ // Returns information about the window represented by this executor.
+ //
+ // @param populate_tabs Specifies whether we want to receive the list of tabs.
+ // @param window_info Where to return the info about the window. The
+ // @p tab_list field are only set if @p populate_tabs is
+ // true.
+ HRESULT GetWindow([in] BOOL populate_tabs,
+ [out, ref] CeeeWindowInfo* window_info);
+
+ // Returns the list of tabs of this window. We return both the tab HWND and
+ // the tab index (encoded in the @p tab_list BSTR) so that our callers don't
+ // need an extra IPC to get the tab index later on.
+ //
+ // @param tab_list Where to return the tab identifiers in the same format as
+ // described for tagCeeeWindowInfo::tab_ids.
+ HRESULT GetTabs([out, retval] BSTR* tab_list);
+
+ // Updates the window with the given set of parameters.
+ //
+ // @param left The new left position of the window. -1 to leave unchanged.
+ // @param top The new top position of the window. -1 to leave unchanged.
+ // @param width The new width of the window. -1 to leave unchanged.
+ // @param height The new height of the window. -1 to leave unchanged.
+ // @param window_info Where to return the new info about the updated window.
+ HRESULT UpdateWindow(
+ [in] long left, [in] long top, [in] long width, [in] long height,
+ [out, ref] CeeeWindowInfo* window_info);
+
+ // Close the window represented by our FrameExecutor.
+ //
+ // @retval S_OK We could successfully and silently removed the window.
+ // @retval S_FALSE We failed to <b>silently</b> remove the window,
+ // so the caller should try other alternatives (e.g.,
+ // posting WM_CLOSE to the window).
+ HRESULT RemoveWindow();
+
+ // Returns the index of the given tab.
+ //
+ // @param tab The window handle (HWND) of the tab we want the index of.
+ // @param index Where to return the index.
+ HRESULT GetTabIndex([in] CeeeWindowHandle tab, [out, ref] long* index);
+
+ // Moves the tab specified by @p tab to the index identified by @p index.
+ //
+ // @param tab The window handle (HWND) of the tab to move.
+ // @param index Where to move the tab.
+ HRESULT MoveTab([in] CeeeWindowHandle tab, [in] long index);
+
+ // Removes the specified tab.
+ //
+ // @param tab The window handle (HWND) of the tab to be removed.
+ HRESULT RemoveTab([in] CeeeWindowHandle tab);
+
+ // Selects the specified tab.
+ //
+ // @param tab The window handle (HWND) of the tab to be selected.
+ HRESULT SelectTab([in] CeeeWindowHandle tab);
+};
+
+[
+ object,
+ uuid(C7FF41BA-72D5-4086-8B5A-2EF4FD12E0FE),
+ 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 {
+ // Initializes the executor to work with the given CeeeWindowHandle.
+ //
+ // @param hwnd The HWND of the tab the executor represents.
+ HRESULT Initialize([in] CeeeWindowHandle hwnd);
+
+ // Returns information about the tab represented by the TabExecutor in
+ // @p tab_info structure used to return the information.
+ //
+ // @param tab_info Where to return the tab information.
+ //
+ // @rvalue S_OK Success
+ // @return Other failure HRESULTs may also be returned in case of cascading
+ // errors.
+ HRESULT GetTabInfo([out, ref] CeeeTabInfo* tab_info);
+
+ // Navigate to the given url from the given properties.
+ //
+ // @param url The URL where to navigate the tab to. Can NOT be NULL.
+ // @param flags Specifies the type of navigation based on the
+ // BrowserNavConstants enum values.
+ // @param target Specifies the navigation target (e.g., _top or _blank).
+ //
+ // @rvalue S_OK Success
+ // S_FALSE Nothing needed to be done since the URL was already set.
+ // @return Other failure HRESULTs may also be returned in case of cascading
+ // errors.
+ HRESULT Navigate([in] BSTR url, [in] long flags, [in] BSTR target);
+
+ // Execute or insert code inside a tab.
+ //
+ // @param code The code to execute or insert.
+ // @param file A path to the file that contains the script to execute.
+ // This path is relative to the extension root.
+ // @param all_frames If true, applies to the top level frame as well as
+ // contained iframes. Otherwise, applies onlt to the
+ // top level frame.
+ HRESULT InsertCode([in] BSTR code,
+ [in] BSTR file,
+ [in] BOOL all_frames,
+ [in] CeeeTabCodeType type);
+};
+
+[
+ object,
+ uuid(07630967-D7FB-4745-992F-28614930D9A3),
+ 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 {
+ // Returns information about the cookie identified by the @c name field of
+ // the @p cookie_info structure used to return the information.
+ //
+ // @param url The URL with which the cookie to retrieve is associated.
+ // @param name The name of the cookie to retrieve.
+ // @param cookie_info Where to return the cookie information.
+ HRESULT GetCookie([in] BSTR url, [in] BSTR name,
+ [out, ref] CeeeCookieInfo* cookie_info);
+
+ // Registers the executor's process as a known cookie store; used to indicate
+ // that the cookie store ID has been issued for this process and may be used
+ // in other cookie APIs.
+ // This API is used to ensure that stale cookie store IDs don't inadvertently
+ // match new cookie store processes. This may happen because IE derives the
+ // cookie store ID from the IE process ID, which may be recycled by Windows.
+ // The first time a cookie store ID is issued for an IE process, this
+ // RegisterCookieStore function should be called to indicate that the IE
+ // process may now be selected by a user-provided store ID. All cookie APIs
+ // should verify that CookieStoreIsRegistered() returns S_OK before matching
+ // a user-provided cookie store ID to an IE process.
+ HRESULT RegisterCookieStore();
+
+ // Returns S_OK if the executor's process has been registered as a cookie
+ // store, S_FALSE if not. All cookie API implementations must ensure this
+ // call returns S_OK before accessing a cookie store via a user-provided
+ // cookie store ID; if it doesn't, the store ID is stale.
+ HRESULT CookieStoreIsRegistered();
+};
+
+[
+ object,
+ uuid(276D47E8-1692-4a21-907D-948D170E4330),
+ 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 {
+ // Stores the id of our extension.
+ // @param extension_id The id of the extension.
+ HRESULT SetExtensionId([in] BSTR extension_id);
+
+ // Creates infobar and opens @p url in it. Translates relative path to the
+ // absolute path using "chrome-extension://extension_id" prefix where
+ // extension_id is the id set by SetExtensionId() call.
+ // @param url The URL the infobar window should be navigated to.
+ // @param window_handle Where to return the handle of the window in which
+ // this infobar was created.
+ HRESULT ShowInfobar([in] BSTR url,
+ [out, ref] CeeeWindowHandle* window_handle);
+
+ // Notifies infobar about OnBeforeNavigate2 event for the browser top frame.
+ // @param url The URL the top frame is about to navigate to.
+ HRESULT OnTopFrameBeforeNavigate([in] BSTR url);
+};
+
+[
+ object,
+ uuid(BBB10A7B-DB0D-4f1a-8669-65378DAD0C99),
+ nonextensible,
+ helpstring("ICeeeExecutorCreator Interface"),
+ pointer_default(unique),
+ local
+]
+// Creates an executor in a destination thread, and registers it in the
+// CeeeBroker.
+
+// Used to instantiate a CeeeExecutor.
+interface ICeeeExecutorCreator : IUnknown {
+ // Creates a CeeeExecutor for the given @p thread_id.
+ //
+ // @param thread_id The identifier of the destination thread where we want
+ // an executor to be creared.
+ // @param window The window handle (HWND) the new executor represents.
+ HRESULT CreateWindowExecutor([in] long thread_id,
+ [in] CeeeWindowHandle window);
+
+ // Teardown what was left hanging while waiting for the
+ // new executor to be registered for the given @p thread_id.
+ //
+ // @param thread_id The identifier of the destination thread for which we want
+ // to tear down our infrastructure.
+ HRESULT Teardown([in] long thread_id);
+};
+
+[
+ uuid(7C09079D-F9CB-4E9E-9293-D224B071D8BA),
+ version(1.0),
+ helpstring("Google CEEE 1.0 Type Library")
+]
+library ToolbandLib {
+ importlib("stdole2.tlb");
+
+ // include type info in .tlb
+ interface ICEEEContentScriptNativeApi;
+ interface ICeeeTabExecutor;
+ interface ICeeeWindowExecutor;
+
+ [
+ uuid(E49EBDB7-CEC9-4014-A5F5-8D3C8F5997DC),
+ helpstring("BrowserHelperObject Class")
+ ]
+ coclass BrowserHelperObject {
+ [default] interface IUnknown;
+ };
+ [
+ uuid(2F1A2D6B-55F6-4B63-8C37-F698D28FDC2B),
+ helpstring("ToolBand Class")
+ ]
+ coclass ToolBand {
+ [default] interface IUnknown;
+ };
+ [
+ uuid(4A562910-2D54-4e98-B87F-D4A7F5F5D0B9),
+ helpstring("CEEE Executor Creator Class")
+ ]
+ coclass CeeeExecutorCreator {
+ [default] interface IUnknown;
+ };
+ [
+ uuid(057FCFE3-F872-483d-86B0-0430E375E41F),
+ helpstring("CEEE Executor Class")
+ ]
+ coclass CeeeExecutor {
+ [default] interface IUnknown;
+ interface ICeeeTabExecutor;
+ interface ICeeeWindowExecutor;
+ interface ICeeeCookieExecutor;
+ interface ICeeeInfobarExecutor;
+ };
+};
diff --git a/ceee/ie/plugin/toolband/toolband.rc b/ceee/ie/plugin/toolband/toolband.rc
new file mode 100644
index 0000000..d1319c2
--- /dev/null
+++ b/ceee/ie/plugin/toolband/toolband.rc
@@ -0,0 +1,116 @@
+// 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.
+// Microsoft Visual C++ generated resource script.
+//
+#include "ceee/ie/plugin/toolband/resource.h"
+#include "version.h"
+
+// See winuser.h.
+#define RT_HTML 23
+
+#define APSTUDIO_READONLY_SYMBOLS
+/////////////////////////////////////////////////////////////////////////////
+//
+// Generated from the TEXTINCLUDE 2 resource.
+//
+#include "winres.h"
+
+/////////////////////////////////////////////////////////////////////////////
+#undef APSTUDIO_READONLY_SYMBOLS
+
+/////////////////////////////////////////////////////////////////////////////
+// English (U.S.) resources
+
+#if !defined(AFX_RESOURCE_DLL) || defined(AFX_TARG_ENU)
+#ifdef _WIN32
+LANGUAGE LANG_ENGLISH, SUBLANG_ENGLISH_US
+#pragma code_page(1252)
+#endif //_WIN32
+
+#ifdef APSTUDIO_INVOKED
+# error Don't open this in the GUI, it'll be massacred on save.
+#endif // APSTUDIO_INVOKED
+
+
+/////////////////////////////////////////////////////////////////////////////
+//
+// Version
+//
+
+VS_VERSION_INFO VERSIONINFO
+ FILEVERSION CHROME_VERSION
+ PRODUCTVERSION CHROME_VERSION
+ FILEFLAGSMASK 0x3fL
+#ifdef _DEBUG
+ FILEFLAGS 0x1L
+#else
+ FILEFLAGS 0x0L
+#endif
+ FILEOS 0x4L
+ FILETYPE 0x2L
+ FILESUBTYPE 0x0L
+BEGIN
+ BLOCK "StringFileInfo"
+ BEGIN
+ BLOCK "040904e4"
+ BEGIN
+ VALUE "CompanyName", "Google Inc."
+ VALUE "FileDescription", "Google Chrome Extensions Execution Environment for IE."
+ VALUE "FileVersion", CHROME_VERSION_STRING
+ VALUE "LegalCopyright", COPYRIGHT_STRING
+ VALUE "InternalName", "ceee_ie.dll"
+ VALUE "OriginalFilename", "ceee_ie.dll"
+ VALUE "ProductName", "Google Chrome Extensions Execution Environment"
+ VALUE "ProductVersion", CHROME_VERSION_STRING
+
+ END
+ END
+ BLOCK "VarFileInfo"
+ BEGIN
+ VALUE "Translation", 0x409, 1252
+ END
+END
+
+
+/////////////////////////////////////////////////////////////////////////////
+//
+// REGISTRY
+//
+
+IDR_BROWSERHELPEROBJECT REGISTRY "..\\..\\ceee\\ie\\plugin\\bho\\browser_helper_object.rgs"
+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"
+
+/////////////////////////////////////////////////////////////////////////////
+//
+// BINDATA
+//
+
+IDR_GREASEMONKEY_API_JS BINDATA "..\\..\\chrome\\renderer\\resources\\greasemonkey_api.js"
+
+/////////////////////////////////////////////////////////////////////////////
+//
+// String Table
+//
+
+STRINGTABLE
+BEGIN
+ IDS_PROJNAME "IE"
+END
+
+#endif // English (U.S.) resources
+/////////////////////////////////////////////////////////////////////////////
+
+
+
+#ifndef APSTUDIO_INVOKED
+/////////////////////////////////////////////////////////////////////////////
+//
+// Generated from the TEXTINCLUDE 3 resource.
+//
+1 TYPELIB "toolband.tlb"
+
+/////////////////////////////////////////////////////////////////////////////
+#endif // not APSTUDIO_INVOKED
diff --git a/ceee/ie/plugin/toolband/toolband_module.cc b/ceee/ie/plugin/toolband/toolband_module.cc
new file mode 100644
index 0000000..2d2c735
--- /dev/null
+++ b/ceee/ie/plugin/toolband/toolband_module.cc
@@ -0,0 +1,408 @@
+// 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.
+//
+// Declaration of ATL module object and DLL exports.
+
+#include "base/at_exit.h"
+#include "base/atomic_ref_count.h"
+#include "base/command_line.h"
+#include "base/logging.h"
+#include "base/logging_win.h"
+#include "base/thread.h"
+#include "ceee/common/com_utils.h"
+#include "ceee/common/install_utils.h"
+#include "ceee/ie/common/ceee_module_util.h"
+#include "ceee/ie/plugin/bho/browser_helper_object.h"
+#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/scripting/script_host.h"
+#include "ceee/common/windows_constants.h"
+#include "chrome/common/url_constants.h"
+
+#include "toolband.h" // NOLINT
+
+namespace {
+
+const wchar_t kLogFileName[] = L"ceee.log";
+
+// {73213C1A-C369-4740-A75C-FA849E6CE540}
+static const GUID kCeeeIeLogProviderName =
+ { 0x73213c1a, 0xc369, 0x4740,
+ { 0xa7, 0x5c, 0xfa, 0x84, 0x9e, 0x6c, 0xe5, 0x40 } };
+
+// This is the Script Debugging state for all script engines we instantiate.
+ScriptHost::DebugApplication debug_application(L"CEEE");
+
+} // namespace
+
+// Object entries go here instead of with each object, so that we can move
+// the objects in a lib, and also to decrease the amount of magic.
+OBJECT_ENTRY_AUTO(CLSID_BrowserHelperObject, BrowserHelperObject)
+OBJECT_ENTRY_AUTO(CLSID_ToolBand, ToolBand)
+OBJECT_ENTRY_AUTO(CLSID_CeeeExecutorCreator, CeeeExecutorCreator)
+OBJECT_ENTRY_AUTO(CLSID_CeeeExecutor, CeeeExecutor)
+
+class ToolbandModule : public CAtlDllModuleT<ToolbandModule> {
+ public:
+ ToolbandModule();
+ ~ToolbandModule();
+
+ DECLARE_LIBID(LIBID_ToolbandLib)
+
+ // Needed to make sure we call Init/Term outside the loader lock.
+ HRESULT DllCanUnloadNow();
+ HRESULT DllGetClassObject(REFCLSID clsid, REFIID iid, void** object);
+ void Init();
+ void Term();
+ bool module_initialized() const {
+ return module_initialized_;
+ }
+
+ // Fires an event to the broker, so that the call can be made with an
+ // instance of a broker proxy that was CoCreated in the worker thread.
+ void FireEventToBroker(const std::string& event_name,
+ const std::string& event_args);
+
+ private:
+ class ComWorkerThread : public base::Thread {
+ public:
+ ComWorkerThread();
+
+ // Called just prior to starting the message loop
+ virtual void Init();
+
+ // Called just after the message loop ends
+ virtual void CleanUp();
+
+ // Called by FireEventTask so that the broker we instantiate in the
+ // worker thread can be used.
+ void FireEventToBroker(BSTR event_name, BSTR event_args);
+ protected:
+ CComPtr<ICeeeBroker> broker_;
+ static const int kMaxNumberOfRetries = 5;
+ static const int64 kRetryDelayMs = 10;
+ int current_number_of_retries_;
+ };
+
+ class FireEventTask : public Task {
+ public:
+ FireEventTask(ComWorkerThread* worker_thread,
+ const std::string& event_name,
+ const std::string& event_args)
+ : worker_thread_(worker_thread),
+ event_name_(event_name.c_str()),
+ event_args_(event_args.c_str()) {
+ }
+ FireEventTask(ComWorkerThread* worker_thread,
+ const BSTR event_name,
+ const BSTR event_args)
+ : worker_thread_(worker_thread),
+ event_name_(event_name),
+ event_args_(event_args) {
+ }
+ virtual void Run() {
+ worker_thread_->FireEventToBroker(event_name_, event_args_);
+ }
+ private:
+ ComWorkerThread* worker_thread_;
+ CComBSTR event_name_;
+ CComBSTR event_args_;
+ };
+ // We only start the thread on first use. If we would start it on
+ // initialization, when our DLL is loaded into the broker process,
+ // it would try to start this thread which tries to CoCreate a Broker
+ // and this could cause a complex deadlock...
+ void EnsureThreadStarted();
+
+ // We use a pointer so that we can make sure we only destroy the object
+ // when the thread is properly stopped. Otherwise, we would get a DCHECK
+ // if the thread is killed before we get to Stop it when DllCanUnloadNow
+ // returns S_OK, which happens when the application quits with live objects,
+ // this causes the destructor to DCHECK.
+ ComWorkerThread* worker_thread_;
+ base::AtExitManager at_exit_;
+ bool module_initialized_;
+ bool crash_reporting_initialized_;
+
+ int worker_thread_ref_count_;
+
+ friend void ceee_module_util::AddRefModuleWorkerThread();
+ friend void ceee_module_util::ReleaseModuleWorkerThread();
+
+ void IncThreadRefCount();
+ void DecThreadRefCount();
+};
+
+ToolbandModule::ToolbandModule()
+ : crash_reporting_initialized_(false),
+ module_initialized_(false),
+ worker_thread_(NULL) {
+ wchar_t logfile_path[MAX_PATH];
+ DWORD len = ::GetTempPath(arraysize(logfile_path), logfile_path);
+ ::PathAppend(logfile_path, kLogFileName);
+
+ // It seems we're obliged to initialize the current command line
+ // before initializing logging. This feels a little strange for
+ // a plugin.
+ CommandLine::Init(0, NULL);
+
+ logging::InitLogging(
+ logfile_path,
+ logging::LOG_TO_BOTH_FILE_AND_SYSTEM_DEBUG_LOG,
+ logging::LOCK_LOG_FILE,
+ logging::APPEND_TO_OLD_LOG_FILE);
+
+ // Initialize ETW logging.
+ logging::LogEventProvider::Initialize(kCeeeIeLogProviderName);
+
+ // Initialize control hosting.
+ BOOL initialized = AtlAxWinInit();
+ DCHECK(initialized);
+
+ // Needs to be called before we can use GURL.
+ chrome::RegisterChromeSchemes();
+
+ ScriptHost::set_default_debug_application(&debug_application);
+}
+
+ToolbandModule::~ToolbandModule() {
+ ScriptHost::set_default_debug_application(NULL);
+
+ // Just leave thread as is. Releasing interface from this thread may hang IE.
+ DCHECK(worker_thread_ref_count_ == 0);
+ DCHECK(worker_thread_ == NULL);
+
+ // Uninitialize control hosting.
+ BOOL uninitialized = AtlAxWinTerm();
+ DCHECK(uninitialized);
+
+ logging::CloseLogFile();
+}
+
+HRESULT ToolbandModule::DllCanUnloadNow() {
+ HRESULT hr = CAtlDllModuleT<ToolbandModule>::DllCanUnloadNow();
+ if (hr == S_OK) {
+ // We must protect our data member against concurrent calls to check if we
+ // can be unloaded. We must also making the call to Term within the lock
+ // to make sure we don't try to re-initialize in case a new
+ // DllGetClassObject would occur in the mean time, in another thread.
+ m_csStaticDataInitAndTypeInfo.Lock();
+ if (module_initialized_) {
+ Term();
+ }
+ m_csStaticDataInitAndTypeInfo.Unlock();
+ }
+ return hr;
+}
+
+HRESULT ToolbandModule::DllGetClassObject(REFCLSID clsid, REFIID iid,
+ void** object) {
+ // Same comment as above in ToolbandModule::DllCanUnloadNow().
+ m_csStaticDataInitAndTypeInfo.Lock();
+ if (!module_initialized_) {
+ Init();
+ }
+ m_csStaticDataInitAndTypeInfo.Unlock();
+ return CAtlDllModuleT<ToolbandModule>::DllGetClassObject(clsid, iid, object);
+}
+
+void ToolbandModule::Init() {
+ crash_reporting_initialized_ = InitializeCrashReporting();
+ module_initialized_ = true;
+}
+
+void ToolbandModule::Term() {
+ if (worker_thread_ != NULL) {
+ // It is OK to call Stop on a thread even when it isn't running.
+ worker_thread_->Stop();
+ delete worker_thread_;
+ worker_thread_ = NULL;
+ }
+ if (crash_reporting_initialized_) {
+ bool crash_reporting_deinitialized = ShutdownCrashReporting();
+ DCHECK(crash_reporting_deinitialized);
+ crash_reporting_initialized_ = false;
+ }
+ module_initialized_ = false;
+}
+
+void ToolbandModule::IncThreadRefCount() {
+ m_csStaticDataInitAndTypeInfo.Lock();
+ DCHECK_GE(worker_thread_ref_count_, 0);
+ worker_thread_ref_count_++;
+ m_csStaticDataInitAndTypeInfo.Unlock();
+}
+
+void ToolbandModule::DecThreadRefCount() {
+ ComWorkerThread* thread = NULL;
+
+ m_csStaticDataInitAndTypeInfo.Lock();
+ // If we're already at 0, we have a problem, so we check if we're >=.
+ DCHECK_GT(worker_thread_ref_count_, 0);
+
+ // If this was our last reference, we delete the thread. This is okay even if
+ // we increment the count again, because the thread is created on the "first"
+ // FireEventToBroker, thus it will be created again if needed.
+ if (--worker_thread_ref_count_ == 0) {
+ if (worker_thread_ != NULL) {
+ // Store the worker_thread to a temporary pointer. It will be freed later.
+ thread = worker_thread_;
+ worker_thread_ = NULL;
+ }
+ }
+ m_csStaticDataInitAndTypeInfo.Unlock();
+
+ // Clean the thread after the unlock to be certain we don't get a deadlock
+ // (the CriticalSection could be used in the worker thread).
+ if (thread) {
+ // It is OK to call Stop on a thread even when it isn't running.
+ thread->Stop();
+ delete thread;
+ }
+}
+
+void ToolbandModule::EnsureThreadStarted() {
+ m_csStaticDataInitAndTypeInfo.Lock();
+ if (worker_thread_ == NULL) {
+ worker_thread_ = new ComWorkerThread;
+ // The COM worker thread must be a UI thread so that it can pump windows
+ // messages and allow COM to handle cross apartment calls.
+ worker_thread_->StartWithOptions(base::Thread::Options(MessageLoop::TYPE_UI,
+ 0)); // stack_size
+ }
+ m_csStaticDataInitAndTypeInfo.Unlock();
+}
+
+void ToolbandModule::FireEventToBroker(const std::string& event_name,
+ const std::string& event_args) {
+ EnsureThreadStarted();
+ DCHECK(worker_thread_ != NULL);
+ MessageLoop* message_loop = worker_thread_->message_loop();
+ if (message_loop) {
+ message_loop->PostTask(FROM_HERE,
+ new FireEventTask(worker_thread_, event_name, event_args));
+ } else {
+ LOG(ERROR) << "Trying to post a message before the COM worker thread is"
+ "completely initialized and ready.";
+ }
+}
+
+
+ToolbandModule::ComWorkerThread::ComWorkerThread()
+ : base::Thread("CEEE-COM Worker Thread"),
+ current_number_of_retries_(0) {
+}
+
+void ToolbandModule::ComWorkerThread::Init() {
+ ::CoInitializeEx(0, COINIT_MULTITHREADED);
+ HRESULT hr = broker_.CoCreateInstance(CLSID_CeeeBroker);
+ DCHECK(SUCCEEDED(hr)) << "Failed to create broker. " << com::LogHr(hr);
+}
+
+void ToolbandModule::ComWorkerThread::CleanUp() {
+ broker_.Release();
+ ::CoUninitialize();
+}
+
+void ToolbandModule::ComWorkerThread::FireEventToBroker(BSTR event_name,
+ BSTR event_args) {
+ DCHECK(broker_ != NULL);
+ if (broker_ != NULL) {
+ HRESULT hr = broker_->FireEvent(event_name, event_args);
+ if (SUCCEEDED(hr)) {
+ current_number_of_retries_ = 0;
+ return;
+ }
+ // If the server is busy (which can happen if it is calling in as we try to
+ // to call out to it), then we should retry a few times a little later.
+ if (current_number_of_retries_ < kMaxNumberOfRetries && message_loop()) {
+ ++current_number_of_retries_;
+ LOG(WARNING) << "Retrying Broker FireEvent Failure. " << com::LogHr(hr);
+ message_loop()->PostDelayedTask(FROM_HERE,
+ new FireEventTask(this, event_name, event_args), kRetryDelayMs);
+ } else {
+ current_number_of_retries_ = 0;
+ DCHECK(SUCCEEDED(hr)) << "Broker FireEvent Failed. " << com::LogHr(hr);
+ }
+ }
+}
+
+ToolbandModule module;
+
+void ceee_module_util::AddRefModuleWorkerThread() {
+ module.IncThreadRefCount();
+}
+void ceee_module_util::ReleaseModuleWorkerThread() {
+ module.DecThreadRefCount();
+}
+
+void ceee_module_util::FireEventToBroker(const std::string& event_name,
+ const std::string& event_args) {
+ module.FireEventToBroker(event_name, event_args);
+}
+
+void ceee_module_util::Lock() {
+ module.m_csStaticDataInitAndTypeInfo.Lock();
+}
+
+void ceee_module_util::Unlock() {
+ module.m_csStaticDataInitAndTypeInfo.Unlock();
+}
+
+LONG ceee_module_util::LockModule() {
+ return module.Lock();
+}
+
+LONG ceee_module_util::UnlockModule() {
+ return module.Unlock();
+}
+
+
+// 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.
+ if (reason == DLL_PROCESS_ATTACH) {
+ wchar_t main_exe[MAX_PATH] = { 0 };
+ ::GetModuleFileName(NULL, main_exe, arraysize(main_exe));
+
+ // We don't want to be loaded in the explorer process.
+ _wcslwr_s(main_exe, arraysize(main_exe));
+ if (wcsstr(main_exe, windows::kExplorerModuleName))
+ return FALSE;
+ }
+
+ return module.DllMain(reason, reserved);
+}
+
+// Used to determine whether the DLL can be unloaded by OLE
+STDAPI DllCanUnloadNow(void) {
+ return module.DllCanUnloadNow();
+}
+
+// Returns a class factory to create an object of the requested type
+STDAPI DllGetClassObject(REFCLSID rclsid, REFIID riid, LPVOID* ppv) {
+ return module.DllGetClassObject(rclsid, riid, ppv);
+}
+
+// DllRegisterServer - Adds entries to the system registry
+//
+// This is not the actual entrypoint; see the define right below this
+// 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
+ HRESULT hr = module.DllRegisterServer();
+ return hr;
+}
+
+CEEE_DEFINE_DLL_REGISTER_SERVER()
+
+// DllUnregisterServer - Removes entries from the system registry
+STDAPI DllUnregisterServer(void) {
+ // We always allow unregistration, even if no --enable-ceee install flag.
+ HRESULT hr = module.DllUnregisterServer();
+ return hr;
+}
diff --git a/ceee/ie/plugin/toolband/toolband_module_reporting.cc b/ceee/ie/plugin/toolband/toolband_module_reporting.cc
new file mode 100644
index 0000000..c1d5f58
--- /dev/null
+++ b/ceee/ie/plugin/toolband/toolband_module_reporting.cc
@@ -0,0 +1,50 @@
+// 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.
+//
+// Implementation of CEEE plugin's wrapper around common crash reporting.
+
+#include "ceee/ie/plugin/toolband/toolband_module_reporting.h"
+
+#include "base/file_util.h"
+#include "base/logging.h"
+#include "ceee/ie/common/ceee_module_util.h"
+
+// Well known SID for the system principal.
+const wchar_t kSystemPrincipalSid[] = L"S-1-5-18";
+
+// Returns the custom info structure based on the dll in parameter and the
+// process type.
+google_breakpad::CustomClientInfo* GetCustomInfo() {
+ // TODO(jeffbailey@google.com): Put in a real version.
+ // (bb3143594).
+ static google_breakpad::CustomInfoEntry ver_entry(L"ver", L"Ver.Goes.Here");
+ static google_breakpad::CustomInfoEntry prod_entry(L"prod", L"CEEE_IE");
+ static google_breakpad::CustomInfoEntry plat_entry(L"plat", L"Win32");
+ static google_breakpad::CustomInfoEntry type_entry(L"ptype", L"ie_plugin");
+ static google_breakpad::CustomInfoEntry entries[] = {
+ ver_entry, prod_entry, plat_entry, type_entry };
+ static google_breakpad::CustomClientInfo custom_info = {
+ entries, arraysize(entries) };
+ return &custom_info;
+}
+
+bool InitializeCrashReporting() {
+ if (!ceee_module_util::GetCollectStatsConsent())
+ return false;
+
+ // Get the alternate dump directory. We use the temp path.
+ FilePath temp_directory;
+ if (!file_util::GetTempDir(&temp_directory) || temp_directory.empty()) {
+ return false;
+ }
+
+ bool result = InitializeVectoredCrashReporting(
+ false, kSystemPrincipalSid, temp_directory.value(), GetCustomInfo());
+ DCHECK(result) << "Failed initialize crashreporting.";
+ return result;
+}
+
+bool ShutdownCrashReporting() {
+ return ShutdownVectoredCrashReporting();
+}
diff --git a/ceee/ie/plugin/toolband/toolband_module_reporting.h b/ceee/ie/plugin/toolband/toolband_module_reporting.h
new file mode 100644
index 0000000..9b3aad9
--- /dev/null
+++ b/ceee/ie/plugin/toolband/toolband_module_reporting.h
@@ -0,0 +1,23 @@
+// 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.
+//
+// A wrapper around common crash reporting code to manage reporting for CEEE
+// plugin.
+
+#ifndef CEEE_IE_PLUGIN_TOOLBAND_TOOLBAND_MODULE_REPORTING_H_
+#define CEEE_IE_PLUGIN_TOOLBAND_TOOLBAND_MODULE_REPORTING_H_
+
+#include "chrome_frame/crash_reporting/crash_report.h"
+
+extern const wchar_t kSystemPrincipalSid[];
+
+// Intialize crash reporting for the Toolband Plugin. Specific parameters
+// here include using the temp directory for dumps, using the system-wide
+// install ID, and customized client info.
+bool InitializeCrashReporting();
+
+// Shut down crash reporting for CEEE plug-in.
+bool ShutdownCrashReporting();
+
+#endif // CEEE_IE_PLUGIN_TOOLBAND_TOOLBAND_MODULE_REPORTING_H_
diff --git a/ceee/ie/plugin/toolband/toolband_module_reporting_unittest.cc b/ceee/ie/plugin/toolband/toolband_module_reporting_unittest.cc
new file mode 100644
index 0000000..dfbf904
--- /dev/null
+++ b/ceee/ie/plugin/toolband/toolband_module_reporting_unittest.cc
@@ -0,0 +1,60 @@
+// 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.
+//
+// IE toolband module reporting unit tests.
+#include "ceee/ie/plugin/toolband/toolband_module_reporting.h"
+
+#include "ceee/ie/common/ceee_module_util.h"
+#include "ceee/testing/utils/mock_static.h"
+#include "gmock/gmock.h"
+#include "gtest/gtest.h"
+
+namespace {
+
+using testing::_;
+using testing::Return;
+using testing::StrictMock;
+
+MOCK_STATIC_CLASS_BEGIN(MockToolbandModuleReporting)
+ MOCK_STATIC_INIT_BEGIN(MockToolbandModuleReporting)
+ MOCK_STATIC_INIT2(ceee_module_util::GetCollectStatsConsent,
+ GetCollectStatsConsent);
+ MOCK_STATIC_INIT(InitializeVectoredCrashReporting);
+ MOCK_STATIC_INIT_END()
+
+ MOCK_STATIC0(bool, , GetCollectStatsConsent);
+ MOCK_STATIC4(bool, , InitializeVectoredCrashReporting, bool,
+ const wchar_t*,
+ const std::wstring&,
+ google_breakpad::CustomClientInfo*);
+MOCK_STATIC_CLASS_END(MockToolbandModuleReporting)
+
+TEST(ToolbandModuleReportingTest, InitializeCrashReportingWithoutConsent) {
+ StrictMock<MockToolbandModuleReporting> mock;
+
+ EXPECT_CALL(mock, GetCollectStatsConsent())
+ .Times(1)
+ .WillOnce(Return(false));
+
+ EXPECT_CALL(mock, InitializeVectoredCrashReporting(_, _, _, _))
+ .Times(0);
+
+ InitializeCrashReporting();
+}
+
+TEST(ToolbandModuleReportingTest, InitializeCrashReportingWithConsent) {
+ StrictMock<MockToolbandModuleReporting> mock;
+
+ EXPECT_CALL(mock, GetCollectStatsConsent())
+ .Times(1)
+ .WillOnce(Return(true));
+
+ EXPECT_CALL(mock, InitializeVectoredCrashReporting(_, _, _, _))
+ .Times(1)
+ .WillOnce(Return(true));
+
+ InitializeCrashReporting();
+}
+
+} // namespace