summaryrefslogtreecommitdiffstats
path: root/ceee/ie
diff options
context:
space:
mode:
Diffstat (limited to 'ceee/ie')
-rw-r--r--ceee/ie/broker/api_dispatcher.cc306
-rw-r--r--ceee/ie/broker/api_dispatcher.h377
-rw-r--r--ceee/ie/broker/api_dispatcher_docs.h420
-rw-r--r--ceee/ie/broker/api_dispatcher_unittest.cc275
-rw-r--r--ceee/ie/broker/api_module_constants.cc14
-rw-r--r--ceee/ie/broker/api_module_constants.h18
-rw-r--r--ceee/ie/broker/api_module_util.cc65
-rw-r--r--ceee/ie/broker/api_module_util.h101
-rw-r--r--ceee/ie/broker/broker.cc72
-rw-r--r--ceee/ie/broker/broker.gyp105
-rw-r--r--ceee/ie/broker/broker.h70
-rw-r--r--ceee/ie/broker/broker.rgs9
-rw-r--r--ceee/ie/broker/broker_docs.h61
-rw-r--r--ceee/ie/broker/broker_lib.idl103
-rw-r--r--ceee/ie/broker/broker_module.cc194
-rw-r--r--ceee/ie/broker/broker_module.rc104
-rw-r--r--ceee/ie/broker/broker_module.rgs17
-rw-r--r--ceee/ie/broker/broker_module_util.h25
-rw-r--r--ceee/ie/broker/broker_unittest.cc77
-rw-r--r--ceee/ie/broker/chrome_postman.cc300
-rw-r--r--ceee/ie/broker/chrome_postman.h133
-rw-r--r--ceee/ie/broker/common_api_module.cc146
-rw-r--r--ceee/ie/broker/common_api_module.h71
-rw-r--r--ceee/ie/broker/cookie_api_module.cc450
-rw-r--r--ceee/ie/broker/cookie_api_module.h149
-rw-r--r--ceee/ie/broker/cookie_api_module_unittest.cc777
-rw-r--r--ceee/ie/broker/event_dispatching_docs.h262
-rw-r--r--ceee/ie/broker/executors_manager.cc453
-rw-r--r--ceee/ie/broker/executors_manager.h210
-rw-r--r--ceee/ie/broker/executors_manager_docs.h208
-rw-r--r--ceee/ie/broker/executors_manager_unittest.cc664
-rw-r--r--ceee/ie/broker/infobar_api_module.cc99
-rw-r--r--ceee/ie/broker/infobar_api_module.h43
-rw-r--r--ceee/ie/broker/infobar_api_module_unittest.cc144
-rw-r--r--ceee/ie/broker/resource.h31
-rw-r--r--ceee/ie/broker/tab_api_module.cc1185
-rw-r--r--ceee/ie/broker/tab_api_module.h148
-rw-r--r--ceee/ie/broker/tab_api_module_unittest.cc1452
-rw-r--r--ceee/ie/broker/webnavigation_api_module.cc40
-rw-r--r--ceee/ie/broker/webnavigation_api_module.h21
-rw-r--r--ceee/ie/broker/webrequest_api_module.cc25
-rw-r--r--ceee/ie/broker/webrequest_api_module.h21
-rw-r--r--ceee/ie/broker/window_api_module.cc639
-rw-r--r--ceee/ie/broker/window_api_module.h96
-rw-r--r--ceee/ie/broker/window_api_module_unittest.cc937
-rw-r--r--ceee/ie/broker/window_events_funnel.cc154
-rw-r--r--ceee/ie/broker/window_events_funnel.h52
-rw-r--r--ceee/ie/broker/window_events_funnel_unittest.cc156
-rw-r--r--ceee/ie/common/api_registration.h96
-rw-r--r--ceee/ie/common/ceee_module_util.cc279
-rw-r--r--ceee/ie/common/ceee_module_util.h101
-rw-r--r--ceee/ie/common/ceee_module_util_unittest.cc287
-rw-r--r--ceee/ie/common/chrome_frame_host.cc373
-rw-r--r--ceee/ie/common/chrome_frame_host.h237
-rw-r--r--ceee/ie/common/chrome_frame_host_unittest.cc607
-rw-r--r--ceee/ie/common/common.gyp124
-rw-r--r--ceee/ie/common/constants.cc11
-rw-r--r--ceee/ie/common/constants.h13
-rw-r--r--ceee/ie/common/crash_reporter.cc75
-rw-r--r--ceee/ie/common/crash_reporter.h41
-rw-r--r--ceee/ie/common/crash_reporter_unittest.cc113
-rw-r--r--ceee/ie/common/extension_manifest.cc306
-rw-r--r--ceee/ie/common/extension_manifest.h98
-rw-r--r--ceee/ie/common/extension_manifest_unittest.cc319
-rw-r--r--ceee/ie/common/ie_guids.cc26
-rw-r--r--ceee/ie/common/ie_tab_interfaces.cc46
-rw-r--r--ceee/ie/common/ie_tab_interfaces.h314
-rw-r--r--ceee/ie/common/ie_util.cc134
-rw-r--r--ceee/ie/common/ie_util.h45
-rw-r--r--ceee/ie/common/mock_ceee_module_util.h43
-rw-r--r--ceee/ie/common/mock_ie_tab_interfaces.h106
-rw-r--r--ceee/ie/common/precompile.cc6
-rw-r--r--ceee/ie/common/precompile.h14
-rw-r--r--ceee/ie/ie.gyp146
-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
-rw-r--r--ceee/ie/testing/ie_unittest_main.cc77
-rw-r--r--ceee/ie/testing/mediumtest_ie_common.cc267
-rw-r--r--ceee/ie/testing/mediumtest_ie_common.h204
-rw-r--r--ceee/ie/testing/mediumtest_ie_main.cc68
-rw-r--r--ceee/ie/testing/mock_broker_and_friends.h359
-rw-r--r--ceee/ie/testing/mock_browser_and_friends.h113
-rw-r--r--ceee/ie/testing/mock_chrome_frame_host.h62
-rw-r--r--ceee/ie/testing/mock_frame_event_handler_host.h81
-rw-r--r--ceee/ie/testing/precompile.cc6
-rw-r--r--ceee/ie/testing/precompile.h19
-rw-r--r--ceee/ie/testing/test_data/another_frame_one.html12
-rw-r--r--ceee/ie/testing/test_data/another_frame_two.html12
-rw-r--r--ceee/ie/testing/test_data/another_two_frames.html13
-rw-r--r--ceee/ie/testing/test_data/deep_frames.html12
-rw-r--r--ceee/ie/testing/test_data/frame_one.html12
-rw-r--r--ceee/ie/testing/test_data/frame_two.html12
-rw-r--r--ceee/ie/testing/test_data/level_one_frame.html14
-rw-r--r--ceee/ie/testing/test_data/level_two_frame.html13
-rw-r--r--ceee/ie/testing/test_data/orphans.html72
-rw-r--r--ceee/ie/testing/test_data/simple_page.html12
-rw-r--r--ceee/ie/testing/test_data/two_frames.html13
192 files changed, 41211 insertions, 0 deletions
diff --git a/ceee/ie/broker/api_dispatcher.cc b/ceee/ie/broker/api_dispatcher.cc
new file mode 100644
index 0000000..218a198
--- /dev/null
+++ b/ceee/ie/broker/api_dispatcher.cc
@@ -0,0 +1,306 @@
+// 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.
+//
+// Dispatcher and registry for Chrome Extension APIs.
+
+#include "ceee/ie/broker/api_dispatcher.h"
+
+#include "base/json/json_reader.h"
+#include "base/json/json_writer.h"
+#include "base/logging.h"
+#include "ceee/common/com_utils.h"
+#include "ceee/ie/broker/chrome_postman.h"
+#include "ceee/ie/broker/cookie_api_module.h"
+#include "ceee/ie/broker/executors_manager.h"
+#include "ceee/ie/broker/infobar_api_module.h"
+#include "ceee/ie/broker/tab_api_module.h"
+#include "ceee/ie/broker/webnavigation_api_module.h"
+#include "ceee/ie/broker/webrequest_api_module.h"
+#include "ceee/ie/broker/window_api_module.h"
+#include "chrome/browser/automation/extension_automation_constants.h"
+
+namespace keys = extension_automation_constants;
+
+bool ApiDispatcher::IsRunningInSingleThread() {
+ return thread_id_ == ::GetCurrentThreadId();
+}
+
+
+void ApiDispatcher::HandleApiRequest(BSTR message_text, BSTR* response) {
+ DCHECK(IsRunningInSingleThread());
+ DCHECK(message_text);
+
+ scoped_ptr<Value> base_value(base::JSONReader::Read(CW2A(message_text).m_psz,
+ false));
+ DCHECK(base_value.get() && base_value->GetType() == Value::TYPE_DICTIONARY);
+ if (!base_value.get() || base_value->GetType() != Value::TYPE_DICTIONARY) {
+ return;
+ }
+ DictionaryValue* value = static_cast<DictionaryValue*>(base_value.get());
+
+ std::string function_name;
+ if (!value->GetString(keys::kAutomationNameKey, &function_name)) {
+ DCHECK(false);
+ return;
+ }
+
+ std::string args_string;
+ if (!value->GetString(keys::kAutomationArgsKey, &args_string)) {
+ DCHECK(false);
+ return;
+ }
+
+ base::JSONReader reader;
+ scoped_ptr<Value> args(reader.JsonToValue(args_string, false, false));
+ DCHECK(args.get() && args->GetType() == Value::TYPE_LIST);
+ if (!args.get() || args->GetType() != Value::TYPE_LIST) {
+ return;
+ }
+ ListValue* args_list = static_cast<ListValue*>(args.get());
+
+ int request_id = -1;
+ if (!value->GetInteger(keys::kAutomationRequestIdKey, &request_id)) {
+ DCHECK(false);
+ return;
+ }
+ DLOG(INFO) << "Request: " << request_id << ", for: " << function_name;
+ FactoryMap::const_iterator iter = factories_.find(function_name);
+ DCHECK(iter != factories_.end());
+ if (iter == factories_.end()) {
+ return;
+ }
+
+ scoped_ptr<Invocation> invocation(iter->second());
+ DCHECK(invocation.get());
+ invocation->Execute(*args_list, request_id);
+}
+
+void ApiDispatcher::FireEvent(BSTR event_name, BSTR event_args) {
+ DCHECK(IsRunningInSingleThread());
+ DLOG(INFO) << "ApiDispatcher::FireEvent. " << event_name << " - " <<
+ event_args;
+ std::string event_args_str(CW2A(event_args).m_psz);
+ std::string event_name_str(CW2A(event_name).m_psz);
+ // Start by going through the permanent event handlers map.
+ PermanentEventHandlersMap::const_iterator iter =
+ permanent_event_handlers_.find(event_name_str);
+ bool fire_event = true;
+ std::string converted_event_args;
+ if (iter != permanent_event_handlers_.end()) {
+ fire_event = iter->second(event_args_str, &converted_event_args, this);
+ } else {
+ // Some events don't need a handler and
+ // have all the info already available.
+ converted_event_args = event_args_str;
+ }
+
+ if (fire_event) {
+ // The notification message is an array containing event name as a string
+ // at index 0 and then a JSON encoded string of the other arguments.
+ ListValue message;
+ message.Append(Value::CreateStringValue(event_name_str));
+
+ // Event messages expect a string for the arguments.
+ message.Append(Value::CreateStringValue(converted_event_args));
+
+ std::string message_str;
+ base::JSONWriter::Write(&message, false, &message_str);
+ ChromePostman::GetInstance()->PostMessage(CComBSTR(message_str.c_str()),
+ CComBSTR(keys::kAutomationBrowserEventRequestTarget));
+ }
+
+ // We do this after firing the event, to be in the same order as Chrome.
+ // And to allow reusing the args conversion from the permanent handler.
+ EphemeralEventHandlersMap::iterator map_iter =
+ ephemeral_event_handlers_.find(event_name_str);
+ if (map_iter != ephemeral_event_handlers_.end() &&
+ !map_iter->second.empty()) {
+ // We must work with a copy since other handlers might get registered
+ // as we call the current set of handlers (some of them chain themselves).
+ EphemeralEventHandlersList handlers_list;
+ map_iter->second.swap(handlers_list);
+ // Swap emptied our current list, so we will need to add back the items
+ // that are not affected by this call (the ones that we wouldn't want
+ // to remove from the list just yet return S_FALSE). This scheme is much
+ // simpler than trying to keep up with the live list as it may change.
+ EphemeralEventHandlersList::iterator list_iter = handlers_list.begin();
+ for (;list_iter != handlers_list.end(); ++list_iter) {
+ if (list_iter->handler(converted_event_args,
+ list_iter->user_data, this) == S_FALSE) {
+ map_iter->second.push_back(*list_iter);
+ }
+ }
+ }
+}
+
+void ApiDispatcher::GetExecutor(HWND window, REFIID iid, void** executor) {
+ DWORD thread_id = ::GetWindowThreadProcessId(window, NULL);
+ HRESULT hr = Singleton<ExecutorsManager,
+ ExecutorsManager::SingletonTraits>::get()->
+ GetExecutor(thread_id, window, iid, executor);
+ DLOG_IF(INFO, FAILED(hr)) << "Failed to get executor for window: " <<
+ window << ". In thread: " << thread_id << ". " << com::LogHr(hr);
+}
+
+
+HWND ApiDispatcher::GetTabHandleFromId(int tab_id) const {
+ return Singleton<ExecutorsManager, ExecutorsManager::SingletonTraits>::get()->
+ GetTabHandleFromId(tab_id);
+}
+
+HWND ApiDispatcher::GetWindowHandleFromId(int window_id) const {
+ return reinterpret_cast<HWND>(window_id);
+}
+
+int ApiDispatcher::GetTabIdFromHandle(HWND tab_handle) const {
+ return Singleton<ExecutorsManager, ExecutorsManager::SingletonTraits>::get()->
+ GetTabIdFromHandle(tab_handle);
+}
+
+int ApiDispatcher::GetWindowIdFromHandle(HWND window_handle) const {
+ return reinterpret_cast<int>(window_handle);
+}
+
+
+void ApiDispatcher::SetTabIdForHandle(long tab_id, HWND handle) {
+ Singleton<ExecutorsManager, ExecutorsManager::SingletonTraits>::get()->
+ SetTabIdForHandle(tab_id, handle);
+}
+
+void ApiDispatcher::DeleteTabHandle(HWND handle) {
+ Singleton<ExecutorsManager, ExecutorsManager::SingletonTraits>::get()->
+ DeleteTabHandle(handle);
+}
+
+void ApiDispatcher::RegisterInvocation(const char* function_name,
+ InvocationFactory factory) {
+ DCHECK(function_name && factory);
+ // Re-registration is not expected.
+ DCHECK(function_name && factories_.find(function_name) == factories_.end());
+ if (function_name && factory)
+ factories_[function_name] = factory;
+}
+
+void ApiDispatcher::RegisterPermanentEventHandler(
+ const char* event_name, PermanentEventHandler event_handler) {
+ DCHECK(event_name && event_handler);
+ // Re-registration is not expected.
+ DCHECK(event_name && permanent_event_handlers_.find(event_name) ==
+ permanent_event_handlers_.end());
+ if (event_name && event_handler)
+ permanent_event_handlers_[event_name] = event_handler;
+}
+
+void ApiDispatcher::RegisterEphemeralEventHandler(
+ const char* event_name,
+ EphemeralEventHandler event_handler,
+ InvocationResult* user_data) {
+ DCHECK(IsRunningInSingleThread());
+ // user_data could be NULL, we don't care.
+ DCHECK(event_handler != NULL);
+ if (event_handler != NULL) {
+ ephemeral_event_handlers_[event_name].push_back(
+ EphemeralEventHandlerTuple(event_handler, user_data));
+ }
+}
+
+ApiDispatcher::InvocationResult::~InvocationResult() {
+ // We must have posted the response before we died.
+ DCHECK(request_id_ == kNoRequestId);
+}
+
+// A quick helper function to reuse the call to ChromePostman.
+void ApiDispatcher::InvocationResult::PostResponseToChromeFrame(
+ const char* response_key, const std::string& response_str) {
+ DictionaryValue response_dict;
+ response_dict.SetInteger(keys::kAutomationRequestIdKey, request_id_);
+ response_dict.SetString(response_key, response_str);
+
+ std::string response;
+ base::JSONWriter::Write(&response_dict, false, &response);
+ ChromePostman::GetInstance()->PostMessage(
+ CComBSTR(response.c_str()), CComBSTR(keys::kAutomationResponseTarget));
+}
+
+// Once an invocation completes successfully, it must call this method on its
+// result object to send it back to Chrome.
+void ApiDispatcher::InvocationResult::PostResult() {
+ // We should never post results twice, or for non requested results.
+ DCHECK(request_id_ != kNoRequestId);
+ if (request_id_ != kNoRequestId) {
+ std::string result_str;
+ // Some invocations don't return values. We can set an empty string here.
+ if (value_.get() != NULL)
+ base::JSONWriter::Write(value_.get(), false, &result_str);
+ PostResponseToChromeFrame(keys::kAutomationResponseKey, result_str);
+ // We should only post once!
+ request_id_ = kNoRequestId;
+ }
+}
+
+// Invocations can use this method to post an error to Chrome when it
+// couldn't complete the invocation successfully.
+void ApiDispatcher::InvocationResult::PostError(const std::string& error) {
+ LOG(ERROR) << error;
+ // Event handlers reuse InvocationResult code without a requestId,
+ // so don't DCHECK as in PostResult here.
+ // TODO(mad@chromium.org): Might be better to use a derived class instead.
+ if (request_id_ != kNoRequestId) {
+ PostResponseToChromeFrame(keys::kAutomationErrorKey, error);
+ // We should only post once!
+ request_id_ = kNoRequestId;
+ }
+}
+
+void ApiDispatcher::InvocationResult::SetValue(const char* name, Value* value) {
+ DCHECK(name != NULL && value != NULL);
+ scoped_ptr<Value> value_holder(value);
+ if (name == NULL || value == NULL)
+ return;
+
+ if (temp_values_.get() == NULL) {
+ temp_values_.reset(new DictionaryValue);
+ }
+
+ // scoped_ptr::release() lets go of its ownership.
+ temp_values_->Set(name, value_holder.release());
+}
+
+const Value* ApiDispatcher::InvocationResult::GetValue(const char* name) {
+ if (temp_values_.get() == NULL)
+ return NULL;
+
+ Value* value = NULL;
+ bool success = temp_values_->Get(name, &value);
+ // Make sure that success means NotNull and vice versa.
+ DCHECK((value != NULL || !success) && (success || value == NULL));
+ return value;
+}
+
+ApiDispatcher* ApiDispatcher::InvocationResult::GetDispatcher() {
+ return ProductionApiDispatcher::get();
+}
+
+ApiDispatcher* ApiDispatcher::Invocation::GetDispatcher() {
+ return ProductionApiDispatcher::get();
+}
+
+// Function registration preprocessor magic. See api_registration.h for details.
+#ifdef REGISTER_ALL_API_FUNCTIONS
+#error Must not include api_registration.h previously in this compilation unit.
+#endif // REGISTER_ALL_API_FUNCTIONS
+#define PRODUCTION_API_DISPATCHER
+#define REGISTER_TAB_API_FUNCTIONS() tab_api::RegisterInvocations(this)
+#define REGISTER_WINDOW_API_FUNCTIONS() window_api::RegisterInvocations(this)
+#define REGISTER_COOKIE_API_FUNCTIONS() cookie_api::RegisterInvocations(this)
+#define REGISTER_INFOBAR_API_FUNCTIONS() infobar_api::RegisterInvocations(this)
+#define REGISTER_WEBNAVIGATION_API_FUNCTIONS() \
+ webnavigation_api::RegisterInvocations(this)
+#define REGISTER_WEBREQUEST_API_FUNCTIONS() \
+ webrequest_api::RegisterInvocations(this)
+#include "ceee/ie/common/api_registration.h"
+
+ProductionApiDispatcher::ProductionApiDispatcher() {
+ REGISTER_ALL_API_FUNCTIONS();
+}
diff --git a/ceee/ie/broker/api_dispatcher.h b/ceee/ie/broker/api_dispatcher.h
new file mode 100644
index 0000000..db13110
--- /dev/null
+++ b/ceee/ie/broker/api_dispatcher.h
@@ -0,0 +1,377 @@
+// 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.
+//
+// Dispatcher and registry for Chrome Extension APIs.
+
+#ifndef CEEE_IE_BROKER_API_DISPATCHER_H_
+#define CEEE_IE_BROKER_API_DISPATCHER_H_
+
+#include <list>
+#include <map>
+#include <string>
+#include <vector>
+
+#include "base/basictypes.h"
+#include "base/logging.h"
+#include "base/scoped_ptr.h"
+#include "base/singleton.h"
+#include "base/values.h"
+
+#include "broker_lib.h" // NOLINT
+
+class ExecutorsManager;
+
+// Keeps a registry of all API Invocation implementations and implements
+// the logic needed to deserialize API Invocation requests, dispatch them,
+// and serialize and return the response.
+class ApiDispatcher {
+ public:
+ ApiDispatcher() : thread_id_(0) {}
+ virtual ~ApiDispatcher() {}
+
+ // Dispatches a Chrome Extensions API request and sends back the response.
+ //
+ // @param message_text The raw JSON-encoded text of the API request over
+ // the automation interface.
+ // @param response Where to return the JSON-encoded response.
+ virtual void HandleApiRequest(BSTR message_text, BSTR* response);
+
+ // Fire the given event message to Chrome Frame and potentially use it
+ // to complete pending extension API execution.
+ //
+ // @param event_name The name of the event to fire.
+ // @param event_args The JSON encoded event arguments.
+ virtual void FireEvent(BSTR event_name, BSTR event_args);
+
+ // This class holds on the result and can be derived from to generate results
+ // that are either specific to tabs or windows for example.
+ class InvocationResult {
+ public:
+ static const int kNoRequestId = -1;
+ explicit InvocationResult(int request_id)
+ : request_id_(request_id) {
+ }
+ virtual ~InvocationResult();
+
+ // Post the response string from our current result to Chrome Frame.
+ virtual void PostResult();
+
+ // Post the given error string to Chrome Frame.
+ virtual void PostError(const std::string& error);
+
+ // Identifies if we are pending a post (for which we need a request id).
+ virtual bool Pending() const { return request_id_ != kNoRequestId; }
+
+ // Returns the result of the API invocation as a value object which
+ // is still owned by this object - the caller does not take ownership.
+ // May return NULL if execution didn't produce any results yet.
+ const Value* value() const {
+ return value_.get();
+ }
+ void set_value(Value* value) {
+ value_.reset(value);
+ }
+
+ // Sets a value in a dictionary. The Value pointer is kept in the
+ // dictionary which takes ownership so the caller should NOT deallocate it.
+ // Even in case of errors, the value will be deallocated.
+ // This value can then be retrieved by GetValue below, this can be useful
+ // to keep information in the InvocationResult during async handling.
+ virtual void SetValue(const char* name, Value* value);
+
+ // Returns a value set by SetValue above. Note that the value is only
+ // valid during the lifetime of the InvocationResult object or until another
+ // value is set for the same name. Also, the returned value should NOT
+ // be deallocated by the caller, it will get deallocated on destruction
+ // of the object, or when another value is set for the same name.
+ // Returns NULL if no value of this name have been previously set.
+ virtual const Value* GetValue(const char* name);
+
+ protected:
+ // A unit test seam.
+ virtual ApiDispatcher* GetDispatcher();
+
+ // A helper function to post the given response to Chrome Frame.
+ virtual void PostResponseToChromeFrame(const char* response_key,
+ const std::string& response_str);
+ // Where to store temporary values.
+ scoped_ptr<DictionaryValue> temp_values_;
+
+ // Where to store the result value.
+ scoped_ptr<Value> value_;
+
+ // Invocation request identifier.
+ int request_id_;
+
+ DISALLOW_COPY_AND_ASSIGN(InvocationResult);
+ };
+
+ // Base class for API Invocation Execution registered with this object.
+ // A new execution object is created for each API invocation call even though
+ // the state data is stored in the classes deriving from InvocationResult
+ // (declared above). This is to allow easier testing of the Invocation
+ // implementation by having virtual methods like GetDispatcher to be used
+ // as a test seam, or other specific methods (like
+ // ApiResultCreator<>::CreateApiResult). Otherwise, Invocation::Execute
+ // could be a static callback as the event handlers described below.
+ class Invocation {
+ public:
+ Invocation() {}
+ virtual ~Invocation() {}
+
+ // Called when a request to invoke the execution of an API is received.
+ //
+ // @param args The list value object for the arguments passed.
+ // @param request_id The identifier of the request being executed.
+ virtual void Execute(const ListValue& args, int request_id) = 0;
+
+ protected:
+ // Unit test seam.
+ virtual ApiDispatcher* GetDispatcher();
+
+ DISALLOW_COPY_AND_ASSIGN(Invocation);
+ };
+
+ // The Invocation factory pointers to be stored in the factory map.
+ typedef Invocation* (*InvocationFactory)();
+
+ // The permanent event handlers are stored in a specific map and don't get
+ // removed after they are called. They are also registered without user data.
+ // The handler can stop the broadcast of the event by returning false.
+ typedef bool (*PermanentEventHandler)(const std::string& input_args,
+ std::string* converted_args,
+ ApiDispatcher* dispatcher);
+
+ // The ephemeral event handlers are stored in a specific map and they get
+ // removed after a successfully completed call or an error (a successful call
+ // is one which returns S_OK, S_FALSE is returned to identify that this
+ // set of arguments is not the one we were waiting for, so we need to wait
+ // some more, and returning a FAILED HRESULT causes the handler to be removed
+ // from the queue).
+ // EphemeralEventHandlers can specify user data to be passed back to them
+ // when the event occurs. The dynamic data allocation and release is the
+ // responsibility of the caller/handler pair, the dispatcher only keeps the
+ // pointer and passes it back to the handler as is without any post-cleanup.
+ // Also note that the Ephemeral event handlers are called after the Permanent
+ // handlers which may have had the chance to augment the content of the
+ // arguments which are passed to the ephemeral handlers. So these are useful
+ // for asynchronous invocation completion.
+ // We also pass in the ApiDispatcher for ease of test mocking.
+ typedef HRESULT (*EphemeralEventHandler)(const std::string& input_args,
+ InvocationResult* user_data,
+ ApiDispatcher* dispatcher);
+
+ // Registers a factory for an API Invocation of a given name. Note that
+ // registering the same name more than once is not permitted.
+ //
+ // @param function_name The name of the function handled by the Invocation.
+ // @param factory The factory function to call to create a new instance of
+ // the Invocation type to handle this function.
+ virtual void RegisterInvocation(const char* function_name,
+ InvocationFactory factory);
+
+ // Registers a permanent handler for an event of a given name. Note that
+ // registering the same name more than once is not permitted.
+ //
+ // @param event_name The name of the event to handle.
+ // @param event_handler The event handler to call to handle the event.
+ virtual void RegisterPermanentEventHandler(
+ const char* event_name, PermanentEventHandler event_handler);
+
+ // Registers an ephemeral handler for an event of a given name. Note that
+ // registering the same name more than once is permitted. When an event is
+ // fired, all ephemeral handlers are called after the permanent one if
+ // one was registered.
+ //
+ // @param event_name The name of the event to handle.
+ // @param event_handler The event handler to call to handle the event.
+ // @param user_data The data that has to be passed back to the handler.
+ virtual void RegisterEphemeralEventHandler(
+ const char* event_name,
+ EphemeralEventHandler event_handler,
+ InvocationResult* user_data);
+
+ // Sets the thread id of the ApiInvocation thread so that we can make sure
+ // that we are only ran from that thread. Only used for debug purposes.
+ virtual void SetApiInvocationThreadId(DWORD thread_id) {
+ thread_id_ = thread_id;
+ }
+
+ // Fetch the appropriate executor to execute code for the given window.
+ //
+ // @param window The window for which we want an executor.
+ // @param iid The identifier of the interface we want to work with.
+ // @param executor Where to return the requested executor interface pointer.
+ virtual void GetExecutor(HWND window, REFIID iid, void** executor);
+
+ // Return a tab handle associated with the id.
+ //
+ // @param tab_id The tab identifier.
+ // @return The corresponding HWND (or INVALID_HANDLE_VALUE if tab_id isn't
+ // found).
+ virtual HWND GetTabHandleFromId(int tab_id) const;
+
+ // Return a window handle associated with the id.
+ //
+ // @param window_id The window identifier.
+ // @return The corresponding HWND (or INVALID_HANDLE_VALUE if window_id isn't
+ // found).
+ virtual HWND GetWindowHandleFromId(int window_id) const;
+
+ // Return a tab id associated with the HWND.
+ //
+ // @param tab_handle The tab HWND.
+ // @return The corresponding tab id (or 0 if tab_handle isn't found).
+ virtual int GetTabIdFromHandle(HWND tab_handle) const;
+
+ // Return a window id associated with the HWND.
+ //
+ // @param window_handle The window HWND.
+ // @return The corresponding window id (or 0 if window_handle isn't found).
+ virtual int GetWindowIdFromHandle(HWND window_handle) const;
+
+ // Register the relation between a tab_id and a HWND.
+ virtual void SetTabIdForHandle(long tab_id, HWND tab_handle);
+
+ // Unregister the HWND and its corresponding tab_id.
+ virtual void DeleteTabHandle(HWND handle);
+
+ protected:
+ typedef std::map<std::string, InvocationFactory> FactoryMap;
+ FactoryMap factories_;
+
+ typedef std::map<std::string, PermanentEventHandler>
+ PermanentEventHandlersMap;
+ PermanentEventHandlersMap permanent_event_handlers_;
+
+ // For ephemeral event handlers we also need to keep the user data.
+ struct EphemeralEventHandlerTuple {
+ EphemeralEventHandlerTuple(
+ EphemeralEventHandler handler,
+ InvocationResult* user_data)
+ : handler(handler), user_data(user_data) {}
+ EphemeralEventHandler handler;
+ InvocationResult* user_data;
+ };
+
+ // We can have a list of ephemeral event handlers for a given event name.
+ typedef std::vector<EphemeralEventHandlerTuple>
+ EphemeralEventHandlersList;
+ typedef std::map<std::string, EphemeralEventHandlersList>
+ EphemeralEventHandlersMap;
+ EphemeralEventHandlersMap ephemeral_event_handlers_;
+
+ // The thread we are running into.
+ DWORD thread_id_;
+
+ // Make sure this is always called from the same thread,
+ bool IsRunningInSingleThread();
+
+ DISALLOW_COPY_AND_ASSIGN(ApiDispatcher);
+};
+
+// Convenience InvocationFactory implementation.
+template <class InvocationType>
+ApiDispatcher::Invocation* NewApiInvocation() {
+ return new InvocationType();
+}
+
+// A singleton that initializes and keeps the ApiDispatcher used by production
+// code.
+class ProductionApiDispatcher : public ApiDispatcher,
+ public Singleton<ProductionApiDispatcher> {
+ private:
+ // This ensures no construction is possible outside of the class itself.
+ friend struct DefaultSingletonTraits<ProductionApiDispatcher>;
+ DISALLOW_IMPLICIT_CONSTRUCTORS(ProductionApiDispatcher);
+};
+
+// A convenience class that can be derived from by API function classes instead
+// of ApiDispatcher::Invocation. Mainly benefits ease of testing, so that we
+// can specify a mocked result object.
+template<class ResultType = ApiDispatcher::InvocationResult>
+class ApiResultCreator : public ApiDispatcher::Invocation {
+ protected:
+ // Allocates a new instance of ResultType. The caller of this function takes
+ // ownership of this instance and is responsible for freeing it.
+ virtual ResultType* CreateApiResult(int request_id) {
+ return new ResultType(request_id);
+ }
+};
+
+template<class InvocationBase>
+class IterativeApiResult : public InvocationBase {
+ public:
+ explicit IterativeApiResult(int request_id)
+ : InvocationBase(request_id), result_accumulator_(new ListValue()) {
+ }
+
+ // Unlike the base class, this hack subverts the process by accumulating
+ // responses (positive and errors) rather than posting them right away.
+ virtual void PostResult() {
+ DCHECK(value() != NULL);
+ DCHECK(result_accumulator_ != NULL);
+
+ if (result_accumulator_ != NULL)
+ result_accumulator_->Append(value_.release());
+ }
+
+ virtual void PostError(const std::string& error) {
+ error_accumulator_.push_back(error);
+ }
+
+ // Finally post whatever was reported as result.
+ virtual void FlushAllPosts() {
+ if (AllFailed()) {
+ CallRealPostError(error_accumulator_.back());
+ } else {
+ // Post success as per base class implementation. Declare all is fine
+ // even if !AllSucceeded.
+ // TODO(motek@google.com): Perhaps we should post a different
+ // message post for 'not quite ok, but almost'?
+ set_value(result_accumulator_.release());
+ CallRealPostResult();
+ }
+ error_accumulator_.clear();
+ }
+
+ bool AllSucceeded() const {
+ DCHECK(result_accumulator_ != NULL);
+ return result_accumulator_ != NULL && !result_accumulator_->empty() &&
+ error_accumulator_.empty();
+ }
+
+ bool AllFailed() const {
+ DCHECK(result_accumulator_ != NULL);
+ return !error_accumulator_.empty() &&
+ (result_accumulator_ == NULL || result_accumulator_->empty());
+ }
+
+ bool IsEmpty() const {
+ DCHECK(result_accumulator_ != NULL);
+ return (result_accumulator_ == NULL || result_accumulator_->empty()) &&
+ error_accumulator_.empty();
+ }
+
+ const std::string LastError() const {
+ if (!error_accumulator_.empty())
+ return error_accumulator_.back();
+ return std::string();
+ }
+
+ private:
+ // Redirected invocations of base class's 'post function' create test seam.
+ virtual void CallRealPostResult() {
+ InvocationBase::PostResult();
+ }
+ virtual void CallRealPostError(const std::string& error) {
+ InvocationBase::PostError(error);
+ }
+
+ scoped_ptr<ListValue> result_accumulator_;
+ std::list<std::string> error_accumulator_;
+};
+
+
+#endif // CEEE_IE_BROKER_API_DISPATCHER_H_
diff --git a/ceee/ie/broker/api_dispatcher_docs.h b/ceee/ie/broker/api_dispatcher_docs.h
new file mode 100644
index 0000000..ae6b711
--- /dev/null
+++ b/ceee/ie/broker/api_dispatcher_docs.h
@@ -0,0 +1,420 @@
+// 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_BROKER_API_DISPATCHER_DOCS_H_ // Mainly for lint
+#define CEEE_IE_BROKER_API_DISPATCHER_DOCS_H_
+
+/** @page ApiDispatcherDoc Detailed documentation of the ApiDispatcher.
+
+@section ApiDispatcherIntro Introduction
+
+The API Dispatcher implemented by the ApiDispatcher class
+is used by the CeeeBroker object to dispatch execution of Api calls
+coming from the "Chrome Extensions Execution Environment" or CEEE.
+
+It keeps a registry of all API invocation implementations and takes care of
+the logic needed to de-serialize API function requests, dispatch them,
+and then serialize & return the response.
+
+One of the main complexities of the API invocation implementations is that some
+of them need to interact with windows, and these interactions must happen in
+the same thread that created the window. So the dispatcher need to delegate
+calls to Executor objects that they get from the ExecutorsManager.
+
+So the implementation of these APIs are actually split between the classes
+inheriting from ApiDispatcher::Invocation and the CeeeExecutor COM object.
+
+Another complexity is the fact that some of the APIs can not be completed
+synchronously (e.g., the tab creation is an asynchronous process), so the
+dispatcher must remember the pending execution and get them completed when a
+given event is fired.
+
+And to make it even more fun, there might be two concurrent API invocation
+that depend on the same event being fired (e.g., GetSelectedTab is called
+while a new tab is being created as selected, but the asynchronous selection
+isn't completed yet... pfffewww). So the list of pending asynchronous execution
+must allow more than one to be executed when a given event is fired.
+
+@section InvocationFactory Invocation Factory
+
+The ApiDispatcher uses the @link ApiDispatcher::Invocation Invocation @endlink
+nested class as a base for all API invocation implementation so that it can
+easily dispatch to them. It keeps pointers to registered static functions
+(called Invocation factories) that can be called to instantiation a new
+Invocation object in the @link ApiDispatcher::factories_ factories_ @endlink
+@link ApiDispatcher::FactoryMap map@endlink.
+
+When @link ApiDispatcher::HandleApiRequest HandleApiRequest @endlink is called,
+it looks up the Invocation Factory to find the Invocation object to execute with
+the data that it de-serialized from the JSON string it was given.
+
+@subsection InvocationExecution Invocation Execution
+
+The Invocation base class has an @link ApiDispatcher::Invocation::Execute
+Execute @endlink pure virtual method that is to be called by HandleApiRequest.
+This method receives the argument values, as well as a pointer to a string
+where to return the response.
+
+When the execution can be completed synchronously, the result (or the error
+message in case of failure) is written in the @link
+ApiDispatcher::Invocation::result_ result_ @endlink member variable
+(or @link ApiDispatcher::Invocation::error_ error_ @endlink for errors). If the
+execution must be completed asynchronously, a new AsynchronousHandler object
+is instantiated and stored in the @link
+ApiDispatcher::Invocation::async_handler_ async_handler_ @endlink data member
+so that the dispatcher can find it.
+
+@subsection ExecutorsDelegation Delegating to Executors
+
+When an Invocation implementation needs to interact with windows, it can do so
+by delegating the execution to an Executor object via the ICeeeTabExecutor
+or ICeeeWindowExecutor interface pointer that it can get from a call to
+ApiDispatcher::Invocation::GetExecutor which gets it from the Executors Manager.
+
+@section EventHandling Event Handling
+
+Event handling actually serves two purposes in the API Dispatcher, 1) handle
+events (of course) and 2) complete asynchronous executions. Events are received
+by the @link ApiDispatcher::FireEvent FireEvent @endlink member function.
+
+@subsection HandlingEvents Handling Events
+
+Events are being fired by the browser and must be propagated to extensions via
+the Chrome Frame. The @link EventDispatchingDoc Event Dispatching @endlink
+documentation describes how the EventFunnel derived classes can be used to help
+fire events. Once they got through the funnel, the events get to the API
+Dispatcher, which is responsible to post them to Chrome Frame.
+
+We do the event propagation to Chrome Frame from the API dispatcher so that
+we can also deal with the completion of the asynchronous API invocation at the
+same time... the same place...
+
+We also reuse some of the code written for API invocation since some of the
+events are posted with the same information that is returned to the API
+invocation callbacks (e.g., tabs.create and tabs.onCreated).
+
+So the ApiDispatcher::Invocation derived classes can be used to handle events
+by filling in the blanks in the arguments posted with the event (because some of
+these arguments can be filled by the source of the event, we only need to fill
+in the blanks that were left because, for example, the information must be
+fetched from other threads than the one from where the event come from). Those
+classes that can be used to handle events must expose a static method with the
+syntax defined as ApiDispatcher::EventHandler and register it by calling
+ApiDispatcher::RegisterEventHandler so that it get stored in the
+ApiDispatcher::EventHandlersMap.
+
+When ApiDispatcher::FireEvent is called, the event handlers map is looked up to
+see if a handler was registered for the given event. If so, the event handler
+is called to fill in the blanks in the event arguments. Then the newly filled
+argumentes can be posted with the event.
+
+@subsection AsynchronousResponse Asynchronous API Invocation response
+
+When an API invocation can't be completed synchronously, a new instance of an
+ApiDispatcher::AsynchronousHandler object is stored in the
+ApiDispatcher::AsyncHandlersMap. These objects are associated to a given
+event (which can be identified by calling the
+ApiDispatcher::AsyncHandlersMap::event_name member function). So when an event
+is fired, the map is looked up for the list of handlers registered for the
+given event name (because there can be more than one handler registerd for a
+given name, for the cases of concurrent API invocations, and also for cases
+where more than one invocation need to wait for the same event, e.g., the
+tabs.create and tabs.getSelected APIs).
+
+Since there may be more than one handler for a given event, and we may have
+pending handlers for more than one event with the same name, we need to be able
+to properly match the events with their pending handlers. To identify if an
+event is the one a specific handler is waiting for, we have to ask the handlers
+if they recognize it as theirs. Even if they do, we may still need to let
+other handlers process the same event. This is why a handler can specify that
+it is a pass-through handler (i.e., it doesn't swallow the event, it can let
+other handlers use it, even multiple instances of itself can all use the same
+one, as long as they recognize it as theirs). If a handler recognize an event
+as its own, and isn't a pass-through handler, then, we must stop looking for
+more handlers.
+
+This means that all pass-through handlers must be placed at the beginning of
+the list of handlers for a given event, and all the other ones (the
+non-pass-through handlers) must be put at the end.
+
+@section InvocationImplementation Invocation Implementation
+
+The implementation of API invocations are done by classes derived from the
+Invocation nested class of the ApiDispatcher class. Here is the current list of
+those derived classes and some details about their implementation.
+
+@subsection WindowsApi Windows API
+
+Here is the list of invocations implementing the Windows API.
+You can also read a more detailed information about the Chrome Extension
+Windows api here: http://code.google.com/chrome/extensions/windows.html
+
+@subsubsection GetWindow
+
+This invocation receives a single argument which is the identifier of the window
+to return information for. The window identifier is simply the window's HWND
+value. We only provide such information for IEFrame windows (so we validate
+that before continuing) and since we must get this information from the thread
+that created the window, we delegate this call to the @link
+ICeeeWindowExecutor::GetWindow @endlink method of the
+ICeeeWindowExecutor interface.
+
+The returned information contains the following:
+<table>
+ <tr><td>type</td><td>Info</td></tr>
+ <tr><td>int</td><td>Window identifier</td></tr>
+ <tr><td>bool</td><td>Focused</td></tr>
+ <tr><td>int</td><td>Left position</td></tr>
+ <tr><td>int</td><td>Top position</td></tr>
+ <tr><td>int</td><td>Width</td></tr>
+ <tr><td>int</td><td>Height</td></tr>
+</table>
+
+@subsubsection GetCurrentWindow
+
+This invocation does the same thing as @ref GetWindow except that it doesn't
+receive a windows identifier argument (it doesn't receive any argument). Instead
+it must find the current window from where the invocation started from.
+
+@note We currently don't have a way to find the window from which the invocation
+came from, so until we do, we return the top most IE Frame window as
+@ref GetLastFocusedWindow does.
+
+@subsubsection GetLastFocusedWindow
+
+This invocation return information about the last focused IE window, which it
+gets by calling @link window_api::WindowApiResult::TopIeWindow
+TopIeWindow@endlink, which finds it by calling
+window_utils::FindDescendentWindow with NULL as the parent (which means to ask
+for top level windows).
+
+@subsubsection CreateWindowFunc
+
+This invocation creates a new window with the parameters specified in the
+received argument and navigate it to an optionally provided URL or about:blank
+otherwise. It creates the new window by simply calling the
+ICeeeTabExecutor::Navigate method with a _blank target and the
+navOpenInNewWindow flag.
+
+It then needs to find that window (by diff'ing the list of IEFrame windows
+before and after the navigation), and then it can apply the provided parameters.
+
+The provided parameters are as follows (and are all optional):
+<table>
+<tr><td>type</td><td>Info</td></tr>
+<tr><td>string</td><td>URL</td></tr>
+<tr><td>int</td><td>Left position</td></tr>
+<tr><td>int</td><td>Top position</td></tr>
+<tr><td>int</td><td>Width</td></tr>
+<tr><td>int</td><td>Height</td></tr>
+</table>
+
+The application of the window parameters must be done in the same thread that
+created the window, so we must delegate this task to the @link
+ICeeeWindowExecutor::UpdateWindow UpdateWindow @endlink method of
+the ICeeeWindowExecutor interface. This UpdateWindow method also returns
+information about the updated window so that we can return it. The returned
+information is the same as the one returned by the @ref GetWindow method
+described above.
+
+@subsubsection UpdateWindow
+
+This invocation updates an existing window. So it must be given the window
+identifier, as well as the new parameters to update the window with.
+
+It simply reuses the same code as the @ref CreateWindowFunc invocation, except
+that it doesn't create a new window, it modifies the one specified by the
+identifier, after validating that it is indeed an IEFrame window.
+
+@subsubsection RemoveWindow
+
+This invocation simply closes the window specified as a windows identifier
+argument. It simply delegates the call to the @link
+ICeeeWindowExecutor::RemoveWindow RemoveWindow @endlink method of the
+ICeeeWindowExecutor interface.
+
+@subsubsection GetAllWindows
+
+This invocation returns an array of window information, one for each of the
+current IEFrame window. It also fills an extra array of tabs information if the
+populate flag is set in the single (and optional, defaults to false) argument.
+
+The information returned for each window is the same as in the @ref GetWindow
+invocation. The information for each tab (if requested) is provided as an extra
+entry in the window information in the form of an array of tab information. The
+tab information is the same as the one returned by the @ref GetTab invocation
+described below.
+
+It delegates the call to the @link ICeeeWindowExecutor::GetAllWindows
+GetAllWindows @endlink method of the ICeeeWindowExecutor interface. This
+methods can't return all the information about every tab since the windows for
+these tabs may have been created in other threads/processes. So it only returns
+the identifiers of these tabs. Then we must delegate to different executors for
+each of the tabs potentially created in different threads/process by calling the
+@link ICeeeTabExecutor::GetTab GetTab @endlink method of the
+ICeeeTabExecutor interface as is done for the @ref GetTab API described
+below. We do the same in @ref GetAllTabsInWindow described below.
+
+@subsection TabsApi Tabs API
+
+Here is the list of invocations implementing the Tabs API.
+You can also read a more detailed information about the Chrome Extension
+Tabs api here: http://code.google.com/chrome/extensions/tabs.html
+
+@subsubsection GetTab
+
+This invocation receives a single argument which is the identifier of the tab
+to return information for. The tab identifier is the HWND value of the window
+of class "TabWindowClass" that holds on the real tab window.
+
+We only provide such information for these windows (so we validate
+that before continuing). Also, we must get some of the information from the
+thread that created the tab window, and some other information are only
+available via the tab window manager which lives in the thread that created the
+frame window. So we must delegate this call in two phases, the first
+one to the @link ICeeeTabExecutor::GetTab GetTab @endlink method of the
+ICeeeTabExecutor interface which returns the following information:
+<table>
+<tr><td>type</td><td>Info</td></tr>
+<tr><td>string</td><td>URL</td></tr>
+<tr><td>string</td><td>Title</td></tr>
+<tr><td>string</td><td>Loading/Complete status</td></tr>
+<tr><td>string</td><td>favIconUrl (not supported yet in CEEE)</td></tr>
+</table>
+
+The information above is retrivied via the HTML Document and Web Browser
+associated to the given tab, and these must be fetched and interacted with from
+the same thread that created the tab window.
+
+So we are left with the following:
+<table>
+<tr><td>type</td><td>Info</td></tr>
+<tr><td>int</td><td>Tab identifier</td></tr>
+<tr><td>int</td><td>Window identifier</td></tr>
+<tr><td>bool</td><td>Selected state</td></tr>
+<tr><td>int</td><td>Tab index</td></tr>
+</table>
+
+Most of the other information about the tab can be retrieved from any thread
+by simply using Win32 calls. The tab identifier is the HWND of the window of
+class "TabWindowClass", the window identifier is simply the HWND of
+the top level parent of the tab window, and the selected state can be determined
+by the visibility of the window which we can get from anywhere.
+
+The last piece of information, the tab index, must be retrieved from the tab
+window manager. We must get another executor so that we can
+run it in the appropriate thread. We do this via the @link
+ICeeeTabExecutor::GetTabIndex GetTabIndex @endlink method of the
+ICeeeTabExecutor interface.
+
+@subsubsection GetSelectedTab
+
+This invocation does the same thing as @ref GetTab except that it doesn't
+receive a tab identifier argument, it gets the information of the currently
+selected tab instead. But it can receive an optional window identifier argument,
+specifying which window's selected tab we want. If no window identifier is
+specified, we use the current window. The current window is the same as the one
+returned from @ref GetCurrentWindow.
+
+@subsubsection GetAllTabsInWindow
+
+This invocation returns the information for all the tabs of a given window. If
+no window identifier is provided as an optional argument, then the current
+window is used instead. The returned information is an array of tab information.
+The tab information is the same as the one returned by @ref GetTab.
+
+We delegate the first part of this call to the
+@link ICeeeTabExecutor::GetAllTabs GetAllTabs @endlink method of the
+ICeeeTabExecutor interface which gets the list of tabs from the tab window
+manager in the frame window thread. Then we need to sequentially call different
+instances of executors to call their @ref GetTab method, since they were not
+created in the same thread where GetAllTabs needs to be executed.
+
+@subsubsection CreateTab
+
+This invocation creates a new tab with the parameters specified in the received
+argument and navigate it to an optionally provided URL or about:blank otherwise.
+
+The new tab is to be created in a specified window or the current window if the
+optional window identifier is not provided as an argument. The new tab
+is created by first finding an existing tab in the any window and then
+calling ICeeeTabExecutor::Navigate with the appropriate URL with the _blank
+target and the navOpenInNewTab or navOpenInBackgroundTab whether the selected
+bool optional parameter is set to true or not (defaults to true).
+
+The new tab may also need to be moved to a specific index if the optional index
+parameter argument was provided. This can be done using the tab window manager
+so must be done in the appropriate thread by delegating this task to the @link
+ICeeeTabExecutor::MoveTab MoveTab @endlink method of the
+ICeeeTabExecutor interface.
+
+And finally, the same tab information as from @ref GetTab must be returned.
+Since some of this information isn't available right after the call to Navigate,
+we must handle the rest of the CreateTab API invocation Asynchronously. We
+register the async handler to be called when the tabs.onCreated event is fired.
+Then, we know that all the information is available to return the tab info.
+
+The provided parameters are as follows (and are all optional):
+<table>
+<tr><td>type</td><td>Info</td></tr>
+<tr><td>int</td><td>Parent window identifier</td></tr>
+<tr><td>int</td><td>Index</td></tr>
+<tr><td>string</td><td>URL</td></tr>
+<tr><td>bool</td><td>Selected</td></tr>
+</table>
+
+@note <b>TODO(mad@chromium.org)</b>: We currently can't get the tab id of the
+newly created tab since it is created asynchronously. So we will need to return
+this info asynchronously once we have the support for asynchronous events.
+
+@subsubsection UpdateTab
+
+This invocation updates an existing tab. So it must be given the tab identifier,
+as well as the new parameters to update the tab with.
+
+If the optional URL parameter is provided as an argument, then we must call
+ICeeeTabExecutor::Navigate with this new URL (if it is different from the
+current one).
+
+If the optional selected parameter is provided, then we must use the tab window
+manager to select the tab. But we must do it in the IE Frame thread so we must
+delegate the tab selection to the @link ICeeeTabExecutor::SelectTab
+SelectTab @endlink method of another instance of
+the executor object implementing the ICeeeTabExecutor interface, since it
+must be execute in the thread that created the frame window, this is the one we
+must use when accessing the ITabWindowManager interface.
+
+No information is returned from UpdateTab.
+
+@subsubsection MoveTab
+
+This invocation moves the tab from one index to another. So it must receive the
+identifier of the tab, as well as the destination index. An optional window
+identifier can also be provided if we want to specify in which window we want
+to move the tab. When not specified, the window where the tab currently lies is
+used (and it is the only case that is currently supported in CEEE).
+
+This code is delegated to the @link ICeeeTabExecutor::MoveTab MoveTab
+@endlink method of the ICeeeTabExecutor interface.
+
+No information is returned from MoveTab.
+
+@subsubsection RemoveTab
+
+This invocation closes a tab identified by the single mandatory parameter
+argument. The removal of the tab is delegated to the ITabWindowManager interface
+so it must be executed in the IEFrame thread by calling the @link
+ICeeeTabExecutor::RemoveTab RemoveTab @endlink method on
+the ICeeeTabExecutor interface.
+
+@note <b>TODO(mad@chromium.org)</b>: An alternative to having the RemoveTab and
+MoveTab and SelectTab methods on the ICeeeTabExecutor interface, would be to
+simply have a GetTabWindowManager method which returns a proxy to the
+ITabWindowManager interface that we can safely use from the Broker process.
+
+No information is returned from RemoveTab.
+
+**/
+
+#endif // CEEE_IE_BROKER_API_DISPATCHER_DOCS_H_
diff --git a/ceee/ie/broker/api_dispatcher_unittest.cc b/ceee/ie/broker/api_dispatcher_unittest.cc
new file mode 100644
index 0000000..1987733
--- /dev/null
+++ b/ceee/ie/broker/api_dispatcher_unittest.cc
@@ -0,0 +1,275 @@
+// 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 ApiDispatcher.
+
+#include "base/json/json_writer.h"
+#include "base/logging.h"
+#include "ceee/ie/broker/api_dispatcher.h"
+#include "ceee/ie/broker/chrome_postman.h"
+#include "ceee/testing/utils/test_utils.h"
+#include "chrome/browser/automation/extension_automation_constants.h"
+#include "testing/gmock/include/gmock/gmock.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+
+namespace {
+
+namespace keys = extension_automation_constants;
+
+using testing::_;
+using testing::StrEq;
+using testing::StrictMock;
+
+MATCHER_P(ValuesEqual, value, "") {
+ return arg.Equals(value);
+}
+
+class MockInvocation
+ : public ApiDispatcher::Invocation {
+ public:
+ MOCK_METHOD2(Execute, void(const ListValue& args, int request_id));
+
+ // A factory for the registry.
+ static ApiDispatcher::Invocation* TestingInstance() {
+ ApiDispatcher::Invocation* return_value = testing_instance_.release();
+ testing_instance_.reset(new MockInvocation);
+ return return_value;
+ }
+
+ static scoped_ptr<MockInvocation> testing_instance_;
+};
+
+scoped_ptr<MockInvocation> MockInvocation::testing_instance_;
+
+
+TEST(ApiDispatcher, InvalidRegistrationNoCrash) {
+ testing::LogDisabler no_dchecks;
+
+ ApiDispatcher dispatcher;
+ dispatcher.RegisterInvocation(NULL, NewApiInvocation<MockInvocation>);
+ dispatcher.RegisterInvocation("hi", NULL);
+}
+
+TEST(ApiDispatcher, DuplicateRegistrationNoCrash) {
+ testing::LogDisabler no_dchecks;
+
+ ApiDispatcher dispatcher;
+ dispatcher.RegisterInvocation("hi", NewApiInvocation<MockInvocation>);
+ dispatcher.RegisterInvocation("hi", NewApiInvocation<MockInvocation>);
+}
+
+// Encodes a request in JSON. Optionally skip encoding one of the required
+// keys.
+std::string MakeRequest(const char* function_name, const Value* args,
+ int request_id, const std::string& skip_key = "") {
+ std::string json;
+ DictionaryValue value;
+ if (skip_key != keys::kAutomationNameKey)
+ value.SetString(keys::kAutomationNameKey, function_name);
+ if (skip_key != keys::kAutomationArgsKey) {
+ std::string json;
+ if (args) {
+ base::JSONWriter::Write(args, false, &json);
+ }
+ value.SetString(keys::kAutomationArgsKey, json);
+ }
+ if (skip_key != keys::kAutomationRequestIdKey)
+ value.SetInteger(keys::kAutomationRequestIdKey, request_id);
+ base::JSONWriter::Write(&value, false, &json);
+ return json;
+}
+
+static const int kRequestId = 42;
+
+class ApiDispatcherTests : public testing::Test {
+ public:
+ virtual void SetUp() {
+ // We must create the testing instance before the factory returns it
+ // so that we can set expectations on it beforehand.
+ MockInvocation::testing_instance_.reset(new MockInvocation);
+ }
+ virtual void TearDown() {
+ // We don't want to wait for the static instance to get destroyed,
+ // it will be too late and will look like we leaked.
+ MockInvocation::testing_instance_.reset(NULL);
+ }
+};
+
+
+TEST_F(ApiDispatcherTests, BasicDispatching) {
+ testing::LogDisabler no_dchecks;
+
+ scoped_ptr<ListValue> args1(new ListValue());
+ args1->Append(Value::CreateStringValue("hello world"));
+ scoped_ptr<ListValue> args2(new ListValue());
+ args2->Append(Value::CreateIntegerValue(711));
+ scoped_ptr<ListValue> args3(new ListValue());
+ args3->Append(Value::CreateBooleanValue(true));
+ DictionaryValue* dict_value = new DictionaryValue();
+ dict_value->SetString("foo", "moo");
+ dict_value->SetInteger("blat", 42);
+ scoped_ptr<ListValue> args4(new ListValue());
+ args4->Append(dict_value);
+
+ ListValue* values[] = { args1.get(), args2.get(), args3.get(), args4.get() };
+
+ for (int i = 0; i < arraysize(values); ++i) {
+ ListValue* test_value = values[i];
+ EXPECT_CALL(*MockInvocation::testing_instance_,
+ Execute(ValuesEqual(test_value), kRequestId)).Times(1);
+
+ ApiDispatcher dispatcher;
+ dispatcher.RegisterInvocation("DoStuff", &MockInvocation::TestingInstance);
+ std::string json = MakeRequest("DoStuff", test_value, kRequestId);
+ dispatcher.HandleApiRequest(CComBSTR(json.c_str()), NULL);
+ }
+}
+
+TEST_F(ApiDispatcherTests, ErrorHandlingDispatching) {
+ testing::LogDisabler no_dchecks;
+
+ ApiDispatcher dispatcher;
+ dispatcher.RegisterInvocation("DoStuff", &MockInvocation::TestingInstance);
+
+ std::string test_data[] = {
+ MakeRequest("DoStuff", NULL, 42, keys::kAutomationNameKey),
+ MakeRequest("DoStuff", NULL, 43, keys::kAutomationArgsKey),
+ MakeRequest("DoStuff", NULL, 44, keys::kAutomationRequestIdKey),
+ MakeRequest("NotRegistered", NULL, 45),
+ };
+
+ for (int i = 0; i < arraysize(test_data); ++i) {
+ EXPECT_CALL(*MockInvocation::testing_instance_, Execute(_, _)).Times(0);
+ CComBSTR json(test_data[i].c_str());
+ dispatcher.HandleApiRequest(json, NULL);
+ }
+}
+
+class MockChromePostman : public ChromePostman {
+ public:
+ MOCK_METHOD2(PostMessage, void(BSTR, BSTR));
+};
+
+TEST(ApiDispatcher, PostResult) {
+ testing::LogDisabler no_dchecks;
+
+ // Simply instantiating the postman makes it the single instance
+ // available to all clients.
+ CComObjectStackEx< StrictMock< MockChromePostman > > postman;
+
+ // We use different (numbered) result instances, because they are not
+ // meant to post more than once (and we validate that too).
+ ApiDispatcher::InvocationResult result1(kRequestId);
+
+ // First check that we can correctly create an error response.
+ static const char* kError = "error";
+ DictionaryValue expected_value;
+ expected_value.SetInteger(keys::kAutomationRequestIdKey, kRequestId);
+ expected_value.SetString(keys::kAutomationErrorKey, kError);
+
+ std::string expected_response;
+ base::JSONWriter::Write(&expected_value, false, &expected_response);
+
+ CComBSTR request_name(keys::kAutomationResponseTarget);
+ EXPECT_CALL(postman, PostMessage(
+ StrEq(CComBSTR(expected_response.c_str()).m_str),
+ StrEq(request_name.m_str))).Times(1);
+ result1.PostError(kError);
+
+ // PostResult and PostError must not call the postman anymore,
+ // and StrictMock will validate it.
+ result1.PostResult();
+ result1.PostError(kError);
+
+ // Now check that we can create an empty response when there is no value.
+ expected_value.Remove(keys::kAutomationErrorKey, NULL);
+ expected_value.SetString(keys::kAutomationResponseKey, "");
+ base::JSONWriter::Write(&expected_value, false, &expected_response);
+
+ EXPECT_CALL(postman, PostMessage(
+ StrEq(CComBSTR(expected_response.c_str()).m_str),
+ StrEq(request_name.m_str))).Times(1);
+ ApiDispatcher::InvocationResult result2(kRequestId);
+ result2.PostResult();
+ // These must not get to PostMan, and StrictMock will validate it.
+ result2.PostError("");
+ result2.PostResult();
+
+ // And now check that we can create an full response.
+ ApiDispatcher::InvocationResult result3(kRequestId);
+ result3.set_value(Value::CreateIntegerValue(42));
+ std::string result_str;
+ base::JSONWriter::Write(result3.value(), false, &result_str);
+
+ expected_value.SetString(keys::kAutomationResponseKey, result_str);
+ base::JSONWriter::Write(&expected_value, false, &expected_response);
+
+ EXPECT_CALL(postman, PostMessage(
+ StrEq(CComBSTR(expected_response.c_str()).m_str),
+ StrEq(request_name.m_str))).Times(1);
+ result3.PostResult();
+ // These must not get to PostMan, and StrictMock will validate it.
+ result3.PostResult();
+ result3.PostError("");
+}
+
+bool EventHandler1(const std::string& input_args, std::string* converted_args,
+ ApiDispatcher* dispatcher) {
+ EXPECT_STREQ(input_args.c_str(), "EventHandler1Args");
+ EXPECT_TRUE(converted_args != NULL);
+ *converted_args = "EventHandler1ConvertedArgs";
+ return true;
+}
+
+bool EventHandler2(const std::string& input_args, std::string* converted_args,
+ ApiDispatcher* dispatcher) {
+ EXPECT_STREQ(input_args.c_str(), "EventHandler2Args");
+ EXPECT_TRUE(converted_args != NULL);
+ *converted_args = "EventHandler2ConvertedArgs";
+ return true;
+}
+
+bool EventHandler3(const std::string& input_args, std::string* converted_args,
+ ApiDispatcher* dispatcher) {
+ return false;
+}
+
+TEST(ApiDispatcher, PermanentEventHandler) {
+ testing::LogDisabler no_dchecks;
+
+ // Simply instantiating the postman makes it the single instance
+ // available to all clients.
+ CComObjectStackEx< StrictMock< MockChromePostman > > postman;
+ ApiDispatcher dispatcher;
+ dispatcher.RegisterPermanentEventHandler("Event1", EventHandler1);
+ dispatcher.RegisterPermanentEventHandler("Event2", EventHandler2);
+ dispatcher.RegisterPermanentEventHandler("Event3", EventHandler3);
+
+ ListValue message1;
+ message1.Append(Value::CreateStringValue("Event1"));
+ message1.Append(Value::CreateStringValue("EventHandler1ConvertedArgs"));
+ std::string message1_str;
+ base::JSONWriter::Write(&message1, false, &message1_str);
+ CComBSTR request_name(keys::kAutomationBrowserEventRequestTarget);
+ EXPECT_CALL(postman, PostMessage(StrEq(CComBSTR(message1_str.c_str()).m_str),
+ StrEq(request_name.m_str))).Times(1);
+ dispatcher.FireEvent(CComBSTR("Event1"), CComBSTR("EventHandler1Args"));
+
+ ListValue message2;
+ message2.Append(Value::CreateStringValue("Event2"));
+ message2.Append(Value::CreateStringValue("EventHandler2ConvertedArgs"));
+ std::string message2_str;
+ base::JSONWriter::Write(&message2, false, &message2_str);
+ EXPECT_CALL(postman, PostMessage(StrEq(CComBSTR(message2_str.c_str()).m_str),
+ StrEq(request_name.m_str))).Times(1);
+ dispatcher.FireEvent(CComBSTR("Event2"), CComBSTR("EventHandler2Args"));
+
+ // There shouldn't be a post when the event handler returns false.
+ dispatcher.FireEvent(CComBSTR("Event3"), CComBSTR(""));
+}
+
+// TODO(mad@chromium.org): Add tests for the EphemeralEventHandlers.
+
+} // namespace
diff --git a/ceee/ie/broker/api_module_constants.cc b/ceee/ie/broker/api_module_constants.cc
new file mode 100644
index 0000000..a24a830d
--- /dev/null
+++ b/ceee/ie/broker/api_module_constants.cc
@@ -0,0 +1,14 @@
+// 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.
+
+// Constants used by the API modules.
+
+#include "ceee/ie/broker/api_module_constants.h"
+
+namespace api_module_constants {
+
+const char kInvalidArgumentsError[] = "Invalid arguments.";
+const char kInternalErrorError[] = "Internal error.";
+
+} // namespace api_module_constants
diff --git a/ceee/ie/broker/api_module_constants.h b/ceee/ie/broker/api_module_constants.h
new file mode 100644
index 0000000..0555531
--- /dev/null
+++ b/ceee/ie/broker/api_module_constants.h
@@ -0,0 +1,18 @@
+// 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.
+
+// Constants used by the API modules.
+
+#ifndef CEEE_IE_BROKER_API_MODULE_CONSTANTS_H_
+#define CEEE_IE_BROKER_API_MODULE_CONSTANTS_H_
+
+namespace api_module_constants {
+
+// Errors.
+extern const char kInvalidArgumentsError[];
+extern const char kInternalErrorError[];
+
+} // namespace api_module_constants
+
+#endif // CEEE_IE_BROKER_API_MODULE_CONSTANTS_H_
diff --git a/ceee/ie/broker/api_module_util.cc b/ceee/ie/broker/api_module_util.cc
new file mode 100644
index 0000000..96c8568
--- /dev/null
+++ b/ceee/ie/broker/api_module_util.cc
@@ -0,0 +1,65 @@
+// 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.
+
+// Utilities used by the API modules.
+
+#include "ceee/ie/broker/api_module_util.h"
+
+namespace api_module_util {
+
+bool GetListFromJsonString(const std::string& input_list_args,
+ scoped_ptr<ListValue>* list) {
+ DCHECK(list != NULL);
+ scoped_ptr<Value> input_args_val(base::JSONReader::Read(input_list_args,
+ true));
+ DCHECK(input_args_val != NULL && input_args_val->IsType(Value::TYPE_LIST));
+ if (input_args_val == NULL || !input_args_val->IsType(Value::TYPE_LIST))
+ return false;
+ list->reset(static_cast<ListValue*>(input_args_val.release()));
+ return true;
+}
+
+Value* GetListAndFirstValue(const std::string& input_list_args,
+ scoped_ptr<ListValue>* list) {
+ if (!GetListFromJsonString(input_list_args, list))
+ return NULL;
+ Value* value = NULL;
+ if (!(*list)->Get(0, &value) || value == NULL) {
+ DCHECK(false) << "Input arguments are not a non-empty list.";
+ }
+ return value;
+}
+
+bool GetListAndIntegerValue(const std::string& input_list_args,
+ scoped_ptr<ListValue>* list,
+ int* int_value) {
+ Value* value = GetListAndFirstValue(input_list_args, list);
+ DCHECK(value != NULL && value->IsType(Value::TYPE_INTEGER));
+ if (value == NULL || !value->IsType(Value::TYPE_INTEGER)) {
+ return false;
+ }
+ return value->GetAsInteger(int_value);
+}
+
+DictionaryValue* GetListAndDictionaryValue(const std::string& input_list_args,
+ scoped_ptr<ListValue>* list) {
+ Value* value = GetListAndFirstValue(input_list_args, list);
+ DCHECK(value != NULL && value->IsType(Value::TYPE_DICTIONARY));
+ if (value == NULL || !value->IsType(Value::TYPE_DICTIONARY)) {
+ return NULL;
+ }
+ return static_cast<DictionaryValue*>(value);
+}
+
+bool GetListAndDictIntValue(const std::string& input_list_args,
+ const char* dict_value_key_name,
+ scoped_ptr<ListValue>* list,
+ int* int_value) {
+ DictionaryValue* dict = GetListAndDictionaryValue(input_list_args, list);
+ if (dict == NULL)
+ return false;
+ return dict->GetInteger(dict_value_key_name, int_value);
+}
+
+} // namespace api_module_util
diff --git a/ceee/ie/broker/api_module_util.h b/ceee/ie/broker/api_module_util.h
new file mode 100644
index 0000000..e8bc9c2
--- /dev/null
+++ b/ceee/ie/broker/api_module_util.h
@@ -0,0 +1,101 @@
+// 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.
+
+// Utilities used by the API modules.
+
+#ifndef CEEE_IE_BROKER_API_MODULE_UTIL_H_
+#define CEEE_IE_BROKER_API_MODULE_UTIL_H_
+
+#include <string>
+
+#include "toolband.h" //NOLINT
+
+#include "base/json/json_reader.h"
+#include "base/json/json_writer.h"
+#include "ceee/ie/broker/api_dispatcher.h"
+
+namespace api_module_util {
+
+// Helper function to fetch a list from a JSON string.
+bool GetListFromJsonString(const std::string& input_list_args,
+ scoped_ptr<ListValue>* list);
+
+// Helper function to fetch a list from a JSON string and return its
+// first Value. Note that the Value allocation is owned by the list.
+// Returns NULL on failures.
+Value* GetListAndFirstValue(const std::string& input_list_args,
+ scoped_ptr<ListValue>* list);
+
+// Helper function to fetch a list from a JSON string and return its
+// first Value as an integer. Returns false on failures.
+bool GetListAndIntegerValue(const std::string& input_list_args,
+ scoped_ptr<ListValue>* list,
+ int* int_value);
+
+// Helper function to fetch a list from a JSON string and return its
+// first Value as a DictionaryValue. Note that the DictionaryValue
+// allocation is owned by the list. Returns NULL on failures.
+DictionaryValue* GetListAndDictionaryValue(const std::string& input_list_args,
+ scoped_ptr<ListValue>* list);
+
+// Helper function to fetch a list from a JSON string, get its
+// first Value as a DictionaryValue and extracts an int from the dict using
+// the provided key name. Returns false on failures.
+bool GetListAndDictIntValue(const std::string& input_list_args,
+ const char* dict_value_key_name,
+ scoped_ptr<ListValue>* list,
+ int* int_value);
+
+// A function that can be used as a permanent event handler, which converts the
+// tab window handle in the input arguments into the corresponding tab ID.
+// @tparam tab_id_key_name The key name for the tab ID.
+// @param input_args A list of arguments in the form of a JSON string. The first
+// argument is a dictionary. It contains the key tab_id_key_name, whose
+// corresponding value is the tab window handle.
+// @param converted_args On success returns a JSON string, in which the tab
+// window handle has been replaced with the actual tab ID; otherwise
+// returns input_args.
+// @param dispatcher The dispatcher used to query tab IDs using tab window
+// handles.
+template<const char* tab_id_key_name>
+bool ConvertTabIdInDictionary(const std::string& input_args,
+ std::string* converted_args,
+ ApiDispatcher* dispatcher) {
+ if (converted_args == NULL || dispatcher == NULL) {
+ NOTREACHED();
+ return false;
+ }
+ // Fail safe...
+ *converted_args = input_args;
+
+ scoped_ptr<ListValue> input_list;
+ DictionaryValue* input_dict = GetListAndDictionaryValue(input_args,
+ &input_list);
+ if (input_dict == NULL) {
+ LOG(ERROR)
+ << "Failed to get the details object from the list of arguments.";
+ return false;
+ }
+
+ int tab_handle = -1;
+ bool success = input_dict->GetInteger(tab_id_key_name, &tab_handle);
+ if (!success) {
+ LOG(ERROR) << "Failed to get " << tab_id_key_name << " property.";
+ return false;
+ }
+
+ HWND tab_window = reinterpret_cast<HWND>(tab_handle);
+ int tab_id = kInvalidChromeSessionId;
+ if (tab_window != INVALID_HANDLE_VALUE) {
+ tab_id = dispatcher->GetTabIdFromHandle(tab_window);
+ }
+ input_dict->SetInteger(tab_id_key_name, tab_id);
+
+ base::JSONWriter::Write(input_list.get(), false, converted_args);
+ return true;
+}
+
+} // namespace api_module_util
+
+#endif // CEEE_IE_BROKER_API_MODULE_UTIL_H_
diff --git a/ceee/ie/broker/broker.cc b/ceee/ie/broker/broker.cc
new file mode 100644
index 0000000..24ed0da
--- /dev/null
+++ b/ceee/ie/broker/broker.cc
@@ -0,0 +1,72 @@
+// 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.
+//
+// ICeeeBroker implementation
+
+#include "ceee/ie/broker/broker.h"
+
+#include "base/logging.h"
+#include "ceee/ie/broker/api_dispatcher.h"
+#include "ceee/ie/broker/chrome_postman.h"
+#include "ceee/ie/broker/executors_manager.h"
+#include "ceee/ie/common/ceee_module_util.h"
+
+
+HRESULT CeeeBroker::FinalConstruct() {
+ // So that we get a pointer to the ExecutorsManager and let tests override it.
+ executors_manager_ = Singleton<ExecutorsManager,
+ ExecutorsManager::SingletonTraits>::get();
+ api_dispatcher_ = ProductionApiDispatcher::get();
+ return S_OK;
+}
+
+void CeeeBroker::OnAddConnection(bool first_lock) {
+ if (first_lock)
+ ceee_module_util::LockModule();
+}
+
+void CeeeBroker::OnReleaseConnection(bool last_unlock,
+ bool last_unlock_releases) {
+ if (last_unlock)
+ ceee_module_util::UnlockModule();
+ IExternalConnectionImpl<CeeeBroker>::OnReleaseConnection(
+ last_unlock, last_unlock_releases);
+}
+
+STDMETHODIMP CeeeBroker::Execute(BSTR function, BSTR* response) {
+ // This is DEPRECATED and we should use ChromePostman (see FireEvent).
+ api_dispatcher_->HandleApiRequest(function, response);
+ return S_OK;
+}
+
+STDMETHODIMP CeeeBroker::FireEvent(BSTR event_name, BSTR event_args) {
+ ChromePostman::GetInstance()->FireEvent(event_name, event_args);
+ return S_OK;
+}
+
+STDMETHODIMP CeeeBroker::RegisterWindowExecutor(long thread_id,
+ IUnknown* executor) {
+ // TODO(mad@chromium.org): Add security check here.
+ return executors_manager_->RegisterWindowExecutor(thread_id, executor);
+}
+
+STDMETHODIMP CeeeBroker::UnregisterExecutor(long thread_id) {
+ // TODO(mad@chromium.org): Add security check here.
+ return executors_manager_->RemoveExecutor(thread_id);
+}
+
+STDMETHODIMP CeeeBroker::RegisterTabExecutor(long thread_id,
+ IUnknown* executor) {
+ // TODO(mad@chromium.org): Implement the proper manual/secure registration.
+ return executors_manager_->RegisterTabExecutor(thread_id, executor);
+}
+
+STDMETHODIMP CeeeBroker::SetTabIdForHandle(long tab_id,
+ CeeeWindowHandle handle) {
+ // TODO(mad@chromium.org): Add security check here.
+ DCHECK(tab_id != kInvalidChromeSessionId &&
+ handle != reinterpret_cast<CeeeWindowHandle>(INVALID_HANDLE_VALUE));
+ executors_manager_->SetTabIdForHandle(tab_id, reinterpret_cast<HWND>(handle));
+ return S_OK;
+}
diff --git a/ceee/ie/broker/broker.gyp b/ceee/ie/broker/broker.gyp
new file mode 100644
index 0000000..422eeac
--- /dev/null
+++ b/ceee/ie/broker/broker.gyp
@@ -0,0 +1,105 @@
+# 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': 'broker',
+ 'type': 'static_library',
+ 'dependencies': [
+ '../common/common.gyp:ie_common',
+ '../common/common.gyp:ie_common_settings',
+ '../plugin/toolband/toolband.gyp:toolband_idl',
+ '../../../base/base.gyp:base',
+ '../../../build/temp_gyp/googleurl.gyp:googleurl',
+ '../../../ceee/common/common.gyp:initializing_coclass',
+ '../../../ceee/common/common.gyp:ceee_common',
+ ],
+ 'sources': [
+ 'api_dispatcher.cc',
+ 'api_dispatcher.h',
+ 'api_dispatcher_docs.h',
+ 'api_module_constants.cc',
+ 'api_module_constants.h',
+ 'api_module_util.cc',
+ 'api_module_util.h',
+ 'broker.cc',
+ 'broker.h',
+ 'broker_docs.h',
+ 'chrome_postman.cc',
+ 'chrome_postman.h',
+ 'common_api_module.cc',
+ 'common_api_module.h',
+ 'cookie_api_module.cc',
+ 'cookie_api_module.h',
+ 'event_dispatching_docs.h',
+ 'executors_manager.cc',
+ 'executors_manager.h',
+ 'executors_manager_docs.h',
+ 'infobar_api_module.cc',
+ 'infobar_api_module.h',
+ '../common/precompile.cc',
+ '../common/precompile.h',
+ 'tab_api_module.cc',
+ 'tab_api_module.h',
+ 'webnavigation_api_module.cc',
+ 'webnavigation_api_module.h',
+ 'webrequest_api_module.cc',
+ 'webrequest_api_module.h',
+ 'window_api_module.cc',
+ 'window_api_module.h',
+ 'window_events_funnel.cc',
+ 'window_events_funnel.h',
+ ],
+ 'configurations': {
+ 'Debug': {
+ 'msvs_precompiled_source': '../common/precompile.cc',
+ 'msvs_precompiled_header': '../common/precompile.h',
+ },
+ },
+ },
+ {
+ # IF YOU CHANGE THIS TARGET NAME YOU MUST UPDATE:
+ # ceee_module_util::kCeeeBrokerModuleName
+ 'target_name': 'ceee_broker',
+ 'type': 'executable',
+ 'sources': [
+ 'broker.rgs',
+ 'broker_module.cc',
+ 'broker_module.rc',
+ 'broker_module.rgs',
+ 'broker_module_util.h',
+ 'resource.h'
+ ],
+ 'dependencies': [
+ 'broker',
+ '../common/common.gyp:ie_common_settings',
+ '../common/common.gyp:ie_guids',
+ '../plugin/toolband/toolband.gyp:toolband_idl',
+ '../../../base/base.gyp:base',
+ '../../../breakpad/breakpad.gyp:breakpad_handler',
+ '../../../ceee/common/common.gyp:ceee_common',
+ '<(DEPTH)/chrome/chrome.gyp:chrome_version_header',
+ '<(DEPTH)/chrome_frame/chrome_frame.gyp:chrome_frame_ie', # for GUIDs.
+ ],
+ 'msvs_settings': {
+ 'VCLinkerTool': {
+ 'OutputFile': '$(OutDir)/servers/$(ProjectName).exe',
+ # Set /SUBSYSTEM:WINDOWS since this is not a command-line program.
+ 'SubSystem': '2',
+ },
+ },
+ 'libraries': [
+ 'oleacc.lib',
+ 'iepmapi.lib',
+ ],
+ },
+ ]
+}
diff --git a/ceee/ie/broker/broker.h b/ceee/ie/broker/broker.h
new file mode 100644
index 0000000..8132e8a
--- /dev/null
+++ b/ceee/ie/broker/broker.h
@@ -0,0 +1,70 @@
+// 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
+// ICeeeBroker implementation.
+
+#ifndef CEEE_IE_BROKER_BROKER_H_
+#define CEEE_IE_BROKER_BROKER_H_
+
+#include <atlbase.h>
+#include <atlcom.h>
+
+#include "ceee/ie/broker/resource.h"
+
+#include "broker_lib.h" // NOLINT
+
+class ExecutorsManager;
+class ApiDispatcher;
+
+// The CEEE user Broker is a COM object exposed from an executable server
+// so that we can call it to execute code in destination threads of other
+// processes, even if they run at a hight integrity level.
+
+// Entry point to execute code in specific destination threads.
+class ATL_NO_VTABLE CeeeBroker
+ : public CComObjectRootEx<CComMultiThreadModel>,
+ public CComCoClass<CeeeBroker, &CLSID_CeeeBroker>,
+ public ICeeeBroker,
+ public ICeeeBrokerRegistrar,
+ public IExternalConnectionImpl<CeeeBroker> {
+ public:
+ DECLARE_REGISTRY_RESOURCEID(IDR_BROKER)
+
+ DECLARE_NOT_AGGREGATABLE(CeeeBroker)
+ BEGIN_COM_MAP(CeeeBroker)
+ COM_INTERFACE_ENTRY(IExternalConnection)
+ COM_INTERFACE_ENTRY(ICeeeBrokerRegistrar)
+ COM_INTERFACE_ENTRY(ICeeeBroker)
+ END_COM_MAP()
+ DECLARE_PROTECT_FINAL_CONSTRUCT()
+
+ // To set get our pointer to the ExecutorsManager and let tests override it.
+ virtual HRESULT FinalConstruct();
+
+ // @name ICeeeBroker implementation.
+ // @{
+ STDMETHOD(Execute)(BSTR function, BSTR* response);
+ STDMETHOD(FireEvent)(BSTR event_name, BSTR event_args);
+ // @}
+
+ // @name ICeeeBrokerRegistrar implementation.
+ // @{
+ STDMETHOD(RegisterWindowExecutor)(long thread_id, IUnknown* executor);
+ STDMETHOD(RegisterTabExecutor)(long thread_id, IUnknown* executor);
+ STDMETHOD(UnregisterExecutor)(long thread_id);
+ STDMETHOD(SetTabIdForHandle)(long tab_id, CeeeWindowHandle handle);
+ // @}
+
+ // IExternalConnectionImpl overrides
+ void OnAddConnection(bool first_lock);
+ void OnReleaseConnection(bool last_unlock, bool last_unlock_releases);
+
+ protected:
+ // A pointer to single instance objects, or seams set for unittests.
+ ExecutorsManager * executors_manager_;
+ ApiDispatcher* api_dispatcher_;
+};
+
+#endif // CEEE_IE_BROKER_BROKER_H_
diff --git a/ceee/ie/broker/broker.rgs b/ceee/ie/broker/broker.rgs
new file mode 100644
index 0000000..65b012b
--- /dev/null
+++ b/ceee/ie/broker/broker.rgs
@@ -0,0 +1,9 @@
+HKCR {
+ NoRemove CLSID {
+ ForceRemove {6D88A70D-2218-4466-BBD6-87AB563811A2} = s 'Google CEEE Broker' {
+ ForceRemove 'Programmable'
+ LocalServer32 = s '%MODULE%'
+ 'TypeLib' = s '{45B783D0-8040-49a6-A719-84E320AAB3C5}'
+ }
+ }
+}
diff --git a/ceee/ie/broker/broker_docs.h b/ceee/ie/broker/broker_docs.h
new file mode 100644
index 0000000..5cc1220
--- /dev/null
+++ b/ceee/ie/broker/broker_docs.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.
+//
+#ifndef CEEE_IE_BROKER_BROKER_DOCS_H_ // Mainly for lint
+#define CEEE_IE_BROKER_BROKER_DOCS_H_
+
+/** @page BrokerDoc Detailed documentation of the Broker process.
+
+@section BrokerIntro Introduction
+
+Since Internet Explorer can have multiple processes and run them at different
+integrity level, we use a separate process to take care of integrity level
+elevation when needed. This Broker process is also used as the sole
+communication point with a Chrome Frame instance for Chrome Extension API
+invocation to be executed in IE as well as events coming from IE and going back
+to Chrome.
+
+@subsection ApiInvocation API Invocation
+
+The Broker must register a Chrome Frame Events sink to receive the private
+messages used to redirect Chrome Extension API Invocations. But this has to be
+done in a non-blocking single threaded way. So we must create a Single Thread
+Apartment (STA) into which an instance of the ChromeFrameHost class will run, so
+it can receive the private messages safely.
+
+Once an API Invocation message is passed to the Broker via the Chrome Frame
+Host, the request is simply added to a global queue of API Invocation, waiting
+to be processed by a worker thread running in the Multi Thread Apartment (MTA).
+
+Running the API invocation mostly likely need to execute code in the IE process,
+but we can't block the STA for those calls, this is why we need to delegate
+these calls to a work thread while the Chrome Frame Host STA can run free.
+
+Once an API invocation is picked up from the queue, and processed (by making
+calls to instances of the CeeeExecutor running in the IE process and managed
+by the ExecutorsManager), then the API response can be sent back to Chrome, via
+the Chrome Frame Host, in the exact same way that we would send Chrome Extension
+Events back to Chrome as described below.
+
+@subsection EventDispatching Event Dispatching
+
+When an event occurs in IE, that Chrome Extension might be interested in, the
+code running in IE fires an event on the ICeeeBroker interface and one of
+the RPC threads, running in the MTA catch it. We process all that is needed
+to fill the event data from the RPC thread, and then add the event to the
+global queue of events that are to be dispatched to Chrome via the Chrome Frame
+Host running in the STA.
+
+So the Chrome Frame Host thread must wake up when events are added to the queue
+and simply pass them to Chrome Frame before going back to sleep. Waiting for
+further API invocations, or more events.
+
+Since the API invocation responses need to go through the Chrome Frame Host
+in the exact same way as the events (except for a different target string), we
+can use the exact same mechanism to send back API invocation response as we do
+for events.
+
+**/
+
+#endif // CEEE_IE_BROKER_BROKER_DOCS_H_
diff --git a/ceee/ie/broker/broker_lib.idl b/ceee/ie/broker/broker_lib.idl
new file mode 100644
index 0000000..ddb2ece
--- /dev/null
+++ b/ceee/ie/broker/broker_lib.idl
@@ -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.
+//
+// @file
+// Interface and object declarations for CEEE IE user broker.
+import "exdisp.idl";
+import "oaidl.idl";
+import "ocidl.idl";
+
+typedef long CeeeWindowHandle;
+
+[
+ object,
+ uuid(A767CE3D-D65B-458f-B66A-CDB10B1F2DF6),
+ nonextensible,
+ helpstring("ICeeeBroker Interface"),
+ pointer_default(unique),
+ oleautomation
+]
+// Can execute code in specific threads of other process, even at higher
+// integrity level than the calling process.
+
+// Allows the execution of code in destination threads.
+interface ICeeeBroker : IUnknown {
+ // This method is DEPRECATED.
+ // Execute the requested message in the appropriate process/thread.
+ //
+ // @param function The JSON encoded function message to process.
+ // @param response Where to return the JSON encoded response.
+ HRESULT Execute([in] BSTR function, [out] BSTR* response);
+
+ // Fire the given event message to Chrome Frame and potentially use it
+ // to complete asynchronous extension API execution.
+ //
+ // @param event_name The name of the event to fire.
+ // @param event_args The JSON encoded event arguments.
+ HRESULT FireEvent([in] BSTR event_name, [in] BSTR event_args);
+}
+
+[
+ object,
+ uuid(1821043a-a496-4d0e-9b50-1e0599237016),
+ nonextensible,
+ helpstring("ICeeeBrokerRegistrar Interface"),
+ pointer_default(unique),
+ oleautomation
+]
+// Access to the broker registrar to un/register a CeeeExecutor or a
+// Chrome Frame Host running in an STA.
+interface ICeeeBrokerRegistrar : IUnknown {
+ // Register the Window Executor @p executor to be used for the given
+ // @p thread_id.
+ //
+ // @param thread_id The identifier of the thread where @p executor runs.
+ // @param executor The executor to register. It must expose the
+ // ICeeeWindowExecutor interface.
+ HRESULT RegisterWindowExecutor([in] long thread_id, [in] IUnknown* executor);
+
+ // TODO(mad@chromium.org): Implement the proper manual/secure
+ // registration. Register a tab executor. This version bypasses the
+ // checks to make sure the broker is the one which initiated the
+ // registration.
+ //
+ // @param thread_id The identifier of the thread where @p executor runs.
+ // @param executor The executor to register. It must expose the
+ // ICeeeTabExecutor interface.
+ HRESULT RegisterTabExecutor([in] long thread_id, [in] IUnknown* executor);
+
+ // Unregister @p executor for the given @p thread_id.
+ //
+ // @param thread_id The identifier of the thread for which we want to
+ // unregister the executor of.
+ HRESULT UnregisterExecutor([in] long thread_id);
+
+ // TODO(hansl@google.com): Remove this and implement it in
+ // RegisterTabExecutor. Make sure the Tab isn't registered before a TabId
+ // is available.
+ // Link a tab_id with a related BHO handle. There is a strict one to one
+ // relation between tab_ids and handles.
+ //
+ // @param tab_id The Chrome TabId related to the tab.
+ // @param handle The HWND of the BHO for this TabId.
+ HRESULT SetTabIdForHandle([in] long tab_id, [in] CeeeWindowHandle handle);
+};
+
+[
+ uuid(45B783D0-8040-49a6-A719-84E320AAB3C5),
+ version(1.0),
+ helpstring("CEEE Broker 1.0 Type Library")
+]
+library CeeeBrokerLib {
+ interface ICeeeBrokerRegistrar;
+ interface ICeeePostman;
+
+ [
+ uuid(6D88A70D-2218-4466-BBD6-87AB563811A2),
+ helpstring("CEEE Broker Class")
+ ]
+ coclass CeeeBroker {
+ [default] interface ICeeeBroker;
+ };
+};
diff --git a/ceee/ie/broker/broker_module.cc b/ceee/ie/broker/broker_module.cc
new file mode 100644
index 0000000..38b8529
--- /dev/null
+++ b/ceee/ie/broker/broker_module.cc
@@ -0,0 +1,194 @@
+// 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 for EXE module.
+
+#include <atlbase.h>
+#include <atlhost.h>
+
+#include "base/at_exit.h"
+#include "base/command_line.h"
+#include "base/debug/trace_event.h"
+#include "base/logging.h"
+#include "base/logging_win.h"
+#include "ceee/ie/broker/broker.h"
+#include "ceee/ie/broker/chrome_postman.h"
+#include "ceee/ie/broker/executors_manager.h"
+#include "ceee/ie/broker/resource.h"
+#include "ceee/ie/broker/window_events_funnel.h"
+#include "ceee/ie/common/crash_reporter.h"
+#include "ceee/common/com_utils.h"
+#include "chrome/common/url_constants.h"
+
+namespace {
+
+const wchar_t kLogFileName[] = L"CeeeBroker.log";
+
+// {6E3D6168-1DD2-4edb-A183-584C2C66E96D}
+const GUID kCeeeBrokerLogProviderName =
+ { 0x6e3d6168, 0x1dd2, 0x4edb,
+ { 0xa1, 0x83, 0x58, 0x4c, 0x2c, 0x66, 0xe9, 0x6d } };
+
+} // namespace
+
+// Object entries go here instead of with each object, so that we can keep
+// the objects in a lib, and also to decrease the amount of magic.
+OBJECT_ENTRY_AUTO(__uuidof(CeeeBroker), CeeeBroker)
+
+class CeeeBrokerModule : public CAtlExeModuleT<CeeeBrokerModule> {
+ public:
+ CeeeBrokerModule();
+ ~CeeeBrokerModule();
+
+ DECLARE_LIBID(LIBID_CeeeBrokerLib)
+ static HRESULT WINAPI UpdateRegistryAppId(BOOL register) throw();
+
+ // We have our own version so that we can explicitly specify
+ // that we want to be in a multi threaded apartment.
+ static HRESULT InitializeCom();
+
+ // Prevent COM objects we don't own to control our lock count.
+ // To properly manage our lifespan, yet still be able to control the
+ // lifespan of the ChromePostman's thread, we must only rely on the
+ // CeeeBroker implementation of the IExternalConnection interface
+ // as well as the ExecutorsManager map content to decide when to die.
+ virtual LONG Lock() {
+ return 1;
+ }
+ virtual LONG Unlock() {
+ return 1;
+ }
+
+ // We prevent access to the module lock count from objects we don't
+ // own by overriding the Un/Lock methods above. But when we want to
+ // access the module lock count, we do it from here, and bypass our
+ // override. These are the entry points that only our code accesses.
+ LONG LockModule() {
+ return CAtlExeModuleT<CeeeBrokerModule>::Lock();
+ }
+ LONG UnlockModule() {
+ return CAtlExeModuleT<CeeeBrokerModule>::Unlock();
+ }
+
+ HRESULT PostMessageLoop();
+ HRESULT PreMessageLoop(int show);
+ private:
+ // We maintain a postman COM object on the stack so that we can
+ // properly initialize and terminate it at the right time.
+ CComObjectStackEx<ChromePostman> chrome_postman_;
+ CrashReporter crash_reporter_;
+ base::AtExitManager at_exit_;
+};
+
+CeeeBrokerModule module;
+
+extern "C" int WINAPI _tWinMain(HINSTANCE, HINSTANCE, LPTSTR, int nShowCmd) {
+ return module.WinMain(nShowCmd);
+}
+
+CeeeBrokerModule::CeeeBrokerModule()
+ : crash_reporter_(L"ceee_broker") {
+ TRACE_EVENT_BEGIN("ceee.broker", this, "");
+
+ 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.
+ 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(kCeeeBrokerLogProviderName);
+
+ // Initialize control hosting.
+ BOOL initialized = AtlAxWinInit();
+ DCHECK(initialized);
+
+ // Needs to be called before we can use GURL.
+ chrome::RegisterChromeSchemes();
+
+ crash_reporter_.InitializeCrashReporting(false);
+}
+
+HRESULT CeeeBrokerModule::InitializeCom() {
+ HRESULT hr = ::CoInitializeEx(NULL, COINIT_MULTITHREADED);
+ if (FAILED(hr))
+ return hr;
+
+ // We need to initialize security before setting global options.
+ hr = ::CoInitializeSecurity(NULL,
+ -1,
+ NULL,
+ NULL,
+ // Clients must identify.
+ RPC_C_IMP_LEVEL_IDENTIFY,
+ // And we identify.
+ RPC_C_IMP_LEVEL_IDENTIFY,
+ NULL,
+ // We don't want low integrity to be able to
+ // instantiate arbitrary objects in our process.
+ EOAC_NO_CUSTOM_MARSHAL,
+ NULL);
+ DCHECK(SUCCEEDED(hr));
+
+ // Ensure the marshaling machinery doesn't eat our crashes.
+ CComPtr<IGlobalOptions> options;
+ hr = options.CoCreateInstance(CLSID_GlobalOptions);
+ if (SUCCEEDED(hr)) {
+ hr = options->Set(COMGLB_EXCEPTION_HANDLING, COMGLB_EXCEPTION_DONOT_HANDLE);
+ }
+ DLOG_IF(WARNING, FAILED(hr)) << "IGlobalOptions::Set failed "
+ << com::LogHr(hr);
+
+ // The above is best-effort, don't bail on error.
+ return S_OK;
+}
+
+HRESULT CeeeBrokerModule::PreMessageLoop(int show) {
+ // It's important to initialize the postman BEFORE we make the Broker
+ // and the event funnel available, since we may get requests to execute
+ // API invocation or Fire events before the postman is ready to handle them.
+ chrome_postman_.Init();
+ WindowEventsFunnel::Initialize();
+ return CAtlExeModuleT<CeeeBrokerModule>::PreMessageLoop(show);
+}
+
+HRESULT CeeeBrokerModule::PostMessageLoop() {
+ HRESULT hr = CAtlExeModuleT<CeeeBrokerModule>::PostMessageLoop();
+ Singleton<ExecutorsManager,
+ ExecutorsManager::SingletonTraits>()->Terminate();
+ WindowEventsFunnel::Terminate();
+ chrome_postman_.Term();
+ return hr;
+}
+
+CeeeBrokerModule::~CeeeBrokerModule() {
+ crash_reporter_.ShutdownCrashReporting();
+ logging::CloseLogFile();
+
+ TRACE_EVENT_END("ceee.broker", this, "");
+}
+
+HRESULT WINAPI CeeeBrokerModule::UpdateRegistryAppId(BOOL reg) throw() {
+ return com::ModuleRegistrationWithoutAppid(IDR_BROKER_MODULE, reg);
+}
+
+namespace ceee_module_util {
+
+LONG LockModule() {
+ return module.LockModule();
+}
+
+LONG UnlockModule() {
+ return module.UnlockModule();
+}
+
+} // namespace
diff --git a/ceee/ie/broker/broker_module.rc b/ceee/ie/broker/broker_module.rc
new file mode 100644
index 0000000..43af9fe
--- /dev/null
+++ b/ceee/ie/broker/broker_module.rc
@@ -0,0 +1,104 @@
+// 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/broker/resource.h"
+#include "version.h"
+
+#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", "Internet Explorer User Broker for Google CEEE"
+ VALUE "FileVersion", CHROME_VERSION_STRING
+ VALUE "LegalCopyright", COPYRIGHT_STRING
+ VALUE "InternalName", "ceee_broker.exe"
+ VALUE "OriginalFilename", "ceee_broker.exe"
+ 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_BROKER_MODULE REGISTRY "broker_module.rgs"
+IDR_BROKER REGISTRY "broker.rgs"
+
+
+/////////////////////////////////////////////////////////////////////////////
+//
+// String Table
+//
+
+STRINGTABLE
+BEGIN
+ IDS_PROJNAME "ceee_broker"
+END
+
+#endif // English (U.S.) resources
+/////////////////////////////////////////////////////////////////////////////
+
+
+
+#ifndef APSTUDIO_INVOKED
+/////////////////////////////////////////////////////////////////////////////
+//
+// Generated from the TEXTINCLUDE 3 resource.
+//
+1 TYPELIB "broker_lib.tlb"
+
+/////////////////////////////////////////////////////////////////////////////
+#endif // not APSTUDIO_INVOKED
diff --git a/ceee/ie/broker/broker_module.rgs b/ceee/ie/broker/broker_module.rgs
new file mode 100644
index 0000000..dfd0d75
--- /dev/null
+++ b/ceee/ie/broker/broker_module.rgs
@@ -0,0 +1,17 @@
+HKLM {
+ NoRemove SOFTWARE {
+ NoRemove Microsoft {
+ NoRemove 'Internet Explorer' {
+ NoRemove 'Low Rights' {
+ NoRemove ElevationPolicy {
+ ForceRemove {F2DE0DE9-098E-48a7-92C2-53B89A272BF4} {
+ val Policy = d 3
+ val AppName = s '%MODULE_BASENAME%'
+ val AppPath = s '%MODULE_PATH%'
+ }
+ }
+ }
+ }
+ }
+ }
+}
diff --git a/ceee/ie/broker/broker_module_util.h b/ceee/ie/broker/broker_module_util.h
new file mode 100644
index 0000000..effcd99
--- /dev/null
+++ b/ceee/ie/broker/broker_module_util.h
@@ -0,0 +1,25 @@
+// 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
+// Utility functions to be called on the Broker Module object.
+
+#ifndef CEEE_IE_BROKER_BROKER_MODULE_UTIL_H__
+#define CEEE_IE_BROKER_BROKER_MODULE_UTIL_H__
+
+namespace ceee_module_util {
+
+// Locks the module to prevent it from being terminated.
+//
+// @return The new module ref count, but only for debugging purposes.
+LONG LockModule();
+
+// Unlocks the module to allow it to be terminated appropriately.
+//
+// @return The new module ref count, but only for debugging purposes.
+LONG UnlockModule();
+
+} // namespace
+
+#endif // CEEE_IE_BROKER_BROKER_MODULE_UTIL_H__
diff --git a/ceee/ie/broker/broker_unittest.cc b/ceee/ie/broker/broker_unittest.cc
new file mode 100644
index 0000000..aec7453
--- /dev/null
+++ b/ceee/ie/broker/broker_unittest.cc
@@ -0,0 +1,77 @@
+// 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 user broker.
+
+#include "base/logging.h"
+#include "ceee/ie/broker/broker.h"
+#include "ceee/ie/broker/executors_manager.h"
+#include "ceee/ie/testing/mock_broker_and_friends.h"
+#include "ceee/testing/utils/test_utils.h"
+#include "testing/gmock/include/gmock/gmock.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+namespace {
+
+using testing::Return;
+
+// Lets us test protected members and mock certain calls.
+class MockExecutorsManager : public ExecutorsManager {
+ public:
+ // true for no thread.
+ MockExecutorsManager() : ExecutorsManager(true) {
+ }
+ MOCK_METHOD2(RegisterWindowExecutor, HRESULT(ThreadId thread_id,
+ IUnknown* executor));
+ MOCK_METHOD2(RegisterTabExecutor, HRESULT(ThreadId thread_id,
+ IUnknown* executor));
+ MOCK_METHOD1(RemoveExecutor, HRESULT(ThreadId thread_id));
+};
+
+class TestBroker: public CeeeBroker {
+ public:
+ // Prevents the instantiation of the ExecutorsManager, ApiDispatcher
+ // singletons.
+ HRESULT FinalConstruct() {
+ return S_OK;
+ }
+ // Set our own ExecutorsManager mock.
+ void SetExecutorsManager(ExecutorsManager* executors_manager) {
+ executors_manager_ = executors_manager;
+ }
+
+ // Set our own ApiDispatcher mock.
+ void SetApiDisptacher(ApiDispatcher* api_dispatcher) {
+ api_dispatcher_ = api_dispatcher;
+ }
+};
+
+TEST(CeeeBroker, All) {
+ CComObject<TestBroker>* broker = NULL;
+ CComObject<TestBroker>::CreateInstance(&broker);
+ CComPtr<TestBroker> broker_keeper(broker);
+
+ MockExecutorsManager executors_manager;
+ broker->SetExecutorsManager(&executors_manager);
+
+ testing::MockApiDispatcher api_dispatcher;
+ broker->SetApiDisptacher(&api_dispatcher);
+
+ EXPECT_CALL(api_dispatcher, HandleApiRequest(NULL, NULL)).Times(1);
+ EXPECT_EQ(S_OK, broker->Execute(NULL, NULL));
+
+ EXPECT_CALL(executors_manager, RegisterWindowExecutor(0, NULL)).
+ WillOnce(Return(S_OK));
+ EXPECT_EQ(S_OK, broker->RegisterWindowExecutor(0, NULL));
+
+ EXPECT_CALL(executors_manager, RegisterTabExecutor(0, NULL)).
+ WillOnce(Return(S_OK));
+ EXPECT_EQ(S_OK, broker->RegisterTabExecutor(0, NULL));
+
+ EXPECT_CALL(executors_manager, RemoveExecutor(0)).
+ WillOnce(Return(S_OK));
+ EXPECT_EQ(S_OK, broker->UnregisterExecutor(0));
+}
+
+} // namespace
diff --git a/ceee/ie/broker/chrome_postman.cc b/ceee/ie/broker/chrome_postman.cc
new file mode 100644
index 0000000..59f2ba7
--- /dev/null
+++ b/ceee/ie/broker/chrome_postman.cc
@@ -0,0 +1,300 @@
+// 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.
+//
+// ChromePostman implementation.
+#include "ceee/ie/broker/chrome_postman.h"
+
+#include "base/string_util.h"
+#include "ceee/ie/broker/api_dispatcher.h"
+#include "ceee/ie/common/api_registration.h"
+#include "ceee/ie/common/ceee_module_util.h"
+#include "chrome/browser/automation/extension_automation_constants.h"
+#include "chrome/common/url_constants.h"
+#include "ceee/common/com_utils.h"
+
+namespace ext = extension_automation_constants;
+
+namespace {
+ // The maximum number of times we should try to start Frame.
+ const unsigned int kMaxFrameResetCount = 5;
+}
+
+class ChromeFrameMessageTask : public Task {
+ public:
+ explicit ChromeFrameMessageTask(
+ ChromePostman::ChromePostmanThread* thread_object,
+ BSTR message, BSTR target)
+ : thread_object_(thread_object),
+ message_(message),
+ target_(target) {
+ }
+
+ virtual void Run() {
+ thread_object_->PostMessage(message_, target_);
+ }
+ private:
+ ChromePostman::ChromePostmanThread* thread_object_;
+ CComBSTR message_;
+ CComBSTR target_;
+};
+
+
+class ChromeFrameResetTask : public Task {
+ public:
+ explicit ChromeFrameResetTask(
+ ChromePostman::ChromePostmanThread* thread_object)
+ : thread_object_(thread_object) {
+ }
+
+ virtual void Run() {
+ thread_object_->ResetChromeFrame();
+ }
+ private:
+ ChromePostman::ChromePostmanThread* thread_object_;
+};
+
+
+class ApiExecutionTask : public Task {
+ public:
+ explicit ApiExecutionTask(BSTR message) : message_(message) {}
+
+ virtual void Run() {
+ ProductionApiDispatcher::get()->HandleApiRequest(message_, NULL);
+ }
+ private:
+ CComBSTR message_;
+};
+
+class FireEventTask : public Task {
+ public:
+ FireEventTask(BSTR event_name, BSTR event_args)
+ : event_name_(event_name), event_args_(event_args) {}
+
+ virtual void Run() {
+ ProductionApiDispatcher::get()->FireEvent(event_name_, event_args_);
+ }
+ private:
+ CComBSTR event_name_;
+ CComBSTR event_args_;
+};
+
+
+ChromePostman* ChromePostman::single_instance_ = NULL;
+ChromePostman::ChromePostman() {
+ DCHECK(single_instance_ == NULL);
+ single_instance_ = this;
+ frame_reset_count_ = 0;
+}
+
+ChromePostman::~ChromePostman() {
+ DCHECK(single_instance_ == this);
+ single_instance_ = NULL;
+}
+
+void ChromePostman::Init() {
+ // The postman thread must be a UI thread so that it can pump windows
+ // messages and allow COM to handle cross apartment calls.
+
+ chrome_postman_thread_.StartWithOptions(
+ base::Thread::Options(MessageLoop::TYPE_UI, 0));
+ api_worker_thread_.Start();
+}
+
+void ChromePostman::Term() {
+ api_worker_thread_.Stop();
+ chrome_postman_thread_.Stop();
+}
+
+void ChromePostman::PostMessage(BSTR message, BSTR target) {
+ MessageLoop* message_loop = chrome_postman_thread_.message_loop();
+ if (message_loop) {
+ message_loop->PostTask(
+ FROM_HERE, new ChromeFrameMessageTask(&chrome_postman_thread_,
+ message, target));
+ } else {
+ LOG(ERROR) << "Trying to post a message before the postman thread is"
+ "completely initialized and ready.";
+ }
+}
+
+void ChromePostman::FireEvent(BSTR event_name, BSTR event_args) {
+ MessageLoop* message_loop = api_worker_thread_.message_loop();
+ if (message_loop) {
+ message_loop->PostTask(FROM_HERE,
+ new FireEventTask(event_name, event_args));
+ } else {
+ LOG(ERROR) << "Trying to post a message before the API worker thread is"
+ "completely initialized and ready.";
+ }
+}
+
+HRESULT ChromePostman::OnCfReadyStateChanged(LONG state) {
+ if (state == READYSTATE_COMPLETE) {
+ // If the page is fully loaded, we reset the count to 0 to ensure we restart
+ // it if it's the user's fault (no max count).
+ DLOG(INFO) << "frame_reset_count_ reset";
+ frame_reset_count_ = 0;
+ }
+ return S_OK;
+}
+
+HRESULT ChromePostman::OnCfPrivateMessage(BSTR msg, BSTR origin, BSTR target) {
+ if (CComBSTR(target) == ext::kAutomationRequestTarget) {
+ MessageLoop* message_loop = api_worker_thread_.message_loop();
+ if (message_loop) {
+ message_loop->PostTask(FROM_HERE, new ApiExecutionTask(msg));
+ } else {
+ LOG(ERROR) << "Trying to post a message before the API worker thread is"
+ "completely initialized and ready.";
+ }
+ }
+ return S_OK;
+}
+
+HRESULT ChromePostman::OnCfExtensionReady(BSTR path, int response) {
+ return S_OK;
+}
+
+HRESULT ChromePostman::OnCfGetEnabledExtensionsComplete(
+ SAFEARRAY* tab_delimited_paths) {
+ return S_OK;
+}
+
+HRESULT ChromePostman::OnCfGetExtensionApisToAutomate(BSTR* functions_enabled) {
+ DCHECK(functions_enabled != NULL);
+ std::vector<std::string> function_names;
+#define REGISTER_API_FUNCTION(func) \
+ function_names.push_back(func##Function::function_name())
+ REGISTER_ALL_API_FUNCTIONS();
+#undef REGISTER_API_FUNCTION
+ // There is a special case with CreateWindow that is explained in the
+ // class comments.
+ function_names.push_back(CreateWindowFunction::function_name());
+ std::string function_names_delim = JoinString(function_names, ',');
+ *functions_enabled = CComBSTR(function_names_delim.c_str()).Detach();
+
+ // CF is asking for the list of extension APIs to automate so the
+ // automation channel is ready. Set a dummy URL so we get the OnLoad
+ // callback.
+ //
+ // The current function call should come to us on the COM thread so we can
+ // just call the ChromeFrameHost directly from here.
+ CComPtr<IChromeFrameHost> host;
+ chrome_postman_thread_.GetHost(&host);
+ return host->SetUrl(CComBSTR(chrome::kAboutBlankURL));
+
+ return S_OK;
+}
+
+HRESULT ChromePostman::OnCfChannelError() {
+ MessageLoop* message_loop = chrome_postman_thread_.message_loop();
+ if (message_loop) {
+ frame_reset_count_++;
+ LOG(INFO) << "frame_reset_count_ = " << frame_reset_count_;
+
+ // No use staying alive if Chrome Frame can't start.
+ CHECK(frame_reset_count_ < kMaxFrameResetCount)
+ << "Trying to reset Chrome Frame too many times already. Something's "
+ "wrong.";
+
+ message_loop->PostTask(FROM_HERE,
+ new ChromeFrameResetTask(&chrome_postman_thread_));
+ } else {
+ LOG(ERROR) << "Trying to reset Chrome Frame before the postman thread is"
+ "completely initialized and ready.";
+ }
+ return S_OK;
+}
+
+ChromePostman::ChromePostmanThread::ChromePostmanThread()
+ : base::Thread("ChromePostman") {
+}
+
+void ChromePostman::ChromePostmanThread::Init() {
+ HRESULT hr = ::CoInitializeEx(0, COINIT_APARTMENTTHREADED);
+ DCHECK(SUCCEEDED(hr)) << "Can't Init COM??? " << com::LogHr(hr);
+
+ hr = InitializeChromeFrameHost();
+ DCHECK(SUCCEEDED(hr)) << "InitializeChromeFrameHost failed " <<
+ com::LogHr(hr);
+}
+
+HRESULT ChromePostman::ChromePostmanThread::InitializeChromeFrameHost() {
+ DCHECK(thread_id() == ::GetCurrentThreadId());
+ 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);
+ }
+
+ DCHECK(ChromePostman::GetInstance() != NULL);
+ chrome_frame_host_->SetEventSink(ChromePostman::GetInstance());
+ chrome_frame_host_->SetChromeProfileName(
+ ceee_module_util::GetBrokerProfileNameForIe());
+ hr = chrome_frame_host_->StartChromeFrame();
+ DCHECK(SUCCEEDED(hr));
+ if (FAILED(hr)) {
+ LOG(ERROR) << "Failed to start chrome frame " << com::LogHr(hr);
+ return hr;
+ }
+ return hr;
+}
+
+HRESULT ChromePostman::ChromePostmanThread::CreateChromeFrameHost() {
+ DCHECK(thread_id() == ::GetCurrentThreadId());
+ return ChromeFrameHost::CreateInitializedIID(IID_IChromeFrameHost,
+ &chrome_frame_host_);
+}
+
+void ChromePostman::ChromePostmanThread::CleanUp() {
+ TeardownChromeFrameHost();
+ ::CoUninitialize();
+}
+
+void ChromePostman::ChromePostmanThread::TeardownChromeFrameHost() {
+ DCHECK(thread_id() == ::GetCurrentThreadId());
+ if (chrome_frame_host_) {
+ chrome_frame_host_->SetEventSink(NULL);
+ HRESULT hr = chrome_frame_host_->TearDown();
+ DCHECK(SUCCEEDED(hr)) << "ChromeFrameHost TearDown failed " <<
+ com::LogHr(hr);
+ chrome_frame_host_.Release();
+ }
+}
+
+void ChromePostman::ChromePostmanThread::ResetChromeFrame() {
+ TeardownChromeFrameHost();
+ HRESULT hr = InitializeChromeFrameHost();
+ DCHECK(SUCCEEDED(hr)) << "InitializeChromeFrameHost failed " <<
+ com::LogHr(hr);
+}
+
+void ChromePostman::ChromePostmanThread::PostMessage(BSTR message,
+ BSTR target) {
+ DCHECK(thread_id() == ::GetCurrentThreadId());
+ HRESULT hr = chrome_frame_host_->PostMessage(message, target);
+ DCHECK(SUCCEEDED(hr)) << "ChromeFrameHost PostMessage failed " <<
+ com::LogHr(hr);
+}
+
+void ChromePostman::ChromePostmanThread::GetHost(IChromeFrameHost** host) {
+ DCHECK(thread_id() == ::GetCurrentThreadId());
+ chrome_frame_host_.CopyTo(host);
+}
+
+ChromePostman::ApiInvocationWorkerThread::ApiInvocationWorkerThread()
+ : base::Thread("ApiInvocationWorker") {
+}
+
+void ChromePostman::ApiInvocationWorkerThread::Init() {
+ ::CoInitializeEx(0, COINIT_MULTITHREADED);
+ ProductionApiDispatcher::get()->SetApiInvocationThreadId(
+ ::GetCurrentThreadId());
+}
+
+void ChromePostman::ApiInvocationWorkerThread::CleanUp() {
+ ::CoUninitialize();
+ ProductionApiDispatcher::get()->SetApiInvocationThreadId(0);
+}
diff --git a/ceee/ie/broker/chrome_postman.h b/ceee/ie/broker/chrome_postman.h
new file mode 100644
index 0000000..538abd7
--- /dev/null
+++ b/ceee/ie/broker/chrome_postman.h
@@ -0,0 +1,133 @@
+// Copyright (c) 2010 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+//
+// @file
+// Broke Postman implementation.
+
+#ifndef CEEE_IE_BROKER_CHROME_POSTMAN_H_
+#define CEEE_IE_BROKER_CHROME_POSTMAN_H_
+
+#include "base/singleton.h"
+#include "base/thread.h"
+#include "ceee/ie/common/chrome_frame_host.h"
+
+#include "broker_lib.h" // NOLINT
+
+// The Broker Postman singleton object wrapping the ChromeFrameHost
+// class so that it can receive API invocation from Chrome Frame, and also
+// dispatch response and events to it.
+//
+// Since Chrome Frame must be access from a Single Thread Apartment, this
+// object must only call it in an STA, so the PostMessage method must post a
+// task to do so in the STA thread.
+//
+// But when Chrome Frame calls into this object, we can't block and thus
+// the API invocations must be queued so that the API Dispatcher can
+// fetch them from the queue, handle them appropriately in the MTA and
+// post back the asynchronous response to Chrome Frame via our
+// PostMessage method.
+
+class ChromePostman
+ : public CComObjectRootEx<CComMultiThreadModel>,
+ public IChromeFrameHostEvents {
+ public:
+ BEGIN_COM_MAP(ChromePostman)
+ END_COM_MAP()
+
+ ChromePostman();
+ virtual ~ChromePostman();
+
+ // This posts a tasks to our STA thread so that it posts the given message
+ // to Chrome Frame.
+ virtual void PostMessage(BSTR message, BSTR target);
+
+ // This posts a tasks to the Api Invocation thread to fire the given event.
+ virtual void FireEvent(BSTR event_name, BSTR event_args);
+
+ // This creates both an STA and an MTA threads. We must make sure we only call
+ // Chrome Frame from this STA. And since we can't block Chrome Frame we use
+ // the MTA thread to executes API Invocation we receive from Chrome Frame.
+ // We also create and initialize Chrome Framer from here.
+ virtual void Init();
+
+ // To cleanly terminate the threads, and our hooks into Chrome Frame.
+ virtual void Term();
+
+ // Returns our single instance held by the module.
+ static ChromePostman* GetInstance() { return single_instance_; }
+
+ class ChromePostmanThread : public base::Thread {
+ public:
+ ChromePostmanThread();
+
+ // Called just prior to starting the message loop
+ virtual void Init();
+
+ // Called just after the message loop ends
+ virtual void CleanUp();
+
+ // Posts the message to our instance of Chrome Frame.
+ // THIS CAN ONLY BE CALLED FROM OUR THREAD, and we DCHECK it.
+ void PostMessage(BSTR message, BSTR target);
+
+ // Retrieves the Chrome Frame host; should only be called from the
+ // postman thread.
+ void GetHost(IChromeFrameHost** host);
+
+ // Resets Chrome Frame by tearing it down and restarting it.
+ // We use this when we receive a channel error meaning Chrome has died.
+ void ResetChromeFrame();
+
+ protected:
+ // Creates and initializes the chrome frame host.
+ // CAN ONLY BE CALLED FROM THE STA!
+ HRESULT InitializeChromeFrameHost();
+
+ // Isolate the creation of the host so we can overload it to mock
+ // the Chrome Frame Host in our tests.
+ // CAN ONLY BE CALLED FROM THE STA!
+ virtual HRESULT CreateChromeFrameHost();
+
+ // Tears down the Chrome Frame host.
+ void TeardownChromeFrameHost();
+
+ // The Chrome Frame host handling a Chrome Frame instance for us.
+ CComPtr<IChromeFrameHost> chrome_frame_host_;
+ };
+
+ protected:
+ // Messages received from Chrome Frame are sent to the API dispatcher via
+ // a task posted to our MTA thread.
+ HRESULT OnCfReadyStateChanged(LONG state);
+ HRESULT OnCfPrivateMessage(BSTR msg, BSTR origin, BSTR target);
+ HRESULT OnCfExtensionReady(BSTR path, int response);
+ HRESULT OnCfGetEnabledExtensionsComplete(SAFEARRAY* tab_delimited_paths);
+ HRESULT OnCfGetExtensionApisToAutomate(BSTR* functions_enabled);
+ HRESULT OnCfChannelError();
+
+ class ApiInvocationWorkerThread : public base::Thread {
+ public:
+ ApiInvocationWorkerThread();
+
+ // Called just prior to starting the message loop
+ virtual void Init();
+
+ // Called just after the message loop ends
+ virtual void CleanUp();
+ };
+
+ // The STA in which we run.
+ ChromePostmanThread chrome_postman_thread_;
+
+ // The MTA thread to which we post API Invocations and Fired Events.
+ ApiInvocationWorkerThread api_worker_thread_;
+
+ // The number of times we tried to launch ChromeFrame.
+ int frame_reset_count_;
+
+ // "in the end, there should be only one!" :-)
+ static ChromePostman* single_instance_;
+};
+
+#endif // CEEE_IE_BROKER_CHROME_POSTMAN_H_
diff --git a/ceee/ie/broker/common_api_module.cc b/ceee/ie/broker/common_api_module.cc
new file mode 100644
index 0000000..096003b
--- /dev/null
+++ b/ceee/ie/broker/common_api_module.cc
@@ -0,0 +1,146 @@
+// 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.
+//
+// Implementations for common functions for various API implementation modules.
+
+#include "ceee/ie/broker/common_api_module.h"
+
+#include <atlbase.h>
+#include <atlcom.h> // Must be included AFTER base.
+
+#include "base/string_number_conversions.h"
+#include "ceee/ie/broker/api_module_constants.h"
+#include "ceee/ie/broker/tab_api_module.h"
+#include "ceee/ie/common/ie_util.h"
+#include "chrome/browser/extensions/extension_tabs_module_constants.h"
+#include "chrome/common/extensions/extension_error_utils.h"
+#include "ceee/common/com_utils.h"
+#include "ceee/common/window_utils.h"
+#include "ceee/common/windows_constants.h"
+
+namespace ext = extension_tabs_module_constants;
+
+namespace common_api {
+
+bool CommonApiResult::IsTabWindowClass(HWND window) {
+ return window_utils::IsWindowClass(window, windows::kIeTabWindowClass);
+}
+
+bool CommonApiResult::IsIeFrameClass(HWND window) {
+ return window_utils::IsWindowClass(window, windows::kIeFrameWindowClass);
+}
+
+HWND CommonApiResult::TopIeWindow() {
+ HWND window = NULL;
+ if (window_utils::FindDescendentWindow(NULL, windows::kIeFrameWindowClass,
+ true, &window)) {
+ return window;
+ }
+ NOTREACHED() << "How come we can't find a Top IE Window???";
+ return NULL;
+}
+
+void CommonApiResult::SetResultFromWindowInfo(
+ HWND window, const CeeeWindowInfo& window_info, bool populate_tabs) {
+ scoped_ptr<DictionaryValue> dict(new DictionaryValue());
+ ApiDispatcher* dispatcher = GetDispatcher();
+ DCHECK(dispatcher != NULL);
+ int window_id = dispatcher->GetWindowIdFromHandle(window);
+ dict->SetInteger(ext::kIdKey, window_id);
+ dict->SetBoolean(ext::kFocusedKey, window_info.focused != FALSE);
+
+ dict->SetInteger(ext::kLeftKey, window_info.rect.left);
+ dict->SetInteger(ext::kTopKey, window_info.rect.top);
+ dict->SetInteger(ext::kWidthKey, window_info.rect.right -
+ window_info.rect.left);
+ dict->SetInteger(ext::kHeightKey, window_info.rect.bottom -
+ window_info.rect.top);
+ if (populate_tabs) {
+ DCHECK(window_info.tab_list != NULL);
+ Value* tab_list_value = CreateTabList(window_info.tab_list);
+ // DCHECK yet continue if we get a NULL tab list since we may get here
+ // before it is available.
+ DCHECK(tab_list_value != NULL);
+ if (!tab_list_value)
+ tab_list_value = Value::CreateNullValue();
+ dict->Set(ext::kTabsKey, tab_list_value);
+ }
+
+ dict->SetBoolean(ext::kIncognitoKey, ie_util::GetIEIsInPrivateBrowsing());
+
+ // TODO(mad@chromium.org): for now, always setting to "normal" since we don't
+ // yet have a way to tell if the window is a popup or not.
+ dict->SetString(ext::kWindowTypeKey, ext::kWindowTypeValueNormal);
+
+ DCHECK(value_ == NULL);
+ value_.reset(dict.release());
+}
+
+Value* CommonApiResult::CreateTabList(BSTR tab_list) {
+ // No need for a request_id.
+ tab_api::GetAllTabsInWindowResult result(kNoRequestId);
+ result.Execute(tab_list);
+ const Value* tab_list_value = result.value();
+ // No DCHECK as this will happen if we try to update a window
+ // that has been closed.
+ if (tab_list_value) {
+ return tab_list_value->DeepCopy();
+ }
+ LOG(WARNING) << "Can't get info for tab_ids: '" << tab_list << "'.";
+ return NULL;
+}
+
+bool CommonApiResult::CreateWindowValue(HWND window, bool populate_tabs) {
+ ApiDispatcher* dispatcher = GetDispatcher();
+ DCHECK(dispatcher != NULL);
+ int window_id = dispatcher->GetWindowIdFromHandle(window);
+ if (window_utils::WindowHasNoThread(window)) {
+ PostError(ExtensionErrorUtils::FormatErrorMessage(
+ ext::kWindowNotFoundError, base::IntToString(window_id)));
+ return false;
+ }
+
+ if (!IsIeFrameClass(window)) {
+ PostError(ExtensionErrorUtils::FormatErrorMessage(
+ ext::kWindowNotFoundError, base::IntToString(window_id)));
+ return false;
+ }
+
+ CComPtr<ICeeeWindowExecutor> executor;
+ dispatcher->GetExecutor(window, IID_ICeeeWindowExecutor,
+ reinterpret_cast<void**>(&executor));
+ if (executor == NULL) {
+ LOG(WARNING) << "Failed to get an executor to get window info.";
+ PostError(api_module_constants::kInternalErrorError);
+ return false;
+ }
+
+ common_api::WindowInfo window_info;
+ HRESULT hr = executor->GetWindow(populate_tabs, &window_info);
+ if (FAILED(hr)) {
+ // No DCHECK, this may happen if the window/thread dies on the way.
+ LOG(ERROR) << "Can't get info for window: " << std::hex << window <<
+ ". " << com::LogHr(hr);
+ PostError(api_module_constants::kInternalErrorError);
+ return false;
+ }
+ SetResultFromWindowInfo(window, window_info, populate_tabs);
+ return true;
+}
+
+WindowInfo::WindowInfo() {
+ focused = FALSE;
+ rect.bottom = -1;
+ rect.top = -1;
+ rect.left = -1;
+ rect.right = -1;
+ tab_list = NULL;
+}
+
+WindowInfo::~WindowInfo() {
+ // SysFreeString accepts NULL pointers.
+ ::SysFreeString(tab_list);
+}
+
+} // namespace common_api
diff --git a/ceee/ie/broker/common_api_module.h b/ceee/ie/broker/common_api_module.h
new file mode 100644
index 0000000..c4b6961
--- /dev/null
+++ b/ceee/ie/broker/common_api_module.h
@@ -0,0 +1,71 @@
+// 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 functions for various API implementation modules.
+
+#ifndef CEEE_IE_BROKER_COMMON_API_MODULE_H_
+#define CEEE_IE_BROKER_COMMON_API_MODULE_H_
+
+#include <string>
+
+#include "ceee/ie/broker/api_dispatcher.h"
+
+#include "toolband.h" // NOLINT
+
+namespace common_api {
+
+class CommonApiResult : public ApiDispatcher::InvocationResult {
+ public:
+ explicit CommonApiResult(int request_id)
+ : ApiDispatcher::InvocationResult(request_id) {
+ }
+
+ // Returns true if the given window is an IE "server" window, i.e. a tab.
+ static bool IsTabWindowClass(HWND window);
+
+ // Returns true if the given window is a top-level IE window.
+ static bool IsIeFrameClass(HWND window);
+
+ // Returns the IE frame window at the top of the Z-order. This will generally
+ // be the last window used or the new window just created.
+ // @return The HWND of the top IE window.
+ static HWND TopIeWindow();
+
+ // Build the result_ value from the provided window info. It will set the
+ // value if it is currently NULL, otherwise it assumes it is a ListValue and
+ // adds a new Value to it.
+ // @param window The window handle
+ // @param window_info The info about the window to create a new value for.
+ // @param populate_tabs To specify if we want to populate the tabs info.
+ virtual void SetResultFromWindowInfo(HWND window,
+ const CeeeWindowInfo& window_info,
+ bool populate_tabs);
+
+ // Creates a list value of all tabs in the given list.
+ // @param tab_list A list of HWND and index of the tabs for which we want to
+ // create a value JSON encoded as a list of (id, index) pairs.
+ // @Return A ListValue containing the individual Values for each tab info.
+ virtual Value* CreateTabList(BSTR tab_list);
+
+ // Creates a value for the given window, populating tabs if requested.
+ // Sets value_ with the appropriate value content, or resets it in case of
+ // errors. Also calls PostError() if there is an error and returns false.
+ // The @p window parameter contrasts with CreateTabValue because we most
+ // often use this function with HWND gotten without Ids (ie. from
+ // TopIeWindow()). This was not the case with CreateTabValue.
+ // @param window The identifier of the window for which to create the value.
+ // @param populate_tabs To specify if we want to populate the tabs info.
+ virtual bool CreateWindowValue(HWND window, bool populate_tabs);
+};
+
+// Helper class to handle the data cleanup.
+class WindowInfo : public CeeeWindowInfo {
+ public:
+ WindowInfo();
+ ~WindowInfo();
+};
+
+} // namespace common_api
+
+#endif // CEEE_IE_BROKER_COMMON_API_MODULE_H_
diff --git a/ceee/ie/broker/cookie_api_module.cc b/ceee/ie/broker/cookie_api_module.cc
new file mode 100644
index 0000000..2e7d59b
--- /dev/null
+++ b/ceee/ie/broker/cookie_api_module.cc
@@ -0,0 +1,450 @@
+// Copyright (c) 2010 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+//
+// Cookie API implementation.
+
+#include "ceee/ie/broker/cookie_api_module.h"
+
+#include <atlbase.h>
+#include <atlcom.h>
+
+#include "base/json/json_reader.h"
+#include "base/json/json_writer.h"
+#include "base/string_util.h"
+#include "ceee/common/com_utils.h"
+#include "ceee/common/process_utils_win.h"
+#include "ceee/common/window_utils.h"
+#include "ceee/common/windows_constants.h"
+#include "ceee/ie/broker/api_module_constants.h"
+#include "ceee/ie/broker/api_module_util.h"
+#include "ceee/ie/broker/window_api_module.h"
+#include "ceee/ie/common/api_registration.h"
+#include "chrome/browser/extensions/extension_cookies_api_constants.h"
+#include "chrome/common/extensions/extension_error_utils.h"
+
+namespace keys = extension_cookies_api_constants;
+
+namespace cookie_api {
+
+void RegisterInvocations(ApiDispatcher* dispatcher) {
+#define REGISTER_API_FUNCTION(func) do { dispatcher->RegisterInvocation(\
+ func##Function::function_name(), NewApiInvocation< func >); }\
+ while (false)
+ REGISTER_COOKIE_API_FUNCTIONS();
+#undef REGISTER_API_FUNCTION
+ // Now register the permanent event handler.
+ dispatcher->RegisterPermanentEventHandler(keys::kOnChanged,
+ CookieChanged::EventHandler);
+}
+
+bool CookieApiResult::CreateCookieValue(const CookieInfo& cookie_info) {
+ scoped_ptr<DictionaryValue> cookie(new DictionaryValue());
+ cookie->SetString(keys::kNameKey, com::ToString(cookie_info.name));
+ cookie->SetString(keys::kValueKey, com::ToString(cookie_info.value));
+ cookie->SetString(keys::kDomainKey, com::ToString(cookie_info.domain));
+ cookie->SetBoolean(keys::kHostOnlyKey, cookie_info.host_only == TRUE);
+ cookie->SetString(keys::kPathKey, com::ToString(cookie_info.path));
+ cookie->SetBoolean(keys::kSecureKey, cookie_info.secure == TRUE);
+ cookie->SetBoolean(keys::kHttpOnlyKey, cookie_info.http_only == TRUE);
+ cookie->SetBoolean(keys::kSessionKey, cookie_info.session == TRUE);
+ if (cookie_info.session == FALSE)
+ cookie->SetReal(keys::kExpirationDateKey, cookie_info.expiration_date);
+ cookie->SetString(keys::kStoreIdKey, com::ToString(cookie_info.store_id));
+ DCHECK(value() == NULL);
+ set_value(cookie.release());
+ return true;
+}
+
+HRESULT CookieApiResult::GetCookieInfo(
+ const std::string& url, const std::string& name, HWND window,
+ CookieInfo* cookie_info) {
+ // Get a tab window child of the cookie store window, so that we can properly
+ // access session cookies.
+ HWND tab_window = NULL;
+ if (!window_utils::FindDescendentWindow(
+ window, windows::kIeTabWindowClass, false, &tab_window) ||
+ tab_window == NULL) {
+ PostError("Failed to get tab window for a given cookie store.");
+ return E_FAIL;
+ }
+
+ CComPtr<ICeeeCookieExecutor> executor;
+ GetDispatcher()->GetExecutor(tab_window, IID_ICeeeCookieExecutor,
+ reinterpret_cast<void**>(&executor));
+ if (executor == NULL) {
+ LOG(WARNING) << "Failed to get an executor to get cookie info.";
+ PostError("Internal Error while getting cookie info.");
+ return E_FAIL;
+ }
+
+ HRESULT hr = executor->GetCookie(
+ CComBSTR(url.data()), CComBSTR(name.data()), cookie_info);
+ if (FAILED(hr)) {
+ LOG(ERROR) << "Failed to get cookie info." << com::LogHr(hr);
+ PostError("Internal Error while getting cookie info.");
+ }
+ return hr;
+}
+
+bool CookieApiResult::CreateCookieStoreValue(DWORD process_id,
+ const WindowSet& windows) {
+ scoped_ptr<DictionaryValue> cookie_store(new DictionaryValue());
+ std::ostringstream store_id_stream;
+ store_id_stream << process_id;
+ // For IE CEEE, we use a string representation of the IE process ID as the
+ // cookie store ID.
+ cookie_store->SetString(keys::kIdKey, store_id_stream.str());
+ DCHECK(windows.size());
+ if (windows.size() == 0) {
+ PostError(api_module_constants::kInternalErrorError);
+ return false;
+ }
+ WindowSet::const_iterator iter = windows.begin();
+ // First register the cookie store once per process.
+ if (FAILED(RegisterCookieStore(*iter)))
+ return false;
+ // Now collect all tab IDs from each frame window into a single list.
+ scoped_ptr<ListValue> tab_ids(new ListValue());
+ for (; iter != windows.end(); ++iter) {
+ if (!AppendToTabIdList(*iter, tab_ids.get())) {
+ PostError(api_module_constants::kInternalErrorError);
+ return false;
+ }
+ }
+ cookie_store->Set(keys::kTabIdsKey, tab_ids.release());
+ set_value(cookie_store.release());
+ PostResult();
+ return true;
+}
+
+HRESULT CookieApiResult::RegisterCookieStore(HWND window) {
+ CComPtr<ICeeeCookieExecutor> executor;
+ GetDispatcher()->GetExecutor(window, IID_ICeeeCookieExecutor,
+ reinterpret_cast<void**>(&executor));
+ if (executor == NULL) {
+ LOG(WARNING) << "Failed to get an executor to register a cookie store.";
+ PostError(api_module_constants::kInternalErrorError);
+ return E_FAIL;
+ }
+ HRESULT hr = executor->RegisterCookieStore();
+ if (FAILED(hr)) {
+ // No DCHECK, this may happen if the window/thread dies on the way.
+ LOG(ERROR) << "Can't register cookie store. " << com::LogHr(hr);
+ PostError(api_module_constants::kInternalErrorError);
+ }
+ return hr;
+}
+
+HRESULT CookieApiResult::CookieStoreIsRegistered(HWND window) {
+ CComPtr<ICeeeCookieExecutor> executor;
+ GetDispatcher()->GetExecutor(window, IID_ICeeeCookieExecutor,
+ reinterpret_cast<void**>(&executor));
+ if (executor == NULL) {
+ LOG(WARNING) << "Failed to get an executor to register a cookie store.";
+ PostError(api_module_constants::kInternalErrorError);
+ return E_FAIL;
+ }
+ HRESULT hr = executor->CookieStoreIsRegistered();
+ if (FAILED(hr)) {
+ // No DCHECK, this may happen if the window/thread dies on the way.
+ LOG(ERROR) << "Error accessing cookie store. " << com::LogHr(hr);
+ PostError(api_module_constants::kInternalErrorError);
+ }
+ return hr;
+}
+
+bool CookieApiResult::AppendToTabIdList(HWND window, ListValue* tab_ids) {
+ CComPtr<ICeeeWindowExecutor> executor;
+ GetDispatcher()->GetExecutor(window, IID_ICeeeWindowExecutor,
+ reinterpret_cast<void**>(&executor));
+ if (executor == NULL) {
+ LOG(WARNING) << "Failed to get an executor to get window tabs.";
+ return false;
+ }
+ CComBSTR tab_ids_string;
+ HRESULT hr = executor->GetTabs(&tab_ids_string);
+ if (FAILED(hr)) {
+ // No DCHECK, this may happen if the window/thread dies on the way.
+ LOG(ERROR) << "Can't get tabs for window: " << std::hex << window <<
+ ". " << com::LogHr(hr);
+ return false;
+ }
+ scoped_ptr<ListValue> tabs_list;
+ if (!api_module_util::GetListFromJsonString(CW2A(tab_ids_string).m_psz,
+ &tabs_list)) {
+ NOTREACHED() << "Invalid tabs list BSTR: " << tab_ids_string;
+ return false;
+ }
+ size_t num_values = tabs_list->GetSize();
+ if (num_values % 2 != 0) {
+ // Values should come in pairs, one for the id and another one for the
+ // index.
+ NOTREACHED() << "Invalid tabs list BSTR: " << tab_ids_string;
+ return false;
+ }
+ for (size_t i = 0; i < num_values; i += 2) {
+ int tab_id = 0;
+ if (tabs_list->GetInteger(i, &tab_id))
+ tab_ids->Append(Value::CreateIntegerValue(tab_id));
+ }
+ return true;
+}
+
+void CookieApiResult::FindAllProcessesAndWindows(
+ ProcessWindowMap* all_windows) {
+ DCHECK(all_windows);
+ WindowSet ie_frame_windows;
+ window_utils::FindTopLevelWindows(windows::kIeFrameWindowClass,
+ &ie_frame_windows);
+ if (ie_frame_windows.empty())
+ return;
+
+ WindowSet::const_iterator iter = ie_frame_windows.begin();
+ for (; iter != ie_frame_windows.end(); ++iter) {
+ DWORD process_id = 0;
+ // Skip over windows with no thread.
+ if (::GetWindowThreadProcessId(*iter, &process_id) == 0)
+ continue;
+ DCHECK(process_id);
+
+ if (process_id != 0)
+ (*all_windows)[process_id].insert(*iter);
+ }
+}
+
+HWND CookieApiResult::GetWindowFromStoreId(const std::string& store_id,
+ bool allow_unregistered_store) {
+ DWORD store_process_id = 0;
+ std::istringstream store_id_stream(store_id);
+ store_id_stream >> store_process_id;
+ if (!store_process_id) {
+ PostError(ExtensionErrorUtils::FormatErrorMessage(
+ keys::kInvalidStoreIdError, store_id));
+ return NULL;
+ }
+
+ WindowSet ie_frame_windows;
+ window_utils::FindTopLevelWindows(windows::kIeFrameWindowClass,
+ &ie_frame_windows);
+
+ WindowSet::const_iterator iter = ie_frame_windows.begin();
+ for (; iter != ie_frame_windows.end(); ++iter) {
+ DWORD process_id = 0;
+ if (::GetWindowThreadProcessId(*iter, &process_id) != 0 &&
+ process_id == store_process_id) {
+ if (allow_unregistered_store)
+ return *iter;
+ HRESULT hr = CookieStoreIsRegistered(*iter);
+ // If the above call failed, an error has already been posted.
+ if (FAILED(hr)) {
+ return NULL;
+ } else if (hr == S_OK) {
+ return *iter;
+ }
+ }
+ }
+ // Matching window not found.
+ PostError(ExtensionErrorUtils::FormatErrorMessage(
+ keys::kInvalidStoreIdError, store_id));
+ return NULL;
+}
+
+void GetCookie::Execute(const ListValue& args, int request_id) {
+ scoped_ptr<CookieApiResult> result(CreateApiResult(request_id));
+
+ DictionaryValue* details;
+ if (!args.GetDictionary(0, &details)) {
+ result->PostError(api_module_constants::kInvalidArgumentsError);
+ return;
+ }
+
+ std::string url;
+ if (!details->GetString(keys::kUrlKey, &url)) {
+ result->PostError(api_module_constants::kInvalidArgumentsError);
+ return;
+ }
+ if (!GURL(url).is_valid()) {
+ result->PostError(ExtensionErrorUtils::FormatErrorMessage(
+ keys::kInvalidUrlError, url));
+ return;
+ }
+ // TODO(cindylau@chromium.org): Add extension host permissions
+ // checks against the URL.
+
+ std::string name;
+ if (!details->GetString(keys::kNameKey, &name)) {
+ result->PostError(api_module_constants::kInvalidArgumentsError);
+ return;
+ }
+
+ HWND cookie_store_window = NULL;
+ if (details->HasKey(keys::kStoreIdKey)) {
+ std::string store_id;
+ if (!details->GetString(keys::kStoreIdKey, &store_id)) {
+ result->PostError(api_module_constants::kInvalidArgumentsError);
+ return;
+ }
+ cookie_store_window = result->GetWindowFromStoreId(store_id, false);
+ // If no window was found, an error has already been posted.
+ if (!cookie_store_window)
+ return;
+ } else {
+ // The store ID was unspecified or isn't a registered cookie store
+ // ID. Use the current execution context's cookie store by default.
+ // TODO(cindylau@chromium.org): We currently don't have a way to
+ // get the current execution context, so we are using the top IE
+ // window for now.
+ cookie_store_window = window_api::WindowApiResult::TopIeWindow();
+ }
+ DCHECK(cookie_store_window);
+
+ CookieInfo cookie_info;
+ HRESULT hr = result->GetCookieInfo(url, name, cookie_store_window,
+ &cookie_info);
+ // If the call failed, an error has already been posted.
+ if (FAILED(hr))
+ return;
+ if (hr == S_OK) {
+ DCHECK(WideToASCII(com::ToString(cookie_info.name)) == name);
+ if (!result->CreateCookieValue(cookie_info))
+ return;
+ }
+ result->PostResult();
+}
+
+void GetAllCookies::Execute(const ListValue& args, int request_id) {
+ scoped_ptr<CookieApiResult> result(CreateApiResult(request_id));
+ result->PostError("Not implemented.");
+}
+
+void SetCookie::Execute(const ListValue& args, int request_id) {
+ scoped_ptr<CookieApiResult> result(CreateApiResult(request_id));
+ result->PostError("Not implemented.");
+}
+
+void RemoveCookie::Execute(const ListValue& args, int request_id) {
+ scoped_ptr<CookieApiResult> result(CreateApiResult(request_id));
+ result->PostError("Not implemented.");
+}
+
+void GetAllCookieStores::Execute(const ListValue& args, int request_id) {
+ scoped_ptr<IterativeCookieApiResult> result(CreateApiResult(request_id));
+
+ // TODO(cindylau@chromium.org): Restrict incognito (InPrivate)
+ // windows if incognito is not enabled for the extension. Right now
+ // CEEE has no mechanism to discover the extension's
+ // incognito-enabled setting, so adding support here is premature.
+ CookieApiResult::ProcessWindowMap all_windows;
+ CookieApiResult::FindAllProcessesAndWindows(&all_windows);
+
+ if (all_windows.empty()) {
+ result->FlushAllPosts();
+ return;
+ }
+
+ CookieApiResult::ProcessWindowMap::const_iterator iter = all_windows.begin();
+ for (; iter != all_windows.end(); ++iter) {
+ bool created_ok = result->CreateCookieStoreValue(iter->first, iter->second);
+ LOG_IF(WARNING, !created_ok) << "Could not create cookie store value:"
+ << result->LastError();
+ }
+
+ if (result->IsEmpty()) // This is an error!
+ result->PostError(keys::kNoCookieStoreFoundError);
+
+ result->FlushAllPosts();
+}
+
+// Static wrapper for the real event handler implementation.
+bool CookieChanged::EventHandler(const std::string& input_args,
+ std::string* converted_args,
+ ApiDispatcher* dispatcher) {
+ CookieChanged event_handler;
+ return event_handler.EventHandlerImpl(input_args, converted_args);
+}
+
+// Handles a cookies.onChanged event by verifying that the store ID is
+// registered. If not, this function registers the store ID.
+bool CookieChanged::EventHandlerImpl(const std::string& input_args,
+ std::string* converted_args) {
+ DCHECK(converted_args);
+ // We don't need to modify the arguments, we only need to verify that the
+ // store ID is registered.
+ *converted_args = input_args;
+
+ // Get the cookie info from the input arguments.
+ scoped_ptr<Value> input_val(base::JSONReader::Read(input_args, true));
+ DCHECK(input_val.get() != NULL);
+ if (input_val == NULL) {
+ LOG(ERROR) << "Invalid Arguments sent to CookieChangedEventHandler()";
+ return false;
+ }
+
+ ListValue* input_list = static_cast<ListValue*>(input_val.get());
+ DCHECK(input_list && input_list->GetSize() == 1);
+
+ DictionaryValue* changeInfo = NULL;
+ bool success = input_list->GetDictionary(0, &changeInfo);
+ DictionaryValue* cookie = NULL;
+ if (success && changeInfo && changeInfo->HasKey(keys::kCookieKey))
+ success = changeInfo->GetDictionary(keys::kCookieKey, &cookie);
+ if (!success || cookie == NULL) {
+ NOTREACHED() << "Failed to get the cookie value from the list of args.";
+ return false;
+ }
+ std::string store_id;
+ if (cookie->HasKey(keys::kStoreIdKey))
+ success = cookie->GetString(keys::kStoreIdKey, &store_id);
+ if (!success || store_id.size() == 0) {
+ NOTREACHED() << "Failed to get the store ID value from the cookie arg.";
+ return false;
+ }
+
+ scoped_ptr<CookieApiResult> api_result(CreateApiResult());
+ HWND store_window = api_result->GetWindowFromStoreId(store_id, true);
+ if (store_window == NULL) {
+ // Error was already logged by GetWindowFromStoreId.
+ return false;
+ }
+ HRESULT is_registered = api_result->CookieStoreIsRegistered(store_window);
+ if (FAILED(is_registered)) {
+ // Error was already logged by CookieStoreIsRegistered.
+ return false;
+ }
+ if (is_registered == S_FALSE) {
+ // The store ID has not yet been queried by the user; register it here
+ // before exposing it to the user.
+ is_registered = api_result->RegisterCookieStore(store_window);
+ }
+ if (is_registered != S_OK) {
+ // Any errors should have already been logged by RegisterCookieStore.
+ return false;
+ }
+
+ return true;
+}
+
+CookieInfo::CookieInfo() {
+ name = NULL;
+ value = NULL;
+ domain = NULL;
+ host_only = false;
+ path = NULL;
+ secure = false;
+ http_only = false;
+ session = false;
+ expiration_date = 0;
+ store_id = NULL;
+}
+
+CookieInfo::~CookieInfo() {
+ // SysFreeString accepts NULL pointers.
+ ::SysFreeString(name);
+ ::SysFreeString(value);
+ ::SysFreeString(domain);
+ ::SysFreeString(path);
+ ::SysFreeString(store_id);
+}
+
+} // namespace cookie_api
diff --git a/ceee/ie/broker/cookie_api_module.h b/ceee/ie/broker/cookie_api_module.h
new file mode 100644
index 0000000..ceace34
--- /dev/null
+++ b/ceee/ie/broker/cookie_api_module.h
@@ -0,0 +1,149 @@
+// 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.
+//
+// Cookie API implementation.
+
+#ifndef CEEE_IE_BROKER_COOKIE_API_MODULE_H_
+#define CEEE_IE_BROKER_COOKIE_API_MODULE_H_
+
+#include <list>
+#include <map>
+#include <set>
+#include <string>
+
+#include "ceee/ie/broker/api_dispatcher.h"
+
+#include "toolband.h" //NOLINT
+
+namespace cookie_api {
+
+class CookieApiResult;
+typedef ApiResultCreator<CookieApiResult> CookieApiResultCreator;
+
+// Registers all Cookie API Invocations with the given dispatcher.
+void RegisterInvocations(ApiDispatcher* dispatcher);
+
+// Helper class to handle the data cleanup.
+class CookieInfo : public CeeeCookieInfo {
+ public:
+ CookieInfo();
+ ~CookieInfo();
+ private:
+ DISALLOW_COPY_AND_ASSIGN(CookieInfo);
+};
+
+class CookieApiResult : public ApiDispatcher::InvocationResult {
+ public:
+ typedef std::set<HWND> WindowSet;
+ typedef std::map<DWORD, WindowSet> ProcessWindowMap;
+
+ explicit CookieApiResult(int request_id)
+ : ApiDispatcher::InvocationResult(request_id) {
+ }
+
+ // Creates a value object with the information for a cookie and assigns the
+ // API result value with it.
+ // @return True on success. Returns false and also calls
+ // ApiDispatcher::InvocationResult::PostError on failure.
+ virtual bool CreateCookieValue(const CookieInfo& info);
+
+ // Gets information for the cookie with the given URL and name, using the
+ // cookie store corresponding to the given window.
+ virtual HRESULT GetCookieInfo(const std::string& url,
+ const std::string& name,
+ HWND window, CookieInfo* cookie_info);
+
+ // Constructs a cookie store value given the IE process ID and a set of all
+ // IEFrame windows for that process. As defined by the chrome.cookies
+ // API, a cookie store value consists of a string ID and a list of all tab
+ // IDs belonging to that cookie store. Assigns the API result value with the
+ // newly constructed cookie store object.
+ // @return True on success. Returns false and also calls
+ // ApiDispatcher::InvocationResult::PostError on failure.
+ virtual bool CreateCookieStoreValue(DWORD process_id,
+ const WindowSet& windows);
+
+ // Finds an IEFrame window belonging to the process associated with the given
+ // cookie store ID.
+ // If allow_unregistered_store is true, the function succeeds even if the
+ // given store ID has not been registered. If it's false, an unregistered
+ // store ID will result in a failure and PostError.
+ // @return A window associated with the given cookie store, or NULL on error.
+ // Will also call ApiDispatcher::InvocationResult::PostError
+ // on failure to find a window.
+ virtual HWND GetWindowFromStoreId(const std::string& store_id,
+ bool allow_unregistered_store);
+
+ // Registers the cookie store for the process corresponding to the given
+ // window.
+ // @return S_OK on success, failure code on error and will also call
+ // ApiDispatcher::InvocationResult::PostError on failures.
+ virtual HRESULT RegisterCookieStore(HWND window);
+
+ // Checks whether the given window's process is a registered cookie store.
+ // @return S_OK if the cookie store is registered, S_FALSE if not. Returns
+ // a failure code on error and will also call
+ // ApiDispatcher::InvocationResult::PostError on failures.
+ virtual HRESULT CookieStoreIsRegistered(HWND window);
+
+ // Finds all IEFrame windows and puts them in a map of HWND sets keyed by
+ // process ID.
+ static void FindAllProcessesAndWindows(ProcessWindowMap* all_windows);
+
+ private:
+ // Retrieves all tab IDs from the given window and adds them to the given tab
+ // ID list.
+ // @return false on error and will also call
+ // ApiDispatcher::InvocationResult::PostError on failures.
+ bool AppendToTabIdList(HWND window, ListValue* tab_ids);
+};
+
+typedef IterativeApiResult<CookieApiResult> IterativeCookieApiResult;
+
+class GetCookie : public CookieApiResultCreator {
+ public:
+ virtual void Execute(const ListValue& args, int request_id);
+};
+class GetAllCookies : public CookieApiResultCreator {
+ public:
+ virtual void Execute(const ListValue& args, int request_id);
+};
+class SetCookie : public CookieApiResultCreator {
+ public:
+ virtual void Execute(const ListValue& args, int request_id);
+};
+class RemoveCookie : public CookieApiResultCreator {
+ public:
+ virtual void Execute(const ListValue& args, int request_id);
+};
+class GetAllCookieStores : public ApiResultCreator<IterativeCookieApiResult> {
+ public:
+ virtual void Execute(const ListValue& args, int request_id);
+};
+
+// Permanent event handler for cookies.onChanged. We define a class for the
+// event in order to ease unit testing of the CookieApiResult used by the
+// event handler implementation.
+class CookieChanged {
+ public:
+ bool EventHandlerImpl(const std::string& input_args,
+ std::string* converted_args);
+
+ // Static wrapper for the event handler implementation.
+ static bool EventHandler(const std::string& input_args,
+ std::string* converted_args,
+ ApiDispatcher* dispatcher);
+ protected:
+ virtual ~CookieChanged() {}
+
+ // Returns allocated memory; the caller is responsible for
+ // freeing it.
+ virtual CookieApiResult* CreateApiResult() {
+ return new CookieApiResult(CookieApiResult::kNoRequestId);
+ }
+};
+
+} // namespace cookie_api
+
+#endif // CEEE_IE_BROKER_COOKIE_API_MODULE_H_
diff --git a/ceee/ie/broker/cookie_api_module_unittest.cc b/ceee/ie/broker/cookie_api_module_unittest.cc
new file mode 100644
index 0000000..00e28c8
--- /dev/null
+++ b/ceee/ie/broker/cookie_api_module_unittest.cc
@@ -0,0 +1,777 @@
+// 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.
+//
+// Cookie API implementation unit tests.
+
+// MockWin32 can't be included after ChromeFrameHost because of an include
+// incompatibility with atlwin.h.
+#include "ceee/testing/utils/mock_win32.h" // NOLINT
+
+#include "base/string_util.h"
+#include "ceee/common/process_utils_win.h"
+#include "ceee/ie/broker/api_dispatcher.h"
+#include "ceee/ie/broker/api_module_constants.h"
+#include "ceee/ie/broker/cookie_api_module.h"
+#include "ceee/ie/broker/window_api_module.h"
+#include "ceee/ie/testing/mock_broker_and_friends.h"
+#include "ceee/testing/utils/instance_count_mixin.h"
+#include "ceee/testing/utils/mock_window_utils.h"
+#include "ceee/testing/utils/test_utils.h"
+#include "chrome/browser/extensions/extension_cookies_api_constants.h"
+#include "chrome/common/extensions/extension_error_utils.h"
+#include "testing/gmock/include/gmock/gmock.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+namespace keys = extension_cookies_api_constants;
+
+namespace {
+
+using cookie_api::CookieApiResult;
+using cookie_api::CookieChanged;
+using cookie_api::CookieInfo;
+using cookie_api::GetCookie;
+using cookie_api::GetAllCookies;
+using cookie_api::SetCookie;
+using cookie_api::RemoveCookie;
+using cookie_api::GetAllCookieStores;
+using cookie_api::IterativeCookieApiResult;
+using testing::_;
+using testing::AddRef;
+using testing::AtLeast;
+using testing::Invoke;
+using testing::InstanceCountMixin;
+using testing::MockApiDispatcher;
+using testing::MockApiInvocation;
+using testing::NotNull;
+using testing::Return;
+using testing::SetArgumentPointee;
+using testing::StrEq;
+using testing::StrictMock;
+
+using window_api::WindowApiResult;
+
+const int kRequestId = 21;
+const int kThreadId = 1;
+const int kNumWindows = 4;
+const int kNumProcesses = 3;
+const HWND kAllWindows[kNumWindows] = {(HWND)1, (HWND)17, (HWND)5, (HWND)8};
+const HWND kBadWindows[] = {(HWND)23, (HWND)2};
+const DWORD kWindowProcesses[kNumWindows] = {2, 3, 2, 7};
+const DWORD kAllProcesses[kNumProcesses] = {2, 3, 7};
+const HANDLE kProcessHandles[kNumProcesses]
+ = {HANDLE(0xFF0002), HANDLE(0xFF0003), HANDLE(0xFF0007)};
+
+class MockCookieApiResult : public CookieApiResult {
+ public:
+ explicit MockCookieApiResult(int request_id) : CookieApiResult(request_id) {}
+
+ MOCK_METHOD0(PostResult, void());
+ MOCK_METHOD1(PostError, void(const std::string&));
+ MOCK_METHOD4(GetCookieInfo, HRESULT(const std::string&, const std::string&,
+ HWND, CookieInfo*));
+ MOCK_METHOD2(CreateCookieStoreValue, bool(DWORD, const WindowSet&));
+ MOCK_METHOD1(RegisterCookieStore, HRESULT(HWND));
+ MOCK_METHOD1(CookieStoreIsRegistered, HRESULT(HWND));
+ MOCK_METHOD2(GetWindowFromStoreId, HWND(const std::string&, bool));
+
+ virtual ApiDispatcher* GetDispatcher() {
+ return &mock_api_dispatcher_;
+ }
+
+ HRESULT CallGetCookieInfo(const std::string& url,
+ const std::string& name,
+ HWND window, CookieInfo* cookie_info) {
+ return CookieApiResult::GetCookieInfo(url, name, window, cookie_info);
+ }
+
+ bool CallCreateCookieStoreValue(DWORD process_id, const WindowSet& windows) {
+ return CookieApiResult::CreateCookieStoreValue(process_id, windows);
+ }
+
+ HWND CallGetWindowFromStoreId(const std::string& store_id,
+ bool allow_unregistered_store) {
+ return CookieApiResult::GetWindowFromStoreId(
+ store_id, allow_unregistered_store);
+ }
+
+ StrictMock<MockApiDispatcher> mock_api_dispatcher_;
+};
+
+// Mocks the iterative version of result. Unlike in the mock of the 'straight'
+// version, here PostResult and Post Error are not mocked because they serve
+// to accumulate result, which can be examined by standard means.
+class MockIterativeCookieApiResult : public IterativeCookieApiResult {
+ public:
+ explicit MockIterativeCookieApiResult(int request_id)
+ : IterativeCookieApiResult(request_id) {}
+
+ MOCK_METHOD0(CallRealPostResult, void());
+ MOCK_METHOD1(CallRealPostError, void(const std::string&));
+ MOCK_METHOD4(GetCookieInfo, HRESULT(const std::string&, const std::string&,
+ HWND, CookieInfo*));
+ MOCK_METHOD2(CreateCookieStoreValue, bool(DWORD, const WindowSet&));
+ MOCK_METHOD1(RegisterCookieStore, HRESULT(HWND));
+ MOCK_METHOD1(CookieStoreIsRegistered, HRESULT(HWND));
+ MOCK_METHOD2(GetWindowFromStoreId, HWND(const std::string&, bool));
+
+ virtual void PostError(const std::string& error) {
+ ++error_counter_;
+ IterativeCookieApiResult::PostError(error);
+ }
+
+ virtual void PostResult() {
+ ++success_counter_;
+ IterativeCookieApiResult::PostResult();
+ }
+
+ virtual ApiDispatcher* GetDispatcher() {
+ return &mock_api_dispatcher_;
+ }
+
+ HRESULT CallGetCookieInfo(const std::string& url,
+ const std::string& name,
+ HWND window, CookieInfo* cookie_info) {
+ return CookieApiResult::GetCookieInfo(url, name, window, cookie_info);
+ }
+
+ bool CallCreateCookieStoreValue(DWORD process_id, const WindowSet& windows) {
+ return CookieApiResult::CreateCookieStoreValue(process_id, windows);
+ }
+
+ static HRESULT MockGetTabs(BSTR* tab_list) {
+ // The string is not retained since the calling object takes overship.
+ *tab_list = SysAllocString(L"[27, 1]");
+ return S_OK;
+ }
+
+ HWND CallGetWindowFromStoreId(const std::string& store_id,
+ bool allow_unregistered_store) {
+ return CookieApiResult::GetWindowFromStoreId(
+ store_id, allow_unregistered_store);
+ }
+
+ static void ResetCounters() {
+ success_counter_ = 0;
+ error_counter_ = 0;
+ }
+
+ static int success_counter() {
+ return success_counter_;
+ }
+
+ static int error_counter() {
+ return error_counter_;
+ }
+
+ StrictMock<MockApiDispatcher> mock_api_dispatcher_;
+
+ private:
+ static int success_counter_;
+ static int error_counter_;
+};
+
+int MockIterativeCookieApiResult::success_counter_ = 0;
+int MockIterativeCookieApiResult::error_counter_ = 0;
+
+class MockCookieChanged : public CookieChanged {
+ public:
+ void AllocateApiResult() {
+ api_result_.reset(new MockCookieApiResult(CookieApiResult::kNoRequestId));
+ }
+
+ virtual CookieApiResult* CreateApiResult() {
+ DCHECK(api_result_.get() != NULL);
+ return api_result_.release();
+ }
+
+ scoped_ptr<MockCookieApiResult> api_result_;
+};
+
+// Mock static functions defined in CookieApiResult.
+MOCK_STATIC_CLASS_BEGIN(MockCookieApiResultStatics)
+ MOCK_STATIC_INIT_BEGIN(MockCookieApiResultStatics)
+ MOCK_STATIC_INIT2(CookieApiResult::FindAllProcessesAndWindows,
+ FindAllProcessesAndWindows);
+ MOCK_STATIC_INIT_END()
+ MOCK_STATIC1(void, , FindAllProcessesAndWindows,
+ CookieApiResult::ProcessWindowMap*);
+MOCK_STATIC_CLASS_END(MockCookieApiResultStatics)
+
+// Mock static functions defined in WindowApiResult.
+MOCK_STATIC_CLASS_BEGIN(MockWindowApiResultStatics)
+ MOCK_STATIC_INIT_BEGIN(MockWindowApiResultStatics)
+ MOCK_STATIC_INIT2(WindowApiResult::TopIeWindow,
+ TopIeWindow);
+ MOCK_STATIC_INIT_END()
+ MOCK_STATIC0(HWND, , TopIeWindow);
+MOCK_STATIC_CLASS_END(MockWindowApiResultStatics)
+
+class CookieApiTests: public testing::Test {
+ protected:
+ template <class T> void ExpectInvalidArguments(
+ StrictMock<MockApiInvocation<CookieApiResult,
+ MockCookieApiResult,
+ T> >& invocation,
+ const ListValue& args) {
+ invocation.AllocateNewResult(kRequestId);
+ EXPECT_CALL(*invocation.invocation_result_,
+ PostError(StrEq(
+ api_module_constants::kInvalidArgumentsError))).Times(1);
+ invocation.Execute(args, kRequestId);
+ }
+
+ StrictMock<testing::MockUser32> user32_;
+};
+
+TEST_F(CookieApiTests, RegisterInvocations) {
+ StrictMock<MockApiDispatcher> disp;
+ EXPECT_CALL(disp,
+ RegisterInvocation(NotNull(), NotNull())).Times(AtLeast(5));
+ cookie_api::RegisterInvocations(&disp);
+}
+
+TEST_F(CookieApiTests, CreateCookieValue) {
+ CookieInfo cookie_info;
+ cookie_info.name = ::SysAllocString(L"foo");
+ cookie_info.value = ::SysAllocString(L"bar");
+ cookie_info.domain = ::SysAllocString(L"google.com");
+ cookie_info.host_only = FALSE;
+ cookie_info.path = ::SysAllocString(L"/testpath");
+ cookie_info.secure = TRUE;
+ cookie_info.session = TRUE;
+ cookie_info.expiration_date = 0;
+ cookie_info.store_id = ::SysAllocString(L"test_store_id");
+
+ CookieApiResult result(CookieApiResult::kNoRequestId);
+ EXPECT_TRUE(result.CreateCookieValue(cookie_info));
+ EXPECT_EQ(Value::TYPE_DICTIONARY, result.value()->GetType());
+ const DictionaryValue* cookie =
+ reinterpret_cast<const DictionaryValue*>(result.value());
+
+ std::string string_value;
+ bool boolean_value;
+ EXPECT_TRUE(cookie->GetString(keys::kNameKey, &string_value));
+ EXPECT_EQ("foo", string_value);
+ EXPECT_TRUE(cookie->GetString(keys::kValueKey, &string_value));
+ EXPECT_EQ("bar", string_value);
+ EXPECT_TRUE(cookie->GetString(keys::kDomainKey, &string_value));
+ EXPECT_EQ("google.com", string_value);
+ EXPECT_TRUE(cookie->GetBoolean(keys::kHostOnlyKey, &boolean_value));
+ EXPECT_FALSE(boolean_value);
+ EXPECT_TRUE(cookie->GetString(keys::kPathKey, &string_value));
+ EXPECT_EQ("/testpath", string_value);
+ EXPECT_TRUE(cookie->GetBoolean(keys::kSecureKey, &boolean_value));
+ EXPECT_TRUE(boolean_value);
+ EXPECT_TRUE(cookie->GetBoolean(keys::kHttpOnlyKey, &boolean_value));
+ EXPECT_FALSE(boolean_value);
+ EXPECT_TRUE(cookie->GetBoolean(keys::kSessionKey, &boolean_value));
+ EXPECT_TRUE(boolean_value);
+ EXPECT_TRUE(cookie->GetString(keys::kStoreIdKey, &string_value));
+ EXPECT_EQ("test_store_id", string_value);
+ EXPECT_FALSE(cookie->HasKey(keys::kExpirationDateKey));
+}
+
+TEST_F(CookieApiTests, GetCookieInfo) {
+ testing::LogDisabler no_dchecks;
+ MockCookieApiResult result(CookieApiResult::kNoRequestId);
+ HWND test_frame_window = reinterpret_cast<HWND>(1);
+ HWND test_tab_window = reinterpret_cast<HWND>(2);
+ StrictMock<testing::MockWindowUtils> window_utils;
+
+ // Test no tab windows found.
+ EXPECT_CALL(window_utils, FindDescendentWindow(_, _, _, _))
+ .WillOnce(Return(false));
+ EXPECT_CALL(result, PostError(_)).Times(1);
+ EXPECT_HRESULT_FAILED(result.CallGetCookieInfo("helloworld", "foo",
+ test_frame_window, NULL));
+ // Test invalid tab window.
+ EXPECT_CALL(window_utils, FindDescendentWindow(_, _, _, _)).WillOnce(
+ DoAll(SetArgumentPointee<3>(HWND(NULL)), Return(true)));
+ EXPECT_CALL(result, PostError(_)).Times(1);
+ EXPECT_HRESULT_FAILED(result.CallGetCookieInfo("helloworld", "foo",
+ test_frame_window, NULL));
+
+ EXPECT_CALL(window_utils, FindDescendentWindow(_, _, _, _)).WillRepeatedly(
+ DoAll(SetArgumentPointee<3>(test_tab_window), Return(true)));
+
+ // Test failed executor access.
+ EXPECT_CALL(result.mock_api_dispatcher_,
+ GetExecutor(test_tab_window, _, NotNull())).
+ WillOnce(SetArgumentPointee<2>(static_cast<void*>(NULL)));
+ EXPECT_CALL(result, PostError(_)).Times(1);
+ EXPECT_HRESULT_FAILED(result.CallGetCookieInfo("helloworld", "foo",
+ test_frame_window, NULL));
+ // Test executor.
+ testing::MockCookieExecutor* mock_cookie_executor;
+ CComPtr<ICeeeCookieExecutor> mock_cookie_executor_keeper;
+ EXPECT_HRESULT_SUCCEEDED(testing::MockCookieExecutor::CreateInitialized(
+ &mock_cookie_executor, &mock_cookie_executor_keeper));
+ EXPECT_CALL(result.mock_api_dispatcher_,
+ GetExecutor(test_tab_window, _, NotNull())).
+ WillRepeatedly(DoAll(SetArgumentPointee<2>(
+ mock_cookie_executor_keeper.p),
+ AddRef(mock_cookie_executor_keeper.p)));
+ // Failing Executor.
+ // The executor classes are already strict from their base class impl.
+ EXPECT_CALL(*mock_cookie_executor, GetCookie(_, _, _)).
+ WillOnce(Return(E_FAIL));
+ EXPECT_CALL(result, PostError(_)).Times(1);
+ EXPECT_HRESULT_FAILED(result.CallGetCookieInfo("helloworld", "foo",
+ test_frame_window, NULL));
+ // Success.
+ EXPECT_CALL(*mock_cookie_executor, GetCookie(_, _, _)).
+ WillOnce(Return(S_OK));
+ EXPECT_CALL(result, PostError(_)).Times(0);
+ EXPECT_EQ(S_OK, result.CallGetCookieInfo("helloworld", "foo",
+ test_frame_window, NULL));
+}
+
+TEST_F(CookieApiTests, GetWindowFromStoreId) {
+ MockCookieApiResult result(CookieApiResult::kNoRequestId);
+
+ EXPECT_CALL(result, PostError(_)).Times(1);
+ EXPECT_EQ((HWND)NULL,
+ result.CallGetWindowFromStoreId("test_store_id", false));
+
+ std::set<HWND> empty_window_set;
+ StrictMock<testing::MockWindowUtils> window_utils;
+ EXPECT_CALL(window_utils, FindTopLevelWindows(_, _)).
+ WillOnce(SetArgumentPointee<1>(empty_window_set));
+ EXPECT_CALL(result, PostError(_)).Times(1);
+ EXPECT_EQ((HWND)NULL, result.CallGetWindowFromStoreId("1", false));
+
+ std::set<HWND> test_windows(kAllWindows, kAllWindows + kNumWindows);
+ EXPECT_CALL(window_utils, FindTopLevelWindows(_, _)).
+ WillRepeatedly(SetArgumentPointee<1>(test_windows));
+ EXPECT_CALL(user32_, GetWindowThreadProcessId(_, _)).
+ WillRepeatedly(DoAll(SetArgumentPointee<1>(1),
+ Return(kThreadId)));
+ // Test unregistered cookie store.
+ EXPECT_CALL(result, CookieStoreIsRegistered(_)).
+ WillOnce(Return(E_FAIL));
+ EXPECT_CALL(result, PostError(_)).Times(1);
+ EXPECT_EQ((HWND)NULL, result.CallGetWindowFromStoreId("1", false));
+
+ EXPECT_CALL(result, CookieStoreIsRegistered(_)).
+ WillRepeatedly(Return(S_FALSE));
+ EXPECT_EQ((HWND)NULL, result.CallGetWindowFromStoreId("1", false));
+
+ EXPECT_EQ((HWND)1, result.CallGetWindowFromStoreId("1", true));
+
+ // Test registered cookie store.
+ EXPECT_CALL(result, CookieStoreIsRegistered(_)).
+ WillRepeatedly(Return(S_OK));
+ EXPECT_EQ((HWND)1, result.CallGetWindowFromStoreId("1", false));
+ EXPECT_EQ((HWND)1, result.CallGetWindowFromStoreId("1", true));
+}
+
+TEST_F(CookieApiTests, CreateCookieStoreValue) {
+ testing::LogDisabler no_dchecks;
+ MockCookieApiResult result(CookieApiResult::kNoRequestId);
+ CookieApiResult::WindowSet windows;
+
+ // Test empty window set.
+ EXPECT_CALL(result, PostError(_)).Times(1);
+ EXPECT_FALSE(result.CallCreateCookieStoreValue(2, windows));
+ EXPECT_EQ(NULL, result.value());
+
+ // Test failed cookie store registration.
+ windows.insert(kAllWindows[0]);
+ EXPECT_CALL(result, RegisterCookieStore(kAllWindows[0])).
+ WillOnce(Return(E_FAIL));
+ EXPECT_FALSE(result.CallCreateCookieStoreValue(2, windows));
+ EXPECT_EQ(NULL, result.value());
+
+ // Test failed executor access.
+ EXPECT_CALL(result, RegisterCookieStore(kAllWindows[0])).
+ WillRepeatedly(Return(S_OK));
+ EXPECT_CALL(result.mock_api_dispatcher_,
+ GetExecutor(kAllWindows[0], _, NotNull())).
+ WillOnce(SetArgumentPointee<2>(static_cast<void*>(NULL)));
+ EXPECT_CALL(result, PostError(_)).Times(1);
+ EXPECT_FALSE(result.CallCreateCookieStoreValue(2, windows));
+ EXPECT_EQ(NULL, result.value());
+
+ // Test executor.
+ testing::MockWindowExecutor* mock_window_executor;
+ CComPtr<ICeeeWindowExecutor> mock_window_executor_keeper;
+ EXPECT_HRESULT_SUCCEEDED(testing::MockWindowExecutor::CreateInitialized(
+ &mock_window_executor, &mock_window_executor_keeper));
+ EXPECT_CALL(result.mock_api_dispatcher_,
+ GetExecutor(kAllWindows[0], _, NotNull())).
+ WillRepeatedly(DoAll(SetArgumentPointee<2>(
+ mock_window_executor_keeper.p),
+ AddRef(mock_window_executor_keeper.p)));
+ // Failing Executor.
+ // The executor classes are already strict from their base class impl.
+ EXPECT_CALL(*mock_window_executor, GetTabs(NotNull())).
+ WillOnce(Return(E_FAIL));
+ EXPECT_CALL(result, PostError(_)).Times(1);
+ EXPECT_FALSE(result.CallCreateCookieStoreValue(2, windows));
+ EXPECT_EQ(NULL, result.value());
+
+ // Test success.
+ BSTR tab_ids = SysAllocString(L"[27, 1]");
+ EXPECT_CALL(*mock_window_executor, GetTabs(NotNull())).
+ WillOnce(DoAll(SetArgumentPointee<0>(tab_ids), Return(S_OK)));
+ EXPECT_CALL(result, PostResult()).Times(1);
+ EXPECT_TRUE(result.CallCreateCookieStoreValue(2, windows));
+
+ ASSERT_TRUE(result.value() != NULL &&
+ result.value()->IsType(Value::TYPE_DICTIONARY));
+ const DictionaryValue* cookie_store =
+ static_cast<const DictionaryValue*>(result.value());
+ std::string id;
+ EXPECT_TRUE(cookie_store->GetString(keys::kIdKey, &id));
+ EXPECT_EQ(std::string("2"), id);
+ ListValue* tab_list;
+ EXPECT_TRUE(cookie_store->GetList(keys::kTabIdsKey, &tab_list));
+ EXPECT_EQ(1, tab_list->GetSize());
+ int tab;
+ EXPECT_TRUE(tab_list->GetInteger(0, &tab));
+ EXPECT_EQ(27, tab);
+ // The cookie_store takes ownership of this pointer, so there's no need to
+ // free it.
+ tab_ids = NULL;
+}
+
+TEST_F(CookieApiTests, FindAllProcessesAndWindowsNoWindows) {
+ std::set<HWND> empty_window_set;
+ StrictMock<testing::MockWindowUtils> window_utils;
+ EXPECT_CALL(window_utils, FindTopLevelWindows(_, _)).
+ WillOnce(SetArgumentPointee<1>(empty_window_set));
+ EXPECT_CALL(user32_, GetWindowThreadProcessId(_, _)).Times(0);
+
+ CookieApiResult::ProcessWindowMap all_windows;
+ CookieApiResult::FindAllProcessesAndWindows(&all_windows);
+
+ EXPECT_EQ(0, all_windows.size());
+}
+
+TEST_F(CookieApiTests, FindAllProcessesAndWindowsMultipleProcesses) {
+ testing::LogDisabler no_dchecks;
+
+ std::set<HWND> test_windows(kAllWindows, kAllWindows + kNumWindows);
+ test_windows.insert(kBadWindows[0]);
+ test_windows.insert(kBadWindows[1]);
+ StrictMock<testing::MockWindowUtils> window_utils;
+
+ EXPECT_CALL(window_utils, FindTopLevelWindows(_, _)).
+ WillOnce(SetArgumentPointee<1>(test_windows));
+ for (int i = 0; i < kNumWindows; ++i) {
+ EXPECT_CALL(user32_, GetWindowThreadProcessId(kAllWindows[i], _)).
+ WillOnce(DoAll(SetArgumentPointee<1>(kWindowProcesses[i]),
+ Return(kThreadId)));
+ }
+ // Test that threads and processes with ID 0 don't get added.
+ EXPECT_CALL(user32_, GetWindowThreadProcessId(kBadWindows[0], _)).
+ WillOnce(Return(0));
+ EXPECT_CALL(user32_, GetWindowThreadProcessId(kBadWindows[1], _)).
+ WillOnce(DoAll(SetArgumentPointee<1>(0), Return(kThreadId)));
+
+ CookieApiResult::ProcessWindowMap all_windows;
+ CookieApiResult::FindAllProcessesAndWindows(&all_windows);
+
+ EXPECT_EQ(kNumProcesses, all_windows.size());
+
+ CookieApiResult::WindowSet& window_set = all_windows[kAllProcesses[0]];
+ EXPECT_EQ(2, window_set.size());
+ EXPECT_TRUE(window_set.find(kAllWindows[0]) != window_set.end());
+ EXPECT_TRUE(window_set.find(kAllWindows[2]) != window_set.end());
+
+ window_set = all_windows[kAllProcesses[1]];
+ EXPECT_EQ(1, window_set.size());
+ EXPECT_TRUE(window_set.find(kAllWindows[1]) != window_set.end());
+
+ window_set = all_windows[kAllProcesses[2]];
+ EXPECT_EQ(1, window_set.size());
+ EXPECT_TRUE(window_set.find(kAllWindows[3]) != window_set.end());
+}
+
+TEST_F(CookieApiTests, GetCookiesInvalidArgumentsResultInErrors) {
+ testing::LogDisabler no_dchecks;
+ ListValue args;
+ StrictMock<MockApiInvocation<CookieApiResult,
+ MockCookieApiResult,
+ GetCookie> > invocation;
+ // First test that required arguments are enforced.
+ ExpectInvalidArguments(invocation, args);
+ DictionaryValue* details = new DictionaryValue();
+ args.Append(details);
+ ExpectInvalidArguments(invocation, args);
+ // TODO(cindylau@chromium.org): Check for invalid URL error.
+ details->SetString(keys::kUrlKey, "helloworld");
+ invocation.AllocateNewResult(kRequestId);
+ EXPECT_CALL(*invocation.invocation_result_,
+ PostError(StrEq(ExtensionErrorUtils::FormatErrorMessage(
+ keys::kInvalidUrlError, "helloworld")))).
+ Times(1);
+ invocation.Execute(args, kRequestId);
+ details->SetString(keys::kUrlKey, "http://www.foobar.com");
+ ExpectInvalidArguments(invocation, args);
+ details->SetString(keys::kNameKey, "foo");
+ details->SetInteger(keys::kStoreIdKey, 1);
+ ExpectInvalidArguments(invocation, args);
+
+ // GetWindowFromStoreId fails.
+ std::string store_id("test_cookie_store");
+ details->SetString(keys::kStoreIdKey, store_id);
+ invocation.AllocateNewResult(kRequestId);
+ EXPECT_CALL(*invocation.invocation_result_,
+ GetWindowFromStoreId(StrEq(store_id), false)).
+ WillOnce(Return((HWND)NULL));
+ EXPECT_CALL(*invocation.invocation_result_, PostResult()).Times(0);
+ invocation.Execute(args, kRequestId);
+}
+
+TEST_F(CookieApiTests, GetCookiesTopIeWindowIsDefaultCookieStore) {
+ // TODO(cindylau@chromium.org): The expected behavior here will
+ // change; we should use the current execution context's cookie
+ // store, not the top window.
+ testing::LogDisabler no_dchecks;
+ StrictMock<MockApiInvocation<CookieApiResult,
+ MockCookieApiResult,
+ GetCookie> > invocation;
+
+ ListValue args;
+ DictionaryValue* details = new DictionaryValue();
+ args.Append(details);
+ details->SetString(keys::kUrlKey, "http://www.foobar.com");
+ details->SetString(keys::kNameKey, "foo");
+
+ invocation.AllocateNewResult(kRequestId);
+ MockWindowApiResultStatics window_statics;
+ EXPECT_CALL(window_statics, TopIeWindow()).WillOnce(Return(HWND(42)));
+ EXPECT_CALL(*invocation.invocation_result_, GetCookieInfo(_, _, _, _)).
+ WillOnce(Return(S_OK));
+ EXPECT_CALL(*invocation.invocation_result_, PostResult()).Times(1);
+ invocation.Execute(args, kRequestId);
+}
+
+TEST_F(CookieApiTests, GetAllCookieStores) {
+ testing::LogDisabler no_dchecks;
+
+ MockCookieApiResultStatics result_statics;
+ CookieApiResult::ProcessWindowMap all_windows;
+ ListValue args;
+
+ StrictMock<MockApiInvocation<IterativeCookieApiResult,
+ MockIterativeCookieApiResult,
+ GetAllCookieStores> > invocation;
+
+ // First test the trivial case of no cookie stores. One call to success and
+ // no calls to error function.
+ EXPECT_CALL(result_statics, FindAllProcessesAndWindows(_)).
+ WillOnce(SetArgumentPointee<0>(all_windows));
+ invocation.AllocateNewResult(kRequestId);
+ EXPECT_CALL(*invocation.invocation_result_, CallRealPostResult()).Times(1);
+ EXPECT_CALL(*invocation.invocation_result_, CallRealPostError(_)).Times(0);
+ invocation.Execute(args, kRequestId);
+
+ // Now test cases with multiple windows.
+ for (int i = 0; i < kNumWindows; ++i) {
+ all_windows[kWindowProcesses[i]].insert(kAllWindows[i]);
+ }
+ EXPECT_CALL(result_statics, FindAllProcessesAndWindows(_)).
+ WillRepeatedly(SetArgumentPointee<0>(all_windows));
+
+ // Test error case: can't access a single cookie store.
+ MockIterativeCookieApiResult::ResetCounters();
+ invocation.AllocateNewResult(kRequestId);
+ EXPECT_CALL(*invocation.invocation_result_,
+ CreateCookieStoreValue(_, _)).
+ WillRepeatedly(Return(false));
+ EXPECT_CALL(*invocation.invocation_result_, CallRealPostResult()).Times(0);
+ EXPECT_CALL(*invocation.invocation_result_, CallRealPostError(_)).Times(1);
+ invocation.Execute(args, kRequestId);
+
+ // Test error case: accessing dispatcher fails each time and so an error is
+ // reported. Count errors and make sure everything is reported as error.
+ MockIterativeCookieApiResult::ResetCounters();
+ invocation.AllocateNewResult(kRequestId);
+ EXPECT_CALL(*invocation.invocation_result_, CreateCookieStoreValue(_, _)).
+ WillRepeatedly(Invoke(invocation.invocation_result_.get(),
+ &MockIterativeCookieApiResult::CallCreateCookieStoreValue));
+ EXPECT_CALL(*invocation.invocation_result_,
+ RegisterCookieStore(_)).
+ WillRepeatedly(Return(S_OK));
+ EXPECT_CALL(invocation.invocation_result_->mock_api_dispatcher_,
+ GetExecutor(_, _, NotNull())).
+ WillRepeatedly(SetArgumentPointee<2>(static_cast<void*>(NULL)));
+ EXPECT_CALL(*invocation.invocation_result_, CallRealPostResult()).Times(0);
+ EXPECT_CALL(*invocation.invocation_result_, CallRealPostError(_)).Times(1);
+ invocation.Execute(args, kRequestId);
+ EXPECT_EQ(MockIterativeCookieApiResult::success_counter(), 0);
+ EXPECT_EQ(MockIterativeCookieApiResult::error_counter(), all_windows.size());
+
+ testing::MockWindowExecutor* mock_window_executor;
+ CComPtr<ICeeeWindowExecutor> mock_window_executor_keeper;
+ EXPECT_HRESULT_SUCCEEDED(testing::MockWindowExecutor::CreateInitialized(
+ &mock_window_executor, &mock_window_executor_keeper));
+
+ // Now test the case of multiple open windows/processes. Each invocation is
+ // successful and a result is produced.
+ MockIterativeCookieApiResult::ResetCounters();
+ invocation.AllocateNewResult(kRequestId);
+
+ EXPECT_CALL(invocation.invocation_result_->mock_api_dispatcher_,
+ GetExecutor(_, _, NotNull())).
+ WillRepeatedly(DoAll(SetArgumentPointee<2>(
+ mock_window_executor_keeper.p),
+ AddRef(mock_window_executor_keeper.p)));
+
+ EXPECT_CALL(*mock_window_executor, GetTabs(NotNull())).
+ WillRepeatedly(Invoke(MockIterativeCookieApiResult::MockGetTabs));
+ EXPECT_CALL(*invocation.invocation_result_, CreateCookieStoreValue(_, _)).
+ WillRepeatedly(Invoke(invocation.invocation_result_.get(),
+ &MockIterativeCookieApiResult::CallCreateCookieStoreValue));
+ EXPECT_CALL(*invocation.invocation_result_,
+ RegisterCookieStore(_)).
+ WillRepeatedly(Return(S_OK));
+ EXPECT_CALL(*invocation.invocation_result_, CallRealPostResult()).Times(1);
+ EXPECT_CALL(*invocation.invocation_result_, CallRealPostError(_)).Times(0);
+ invocation.Execute(args, kRequestId);
+ EXPECT_EQ(MockIterativeCookieApiResult::success_counter(),
+ all_windows.size());
+ EXPECT_EQ(MockIterativeCookieApiResult::error_counter(), 0);
+
+ // Now test the case of multiple open windows/processes. One invocation
+ // fails, but everything else will be OK.
+ MockIterativeCookieApiResult::ResetCounters();
+ invocation.AllocateNewResult(kRequestId);
+
+ EXPECT_CALL(invocation.invocation_result_->mock_api_dispatcher_,
+ GetExecutor(_, _, NotNull())).
+ WillRepeatedly(DoAll(SetArgumentPointee<2>(
+ mock_window_executor_keeper.p),
+ AddRef(mock_window_executor_keeper.p)));
+ EXPECT_CALL(invocation.invocation_result_->mock_api_dispatcher_,
+ GetExecutor(kAllWindows[0], _, NotNull())).
+ WillOnce(SetArgumentPointee<2>(static_cast<void*>(NULL)));
+
+ EXPECT_CALL(*mock_window_executor, GetTabs(NotNull())).
+ WillRepeatedly(Invoke(MockIterativeCookieApiResult::MockGetTabs));
+ EXPECT_CALL(*invocation.invocation_result_, CreateCookieStoreValue(_, _)).
+ WillRepeatedly(Invoke(invocation.invocation_result_.get(),
+ &MockIterativeCookieApiResult::CallCreateCookieStoreValue));
+ EXPECT_CALL(*invocation.invocation_result_,
+ RegisterCookieStore(_)).
+ WillRepeatedly(Return(S_OK));
+ EXPECT_CALL(*invocation.invocation_result_, CallRealPostResult()).Times(1);
+ EXPECT_CALL(*invocation.invocation_result_, CallRealPostError(_)).Times(0);
+ invocation.Execute(args, kRequestId);
+ EXPECT_EQ(MockIterativeCookieApiResult::success_counter(),
+ all_windows.size() - 1);
+ EXPECT_EQ(MockIterativeCookieApiResult::error_counter(), 1);
+}
+
+TEST_F(CookieApiTests, GetAllCookiesNotImplemented) {
+ testing::LogDisabler no_dchecks;
+ ListValue args;
+ StrictMock<MockApiInvocation<CookieApiResult,
+ MockCookieApiResult,
+ GetAllCookies> > invocation;
+
+ invocation.AllocateNewResult(kRequestId);
+ EXPECT_CALL(*invocation.invocation_result_,
+ PostError(StrEq("Not implemented."))).Times(1);
+ invocation.Execute(args, kRequestId);
+}
+
+TEST_F(CookieApiTests, SetCookieNotImplemented) {
+ testing::LogDisabler no_dchecks;
+ ListValue args;
+ StrictMock<MockApiInvocation<CookieApiResult,
+ MockCookieApiResult,
+ SetCookie> > invocation;
+
+ invocation.AllocateNewResult(kRequestId);
+ EXPECT_CALL(*invocation.invocation_result_,
+ PostError(StrEq("Not implemented."))).Times(1);
+ invocation.Execute(args, kRequestId);
+}
+
+TEST_F(CookieApiTests, RemoveCookieNotImplemented) {
+ testing::LogDisabler no_dchecks;
+ ListValue args;
+ StrictMock<MockApiInvocation<CookieApiResult,
+ MockCookieApiResult,
+ RemoveCookie> > invocation;
+ invocation.AllocateNewResult(kRequestId);
+ EXPECT_CALL(*invocation.invocation_result_,
+ PostError(StrEq("Not implemented."))).Times(1);
+ invocation.Execute(args, kRequestId);
+}
+
+TEST_F(CookieApiTests, CookieChangedEventHandler) {
+ testing::LogDisabler no_dchecks;
+ MockCookieChanged cookie_changed;
+ std::string converted_args;
+ // Empty args.
+ std::string input_args = "";
+ EXPECT_EQ(false,
+ cookie_changed.EventHandlerImpl(input_args, &converted_args));
+ // Invalid args.
+ input_args = "[false, {hello]";
+ EXPECT_EQ(false,
+ cookie_changed.EventHandlerImpl(input_args, &converted_args));
+ input_args = "[3]";
+ EXPECT_EQ(false,
+ cookie_changed.EventHandlerImpl(input_args, &converted_args));
+ // Valid args.
+ input_args = "[{\"removed\": false, \"cookie\": {\"storeId\": \"1\"}}]";
+
+ // Invalid store ID.
+ cookie_changed.AllocateApiResult();
+ EXPECT_CALL(*cookie_changed.api_result_,
+ GetWindowFromStoreId(StrEq("1"), true))
+ .WillOnce(Return(HWND(NULL)));
+ EXPECT_EQ(false,
+ cookie_changed.EventHandlerImpl(input_args, &converted_args));
+
+ // Cookie store access errors.
+ cookie_changed.AllocateApiResult();
+ EXPECT_CALL(*cookie_changed.api_result_,
+ GetWindowFromStoreId(StrEq("1"), true))
+ .WillOnce(Return(HWND(5)));
+ EXPECT_CALL(*cookie_changed.api_result_,
+ CookieStoreIsRegistered(HWND(5))).WillOnce(Return(E_FAIL));
+ EXPECT_EQ(false,
+ cookie_changed.EventHandlerImpl(input_args, &converted_args));
+
+ cookie_changed.AllocateApiResult();
+ EXPECT_CALL(*cookie_changed.api_result_,
+ GetWindowFromStoreId(StrEq("1"), true))
+ .WillOnce(Return(HWND(5)));
+ EXPECT_CALL(*cookie_changed.api_result_,
+ CookieStoreIsRegistered(HWND(5))).WillOnce(Return(S_FALSE));
+ EXPECT_CALL(*cookie_changed.api_result_,
+ RegisterCookieStore(HWND(5))).WillOnce(Return(E_FAIL));
+ EXPECT_EQ(false,
+ cookie_changed.EventHandlerImpl(input_args, &converted_args));
+
+ // Registered cookie store.
+ cookie_changed.AllocateApiResult();
+ EXPECT_CALL(*cookie_changed.api_result_,
+ GetWindowFromStoreId(StrEq("1"), true))
+ .WillOnce(Return(HWND(5)));
+ EXPECT_CALL(*cookie_changed.api_result_,
+ CookieStoreIsRegistered(HWND(5))).WillOnce(Return(S_OK));
+ EXPECT_EQ(true,
+ cookie_changed.EventHandlerImpl(input_args, &converted_args));
+
+ // Unregistered cookie store.
+ cookie_changed.AllocateApiResult();
+ EXPECT_CALL(*cookie_changed.api_result_,
+ GetWindowFromStoreId(StrEq("1"), true))
+ .WillOnce(Return(HWND(5)));
+ EXPECT_CALL(*cookie_changed.api_result_,
+ CookieStoreIsRegistered(HWND(5))).WillOnce(Return(S_FALSE));
+ EXPECT_CALL(*cookie_changed.api_result_,
+ RegisterCookieStore(HWND(5))).WillOnce(Return(S_OK));
+ EXPECT_EQ(true,
+ cookie_changed.EventHandlerImpl(input_args, &converted_args));
+}
+
+} // namespace
diff --git a/ceee/ie/broker/event_dispatching_docs.h b/ceee/ie/broker/event_dispatching_docs.h
new file mode 100644
index 0000000..254821b
--- /dev/null
+++ b/ceee/ie/broker/event_dispatching_docs.h
@@ -0,0 +1,262 @@
+// 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_BROKER_EVENT_DISPATCHING_DOCS_H_ // Mainly for lint
+#define CEEE_IE_BROKER_EVENT_DISPATCHING_DOCS_H_
+
+/** @page EventDispatchingDoc Detailed documentation of CEEE Event Dispatching.
+
+@section EventDispatchingIntro Introduction
+
+<a href="http://code.google.com/chrome/extensions/index.html">Chrome extensions
+</a> can register to be notified of specific events like
+<a href="http://code.google.com/chrome/extensions/windows.html#event-onCreated">
+window creation</a> or
+<a href="http://code.google.com/chrome/extensions/tabs.html#event-onUpdated">
+tab update</a>.
+
+So CEEE needs to let Chrome know when these events occure in IE. To do so, we
+unfortunately need to be hooked in a few different places. So in the same way
+that the Chrome Extensions API implementation was described in @ref
+ApiDispatcherDoc, this page describes all the notifications one by one and how
+we implement their broadcasting.
+
+Note that we use the EventFunnel class to properly package the arguments to be
+sent with the events, and then send them to the Broker via the
+ICeeeBroker::FireEvent method.
+
+@section WindowsEvents Windows Events
+
+There are three
+<a href="http://code.google.com/chrome/extensions/windows.html#events">Windows
+events</a> that can be sent to Chrome extensions,
+<a href="http://code.google.com/chrome/extensions/windows.html#event-onCreated">
+onCreated</a>, <a href=
+"http://code.google.com/chrome/extensions/windows.html#event-onFocusChanged">
+onFocusChanged</a>, and
+<a href="http://code.google.com/chrome/extensions/windows.html#event-onRemoved">
+onRemoved</a>.
+
+These notifications are for top level windows only, so we can get notifications
+from the OS by using the WH_SHELL hook (as described on
+<a href="http://msdn.microsoft.com/en-us/library/ms644991(VS.85).aspx">
+MSDN</a>). So we need to implement a hook function in the ceee_ie.dll so that
+it can be injected in the system process and be called for the following
+events:
+<table>
+<tr><td>HSHELL_WINDOWACTIVATED</td><td>Handle to the activated window.</td></tr>
+<tr><td>HSHELL_WINDOWCREATED</td><td>Handle to the created window.</td></tr>
+<tr><td>HSHELL_WINDOWDESTROYED</td><td>Handle to the destroyed window.</td></tr>
+</table>
+
+Then we must redirect those via Chrome Frame (more details in the @ref
+ChromeFrameDispatching section). In the case of Windows Events, we must relay
+the notifications to the Broker process via the ICeeeBrokerNotification
+(TBD) interface since they get handled by an injected ceee_ie.dll.
+
+@subsection onCreated
+
+The onCreated event needs to send (as arguments to the notification message)
+the same information about the Window as the one returned by the windows.create
+API (and as described in the @ref ApiDispatcherDoc and on the <a href=
+"http://code.google.com/chrome/extensions/windows.html#type-Window"> Chrome
+Extensions documentation</a>).
+
+@subsection onFocusChanged
+
+All that is needed here is to send the window identifier of the newly focused
+window to the listener.
+
+@subsection onRemoved
+
+All that is needed here is to send the window identifier of the removed window
+to the listener.
+
+@section TabsEvents Tabs Events
+
+There are seven
+<a href="http://code.google.com/chrome/extensions/tabs.html#events">Tabs
+events</a> that can be sent to Chrome extensions,
+<a href="http://code.google.com/chrome/extensions/tabs.html#event-onAttached">
+onAttached</a>,
+<a href="http://code.google.com/chrome/extensions/tabs.html#event-onCreated">
+onCreated</a>,
+<a href="http://code.google.com/chrome/extensions/tabs.html#event-onDetached">
+onDetached</a>,
+<a href="http://code.google.com/chrome/extensions/tabs.html#event-onMoved">
+onMoved</a>,
+<a href="http://code.google.com/chrome/extensions/tabs.html#event-onRemoved">
+onRemoved</a>, <a href=
+"http://code.google.com/chrome/extensions/tabs.html#event-onSelectionChanged"
+>onSelectionChanged</a>, and
+<a href="http://code.google.com/chrome/extensions/tabs.html#event-onUpdated">
+onUpdated</a>.
+
+Since IE can't move tabs between windows (yet), CEEE won't fire the
+attached/detached notifications. The tab created event can be fired when an
+instance of the CEEE BHO is created and associated to a tab. The move, remove
+and selected tab events will most likely need to be caught from IE's
+TabWindowManager somehow (TBD). And finally, the update tab event can be
+fired when the instance of the BHO attached to the given tab receives a
+notification that the tab content was updated (via a series of notification from
+the <a href="http://msdn.microsoft.com/en-us/library/aa752085(VS.85).aspx">
+WebBrowser</a> or the <a href=
+"http://msdn.microsoft.com/en-us/library/aa752574(VS.85).aspx">document</a>).
+
+@note As a side note, here's an email from Siggi about tricks for this:
+
+@note <em>2009/10/16 Sigurour Asgeirsson &lt;siggi@google.com&gt;</em>
+
+@note I think we should be able to derive all the window events from tab
+activity, if we e.g. have the BHO register each tab with its HWND/ID, then we
+can derive the association to parent window from there, which means we have
+created/destroyed on first registration and last unregistration for a frame
+window.
+
+@note I'm pretty sure we can derive the focus event from events fired on the
+top-level browser associated with a BHO as well.
+
+@subsection onAttached
+
+Not implemented on IE.
+
+@subsection onDetached
+
+Not implemented on IE.
+
+@subsection onMoved
+
+TBD: We need to find a way to get the tab move notification from the
+TabWindowManager... somehow... (e.g., patch the move method, but we would
+rather find a notification coming from there instead).
+
+We could also see if we can get win32 notifications of focus or window
+visibility or things of the like.
+
+Once we do, the arguments that must be sent with this notification are the
+identifier of the tab, and a dictionary of moveInfo that contains the
+"windowId", the "fromIndex" and the "toIndex" fields, which are all integers.
+
+@subsection onRemoved
+
+TBD: We need to find a way to get the tab removed notification from the
+TabWindowManager... somehow...
+
+We could also use the destruction of the BHO as a hint that the tab is being
+removed.
+
+Once we do, the argument that must be sent with this notification is just the
+identifier of the tab.
+
+@subsection onSelectionChanged
+
+TBD: We need to find a way to get the tab selection changed notification from
+the TabWindowManager... somehow...
+
+We could also see if we can get win32 notifications of focus or window
+visibility or things of the like.
+
+Once we do, the arguments that must be sent with this notification are the
+identifier of the tab that just got selected, and a dictionary of selectInfo
+that only contains the "windowId" field.
+
+@subsection onCreated
+
+When a new BHO instance is created and attached to a tab, it can send this
+notification directly to the Chrome Frame it is attached to. We will most likely
+change this direct call to Chrome Frame once we implement the asynchronous
+callback to Chrome Extension for the tabs.create API implementation for IE. At
+that point, we should use the same ICeeeBrokerNotification (TBD) interface
+as we plan on using for the @link WindowsEvents Windows Events @endlink.
+
+The onCreated event needs to send to the extensions registered as listeners,
+the same information about the Tab as the one returned by the tabs.create
+API (and as described in the @ref ApiDispatcherDoc and on the <a href=
+"http://code.google.com/chrome/extensions/tabs.html#type-Tab"> Chrome
+Extensions documentation</a>).
+
+@subsection onUpdated
+
+When a new BHO instance receives a <a href=
+"http://msdn.microsoft.com/en-us/library/aa768334(VS.85).aspx">NavigateComplete2
+</a> notification from the WebBrowser, it must fire the onUpdated notification
+for the tab it is attached to. It must also fire it for the ready state change
+notifications it would get from the document. There's an edge case for when
+IE refreshes the page, which can be caught when the frame ready state drops from
+complete to loading, so we must also handle this case to send an onUpdated
+notification for the tab.
+
+It gets trickier when the page has embedded frames. These independent frames
+will all have their own ready state, and changing the ready state (by
+refreshing) a frame in the middle of the hierarchy, will change its own ready
+state from complete, to loading, and then to interactive, and then the child
+frames will go from uninitialized, to loading and then to interactive. The
+innermost child will also go all the way to complete, and then its immediate
+parent will get from interactive to complete, and then the next ancestor, all
+the way to where the refresh started, but not further (so the outermost frame's
+ready state will not change unless it is the one that triggered the refresh).
+
+From the Chrome Extension's point of view, there should be only two events sent,
+the initial one that goes from complete to loading, and the final one to go
+from loading to complete. Since this doesn't necessary happen on the topmost
+frame, we must listen to all the frames ready state changes, but we must be
+able to compute the state of the whole page, so that we only send one
+notification that would go from complete to loading at the beginning of the
+refresh, and then only one that will go from loading to complete, once the
+page is completely refreshed.
+
+To do so, we need to propagate the state changes upward in the hierarchy and
+parse the whole tree to confirm the page state, which is loading unless all
+the frames are complete, in which case it will be complete. To minimize the
+number of times we need to parse the hierarchy, only the top frame needs to
+do the parsing. Also, frames don't need to propagate up their children's state
+unless they are, themselves (the parent) in the complete state. If they are not,
+then their own parent (or themselves if its the root) know that the overall
+state isn't complete.
+
+When we fire the onUpdated notification we must provide the identifier of the
+tab and dictionary that contains the status field, which can be either "loading"
+or "complete", as well as an optional URL field that the tab is set to show
+(it is only provided if it changed).
+
+@section BookmarksEvents Bookmarks Events
+
+TBD
+
+http://code.google.com/chrome/extensions/bookmarks.html#events
+
+@section ExtensionsEvents Extensions Events
+
+This is already available... more details TBD...
+
+http://code.google.com/chrome/extensions/extension.html#events
+
+@section ChromeFrameDispatching Dispatching to Chrome via the Chrome Frame
+
+To simplify our access to (and reception of notifications from) Chrome Frame,
+we have a ChromeFrameHost class that implements all we need. It takes care of
+notification registration, and have virtual methods for all of them, and it
+also has a PostMessage method to allow calls to be dispatched to Chrome via
+the automation layer and Chrome Frame.
+
+Events are such notifications that need to be sent, so the places where we
+intercept IE notifications can use a ChromeFrameHost object, package the
+event information in a JSON encoded string and pass it to the
+ChromeFrameHost::PostMessage method. The JSON data must be a list with two
+entries, the first one is the event name (e.g., "windows.onRemoved" or
+"tabs.onUpdated") and the other one is the arguments for this event (e.g.,
+a single int representing the id of the window to remove or for the
+tabs.onUpdated event, a list of two values, the first one being an int for the
+tab identifier, and the other one is a dictionary with a mandatory field for the
+"status", which value can be "loading" or "complete", and an optional one for
+the "url" which is a string reprensenting the URL to which the tab is being
+navigated to).
+
+The target of the message must be kAutomationBrowserEventRequestTarget (which is
+"__priv_evtreq") and the origin, must, of course, be kAutomationOrigin (which is
+"__priv_xtapi").
+
+**/
+
+#endif // CEEE_IE_BROKER_EVENT_DISPATCHING_DOCS_H_
diff --git a/ceee/ie/broker/executors_manager.cc b/ceee/ie/broker/executors_manager.cc
new file mode 100644
index 0000000..5d87467
--- /dev/null
+++ b/ceee/ie/broker/executors_manager.cc
@@ -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.
+//
+// ExecutorsManager implementation.
+
+#include "ceee/ie/broker/executors_manager.h"
+
+#include "base/logging.h"
+#include "ceee/ie/broker/broker_module_util.h"
+#include "ceee/common/com_utils.h"
+
+namespace {
+
+// The timeout we set before accepting a failure when we wait for events.
+const DWORD kTimeOut = 20000;
+
+// Utility class to ensure the setting of an event before we exit a method.
+class AutoSetEvent {
+ public:
+ explicit AutoSetEvent(HANDLE event_handle) : event_handle_(event_handle) {}
+ ~AutoSetEvent() { ::SetEvent(event_handle_); }
+ private:
+ HANDLE event_handle_;
+};
+
+// A cached handle to our current process which we use when we call
+// DuplicateHandle. GetCurrentProcess returns a pseudo HANDLE that doesn't
+// need to be closed.
+const HANDLE kProcessHandle = ::GetCurrentProcess();
+
+} // namespace
+
+
+const size_t ExecutorsManager::kTerminationHandleIndexOffset = 0;
+const size_t ExecutorsManager::kUpdateHandleIndexOffset = 1;
+const size_t ExecutorsManager::kLastHandleIndexOffset =
+ kUpdateHandleIndexOffset;
+const size_t ExecutorsManager::kExtraHandles = kLastHandleIndexOffset + 1;
+
+ExecutorsManager::ExecutorsManager(bool no_thread)
+ : update_threads_list_gate_(::CreateEvent(NULL, FALSE, FALSE, NULL)),
+ // Termination is manual reset. When we're terminated... We're terminated!
+ termination_gate_(::CreateEvent(NULL, TRUE, FALSE, NULL)) {
+ DCHECK(update_threads_list_gate_ != NULL);
+ DCHECK(termination_gate_ != NULL);
+
+ if (!no_thread) {
+ ThreadStartData thread_start_data;
+ thread_start_data.me = this;
+ // Again, manual reset, because when we are started... WE ARE STARTED!!! :-)
+ thread_start_data.thread_started_gate.Attach(::CreateEvent(NULL, TRUE,
+ FALSE, NULL));
+ DCHECK(thread_start_data.thread_started_gate != NULL);
+
+ // Since we provide the this pointer to the thread, we must make sure to
+ // keep ALL INITIALIZATION CODE ABOVE THIS LINE!!!
+ thread_.Attach(::CreateThread(NULL, 0, ThreadProc, &thread_start_data,
+ 0, 0));
+ DCHECK(thread_ != NULL);
+
+ // Make sure the thread is ready before continuing
+ DWORD result = WaitForSingleObject(thread_start_data.thread_started_gate,
+ kTimeOut);
+ DCHECK(result == WAIT_OBJECT_0);
+ }
+}
+
+HRESULT ExecutorsManager::RegisterTabExecutor(ThreadId thread_id,
+ IUnknown* executor) {
+ // We will need to know outside of the lock if the map was empty or not.
+ // This way we can add a ref to the module for the existence of the map.
+ bool map_was_empty = false;
+ {
+ AutoLock lock(lock_);
+ map_was_empty = executors_.empty();
+ if (!map_was_empty && executors_.find(thread_id) != executors_.end()) {
+ return S_OK;
+ }
+ CHandle thread_handle(::OpenThread(SYNCHRONIZE, FALSE, thread_id));
+ if (thread_handle == NULL) {
+ DCHECK(false) << "Can't Open thread: " << thread_id;
+ return E_UNEXPECTED;
+ }
+ ExecutorInfo& new_executor_info = executors_[thread_id];
+ new_executor_info.executor = executor;
+ new_executor_info.thread_handle = thread_handle;
+ } // End of lock.
+
+ if (map_was_empty) {
+ // We go from empty to not empty,
+ // so lock the module to make sure we stay alive.
+ ceee_module_util::LockModule();
+ }
+ return S_OK;
+}
+
+HRESULT ExecutorsManager::RegisterWindowExecutor(ThreadId thread_id,
+ IUnknown* executor) {
+ // We need to fetch the event handle associated to this thread ID from
+ // our map in a thread safe way...
+ CHandle executor_registration_gate;
+ {
+ AutoLock lock(lock_);
+ if (executors_.find(thread_id) != executors_.end()) {
+ DCHECK(false) << "Unexpected registered thread_id: " << thread_id;
+ return E_UNEXPECTED;
+ }
+
+ Tid2Event::iterator iter = pending_registrations_.find(thread_id);
+ if (iter == pending_registrations_.end() || executor == NULL) {
+ DCHECK(false) << "Invalid thread_id: " << thread_id <<
+ ", or NULL executor.";
+ return E_INVALIDARG;
+ }
+ // Make sure we use a duplicate handle so that we don't get caught setting
+ // a dead handle when we exit, in case the other thread wakes up because
+ // of a (unlikely) double registration.
+ BOOL success = ::DuplicateHandle(
+ kProcessHandle, iter->second.m_h, kProcessHandle,
+ &executor_registration_gate.m_h, 0, FALSE, DUPLICATE_SAME_ACCESS);
+ DCHECK(success) << com::LogWe();
+ } // End of lock.
+
+ // We must make sure to wake up the thread(s) that might be waiting on us.
+ // But only when we are done.
+ AutoSetEvent auto_set_event(executor_registration_gate);
+
+ // Try to get a handle to this thread right away so that we can do the rest
+ // atomically. We need it to wake us up when it dies.
+ CHandle thread_handle(::OpenThread(SYNCHRONIZE, FALSE, thread_id));
+ if (thread_handle == NULL) {
+ DCHECK(false) << "Can't Open thread: " << thread_id;
+ return S_FALSE;
+ }
+
+ // We will need to know outside of the lock if the map was empty or not.
+ // This way we can add a ref to the module for the existence of the map.
+ bool map_was_empty = false;
+ {
+ AutoLock lock(lock_);
+ map_was_empty = executors_.empty();
+ // We should not get here if we already have an executor for that thread.
+ DCHECK(executors_.find(thread_id) == executors_.end());
+ ExecutorInfo& new_executor_info = executors_[thread_id];
+ new_executor_info.executor = executor;
+ new_executor_info.thread_handle = thread_handle;
+ } // End of lock.
+
+ if (map_was_empty) {
+ // We go from empty to not empty,
+ // so lock the module to make sure we stay alive.
+ ceee_module_util::LockModule();
+ }
+
+ // Update the list of handles that our thread is waiting on.
+ BOOL success = ::SetEvent(update_threads_list_gate_);
+ DCHECK(success);
+ return S_OK;
+}
+
+HRESULT ExecutorsManager::GetExecutor(ThreadId thread_id, HWND window,
+ REFIID riid, void** executor) {
+ DCHECK(executor != NULL);
+ // We may need to wait for either a currently pending
+ // or own newly created registration of a new executor.
+ CHandle executor_registration_gate;
+
+ // We need to remember if we must create a new one or not.
+ // But we must create the executor creator outside of the lock.
+ bool create_executor = false;
+ {
+ AutoLock lock(lock_);
+ ExecutorsMap::iterator exec_iter = executors_.find(thread_id);
+ if (exec_iter != executors_.end()) {
+ // Found it... We're done... That was quick!!! :-)
+ DCHECK(exec_iter->second.executor != NULL);
+ return exec_iter->second.executor->QueryInterface(riid, executor);
+ }
+
+ // Check if we need to wait for a pending registration.
+ Tid2Event::iterator event_iter = pending_registrations_.find(thread_id);
+ if (event_iter == pending_registrations_.end()) {
+ // No pending registration, so we will need to create a new executor.
+ create_executor = true;
+
+ // Use the thread id as a cookie to only allow known threads to register.
+ // Also use it to map to a new event we will use to signal the end of this
+ // registration. We use a manual reset event so that more than one thread
+ // can wait for it, and once we're done... we're done... period! :-)
+ executor_registration_gate.Attach(::CreateEvent(NULL, TRUE, FALSE, NULL));
+ DCHECK(executor_registration_gate != NULL);
+ CHandle& new_registration_handle = pending_registrations_[thread_id];
+ // Make sure we use a duplicate handle so that we don't get caught waiting
+ // on a dead handle later, in case other threads wake up before we do and
+ // close the handle before we wake up.
+ BOOL success = ::DuplicateHandle(
+ kProcessHandle, executor_registration_gate, kProcessHandle,
+ &new_registration_handle.m_h, 0, FALSE, DUPLICATE_SAME_ACCESS);
+ DCHECK(success) << com::LogWe();
+ } else {
+ // Same comment as above...
+ BOOL success = ::DuplicateHandle(
+ kProcessHandle, event_iter->second.m_h, kProcessHandle,
+ &executor_registration_gate.m_h, 0, FALSE, DUPLICATE_SAME_ACCESS);
+ DCHECK(success) << com::LogWe();
+ }
+ } // End of lock.
+
+ CComPtr<ICeeeExecutorCreator> executor_creator;
+ if (create_executor) {
+ // We need to create an executor creator so that the code setting up
+ // a Windows Hook in the other process, runs from a DLL that can be
+ // injected in that other process... WE are running in an executable.
+ HRESULT hr = GetExecutorCreator(&executor_creator);
+ DCHECK(SUCCEEDED(hr) && executor_creator != NULL) <<
+ "CoCreating Executor Creator. " << com::LogHr(hr);
+ hr = executor_creator->CreateWindowExecutor(thread_id,
+ reinterpret_cast<CeeeWindowHandle>(window));
+ if (FAILED(hr)) {
+ // This could happen if the thread we want to hook to died prematurely.
+ AutoLock lock(lock_);
+ pending_registrations_.erase(thread_id);
+ return hr;
+ }
+ }
+
+ // Wait for the registration to complete.
+ DWORD result = WaitForSingleObject(executor_registration_gate, kTimeOut);
+ LOG_IF(INFO, result != WAIT_OBJECT_0) << "Registration problem? " <<
+ "Wait Result: " << com::LogWe(result);
+
+ // Let the executor creator know that we got the registration
+ // and it can tear down what was needed to trigger it.
+ if (executor_creator != NULL) {
+ HRESULT hr = executor_creator->Teardown(thread_id);
+ DCHECK(SUCCEEDED(hr)) << "Tearing down executor creator" << com::LogHr(hr);
+ }
+
+ // Do our own cleanup and return a reference thread safely...
+ AutoLock lock(lock_);
+ pending_registrations_.erase(thread_id);
+ ExecutorsMap::iterator iter = executors_.find(thread_id);
+ if (iter == executors_.end()) {
+ DCHECK(false) << "New executor registration failed.";
+ return E_UNEXPECTED;
+ }
+
+ DCHECK(iter->second.executor != NULL);
+ return iter->second.executor->QueryInterface(riid, executor);
+}
+
+HRESULT ExecutorsManager::RemoveExecutor(ThreadId thread_id) {
+ // Make sure to Release the executor outside the lock.
+ CComPtr<IUnknown> dead_executor;
+ bool map_is_empty = false;
+ {
+ AutoLock lock(lock_);
+ ExecutorsMap::iterator iter = executors_.find(thread_id);
+ if (iter == executors_.end()) {
+ return S_FALSE;
+ }
+
+ dead_executor.Attach(iter->second.executor.Detach());
+ executors_.erase(iter);
+ map_is_empty = executors_.empty();
+ } // End of lock.
+
+ if (map_is_empty) {
+ // We go from not empty to empty,
+ // so unlock the module it can leave in peace.
+ ceee_module_util::UnlockModule();
+ }
+ return S_OK;
+}
+
+HRESULT ExecutorsManager::Terminate() {
+ if (thread_ != NULL) {
+ // Ask our thread to quit and wait for it to be done.
+ DWORD result = ::SignalObjectAndWait(termination_gate_, thread_, kTimeOut,
+ FALSE);
+ DCHECK(result == WAIT_OBJECT_0);
+ thread_.Close();
+ }
+ if (!executors_.empty()) {
+ // TODO(mad@chromium.org): Can this happen???
+ NOTREACHED();
+ ceee_module_util::UnlockModule();
+ }
+
+ executors_.clear();
+ update_threads_list_gate_.Close();
+ termination_gate_.Close();
+
+ return S_OK;
+}
+
+void ExecutorsManager::SetTabIdForHandle(long tab_id, HWND handle) {
+ AutoLock lock(lock_);
+ DCHECK(tab_id_map_.end() == tab_id_map_.find(tab_id));
+ DCHECK(handle_map_.end() == handle_map_.find(handle));
+ if (handle == reinterpret_cast<HWND>(INVALID_HANDLE_VALUE) ||
+ tab_id == kInvalidChromeSessionId) {
+ NOTREACHED();
+ return;
+ }
+
+ tab_id_map_[tab_id] = handle;
+ handle_map_[handle] = tab_id;
+}
+
+void ExecutorsManager::DeleteTabHandle(HWND handle) {
+ AutoLock lock(lock_);
+ HandleMap::iterator handle_it = handle_map_.find(handle);
+ if(handle_map_.end() != handle_it) {
+ DCHECK(false);
+ return;
+ }
+
+ TabIdMap::iterator tab_id_it = tab_id_map_.find(handle_it->second);
+ if(tab_id_map_.end() != tab_id_it) {
+ DCHECK(false);
+ return;
+ }
+
+#ifdef DEBUG
+ tab_id_map_[handle_it->second] = reinterpret_cast<HWND>(INVALID_HANDLE_VALUE);
+ handle_map_[handle] = kInvalidChromeSessionId;
+#else
+ tab_id_map_.erase(handle_it->second);
+ handle_map_.erase(handle);
+#endif // DEBUG
+}
+
+HWND ExecutorsManager::GetTabHandleFromId(int tab_id) {
+ AutoLock lock(lock_);
+ TabIdMap::const_iterator it = tab_id_map_.find(tab_id);
+ DCHECK(it != tab_id_map_.end());
+
+ if (it == tab_id_map_.end())
+ return reinterpret_cast<HWND>(INVALID_HANDLE_VALUE);
+
+ // Deleted? I hope not.
+ DCHECK(it->second != reinterpret_cast<HWND>(INVALID_HANDLE_VALUE));
+ return it->second;
+}
+
+int ExecutorsManager::GetTabIdFromHandle(HWND tab_handle) {
+ AutoLock lock(lock_);
+ HandleMap::const_iterator it = handle_map_.find(tab_handle);
+ DCHECK(it != handle_map_.end());
+ if (it == handle_map_.end())
+ return kInvalidChromeSessionId;
+ DCHECK(it->second != kInvalidChromeSessionId); // Deleted? I hope not.
+ return it->second;
+}
+
+HRESULT ExecutorsManager::GetExecutorCreator(
+ ICeeeExecutorCreator** executor_creator) {
+ return ::CoCreateInstance(CLSID_CeeeExecutorCreator, NULL,
+ CLSCTX_INPROC_SERVER, IID_ICeeeExecutorCreator,
+ reinterpret_cast<void**>(executor_creator));
+}
+
+size_t ExecutorsManager::GetThreadHandles(
+ CHandle thread_handles[], ThreadId thread_ids[], size_t num_threads) {
+ AutoLock lock(lock_);
+ ExecutorsMap::iterator iter = executors_.begin();
+ size_t index = 0;
+ for (; index < num_threads && iter != executors_.end(); ++index, ++iter) {
+ DCHECK(thread_handles[index].m_h == NULL);
+ // We need to duplicate the handle to make sure the caller will not wait
+ // on a closed handle.
+ BOOL success = ::DuplicateHandle(
+ kProcessHandle, iter->second.thread_handle, kProcessHandle,
+ &thread_handles[index].m_h, 0, FALSE, DUPLICATE_SAME_ACCESS);
+ DCHECK(success) << com::LogWe();
+ thread_ids[index] = iter->first;
+ }
+
+ return index;
+}
+
+DWORD ExecutorsManager::WaitForSingleObject(HANDLE wait_handle, DWORD timeout) {
+ return ::WaitForSingleObject(wait_handle, timeout);
+}
+
+DWORD ExecutorsManager::WaitForMultipleObjects(DWORD num_handles,
+ const HANDLE* wait_handles, BOOL wait_all, DWORD timeout) {
+ return ::WaitForMultipleObjects(num_handles, wait_handles, wait_all, timeout);
+}
+
+
+DWORD ExecutorsManager::ThreadProc(LPVOID parameter) {
+ // We must make sure to join the multi thread apartment so that the executors
+ // get released properly in the same apartment they were acquired from.
+ ::CoInitializeEx(NULL, COINIT_MULTITHREADED);
+
+ ThreadStartData* thread_start_data =
+ reinterpret_cast<ThreadStartData*>(parameter);
+ DCHECK(thread_start_data != NULL);
+ ExecutorsManager* me = thread_start_data->me;
+ DCHECK(me != NULL);
+
+ // Let our parent know that we are old enough now!
+ ::SetEvent(thread_start_data->thread_started_gate);
+ // Setting the event will destroy the thread start data living on the stack
+ // so make sure we don't use it anymore.
+ thread_start_data = NULL;
+
+ while (true) {
+ CHandle smart_handles[MAXIMUM_WAIT_OBJECTS];
+ HANDLE handles[MAXIMUM_WAIT_OBJECTS];
+ ThreadId thread_ids[MAXIMUM_WAIT_OBJECTS];
+ // Get as many handles as we can, leaving room for kExtraHandles.
+ size_t num_threads = me->GetThreadHandles(
+ smart_handles, thread_ids, MAXIMUM_WAIT_OBJECTS - kExtraHandles);
+ // Wait function needs an array of raw handles, not smart ones.
+ for (size_t index = 0; index < num_threads; ++index)
+ handles[index] = smart_handles[index];
+
+ // We also need to wait for our termination signal.
+ handles[num_threads + kTerminationHandleIndexOffset] =
+ me->termination_gate_;
+ // As well as a signal warning us to go fetch more thread handles.
+ handles[num_threads + kUpdateHandleIndexOffset] =
+ me->update_threads_list_gate_;
+
+ size_t num_handles = num_threads + kExtraHandles;
+ DWORD result = me->WaitForMultipleObjects(num_handles, handles, FALSE,
+ INFINITE);
+ if (result == WAIT_OBJECT_0 + num_threads +
+ kUpdateHandleIndexOffset) {
+ // We got a new thread added,
+ // simply let the loop turn to add it to our watch list.
+ } else if (result >= WAIT_OBJECT_0 &&
+ result < WAIT_OBJECT_0 + num_threads) {
+ // One of our threads have died, cleanup time.
+ me->RemoveExecutor(thread_ids[result - WAIT_OBJECT_0]);
+ } else if (result == WAIT_OBJECT_0 + num_threads +
+ kTerminationHandleIndexOffset) {
+ // we are being terminated, break the cycle.
+ break;
+ } else {
+ DCHECK(result == WAIT_FAILED);
+ LOG(ERROR) << "ExecutorsManager::ThreadProc " << com::LogWe();
+ break;
+ }
+ }
+ // Merci... Bonsoir...
+ ::CoUninitialize();
+ return 1;
+}
diff --git a/ceee/ie/broker/executors_manager.h b/ceee/ie/broker/executors_manager.h
new file mode 100644
index 0000000..3b9ac3b
--- /dev/null
+++ b/ceee/ie/broker/executors_manager.h
@@ -0,0 +1,210 @@
+// 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
+// ExecutorsManager implementation, an object to keep track of the
+// CeeeExecutor objects that were instantiated in destination threads.
+
+#ifndef CEEE_IE_BROKER_EXECUTORS_MANAGER_H_
+#define CEEE_IE_BROKER_EXECUTORS_MANAGER_H_
+
+#include <atlbase.h>
+#include <atlcom.h>
+#include <map>
+
+#include "base/lock.h"
+#include "base/singleton.h"
+
+#include "toolband.h" // NOLINT
+
+// This class is to be used as a single instance for the broker module to
+// hold on a map of executor objects per thread that won't go away when
+// the instance of the Broker object does.
+//
+// See the @ref ExecutorsManagerDoc page for more details.
+
+// Manages a map of destination threads to CeeeExecutor interfaces.
+class ExecutorsManager {
+ public:
+ // Identifiers for destination threads where to run executors.
+ typedef DWORD ThreadId;
+
+ // To avoid lint errors, even though we are only virtual for unittests.
+ virtual ~ExecutorsManager() {}
+
+ // Adds a new executor to the map associated to the given thread_id.
+ //
+ // @param thread_id The thread for which we want to register a new executor.
+ // @param executor The executor we want to register for the given thread_id.
+ // @return S_OK iff we didn't already have an executor, and we had a pending
+ // request to add one for that exact same thread.
+ virtual HRESULT RegisterWindowExecutor(ThreadId thread_id,
+ IUnknown* executor);
+ // TODO(mad@chromium.org): Implement the proper manual/secure registration.
+ //
+ // @param thread_id The thread for which we want to register a new executor.
+ // @param executor The executor we want to register for the given thread_id.
+ // @return S_OK iff we didn't already have an executor, and we had a pending
+ // request to add one for that exact same thread.
+ virtual HRESULT RegisterTabExecutor(ThreadId thread_id, IUnknown* executor);
+
+ // Gets the executor associated to the given thread_id. Gets if from the map
+ // if there was already one in there or create a new one otherwise.
+ //
+ // @param thread_id The thread for which we want the executor.
+ // @param window The window handle for which we want the executor.
+ // @param riid Which interface is to be returned in @p executor.
+ // @param executor Where to return the pointer to that executor.
+ // @return S_OK iff we found an existing or successfully created an executor.
+ virtual HRESULT GetExecutor(ThreadId thread_id, HWND window, REFIID riid,
+ void** executor);
+
+ // Removes an executor from our map.
+ //
+ // @param thread_id The thread for which we want to remove the executor.
+ // @return S_OK if we removed the executor or S_FALSE if it wasn't there.
+ virtual HRESULT RemoveExecutor(ThreadId thread_id);
+
+ // Terminates the usage of the map by freeing our resources.
+ virtual HRESULT Terminate();
+
+ // Return a tab handle associated with the id.
+ //
+ // @param tab_id The tab identifier.
+ // @return The corresponding HWND (or INVALID_HANDLE_VALUE if tab_id isn't
+ // found).
+ virtual HWND GetTabHandleFromId(int tab_id);
+
+ // Return a tab id associated with the HWND.
+ //
+ // @param tab_handle The tab HWND.
+ // @return The corresponding tab id (or 0 if tab_handle isn't found).
+ virtual int GetTabIdFromHandle(HWND tab_handle);
+
+ // Register the relation between a tab_id and a HWND.
+ virtual void SetTabIdForHandle(long tab_id, HWND tab_handle);
+
+ // Unregister the HWND and its corresponding tab_id.
+ virtual void DeleteTabHandle(HWND handle);
+
+ // Traits for Singleton<ExecutorsManager> so that we can pass an argument
+ // to the constructor.
+ struct SingletonTraits {
+ static ExecutorsManager* New() {
+ return new ExecutorsManager(false); // By default, we want a thread.
+ }
+ static void Delete(ExecutorsManager* x) {
+ delete x;
+ }
+ static const bool kRegisterAtExit = true;
+ };
+
+ protected:
+ // The data we pass to start our worker thread.
+ // THERE IS A COPY OF THIS CLASS IN THE UNITTEST WHICH YOU NEED TO UPDATE IF
+ // you change this one...
+ struct ThreadStartData {
+ ExecutorsManager* me;
+ CHandle thread_started_gate;
+ };
+
+ // A structures holding on the info about an executor and thread it runs in.
+ struct ExecutorInfo {
+ ExecutorInfo(IUnknown* new_executor = NULL, HANDLE handle = NULL)
+ : executor(new_executor), thread_handle(handle) {
+ }
+ ExecutorInfo(const ExecutorInfo& executor_info)
+ : executor(executor_info.executor),
+ thread_handle(executor_info.thread_handle) {
+ }
+ CComPtr<IUnknown> executor;
+ // mutable so that we can assign/Detach a const copy.
+ mutable CHandle thread_handle;
+ };
+
+ typedef std::map<ThreadId, ExecutorInfo> ExecutorsMap;
+ typedef std::map<ThreadId, CHandle> Tid2Event;
+ typedef std::map<int, HWND> TabIdMap;
+ typedef std::map<HWND, int> HandleMap;
+
+ // The index of the termination event in the array of handles we wait for.
+ static const size_t kTerminationHandleIndexOffset;
+
+ // The index of the update event in the array of handles we wait for.
+ static const size_t kUpdateHandleIndexOffset;
+
+ // The index of the last event in the array of handles we wait for.
+ static const size_t kLastHandleIndexOffset;
+
+ // The number of extra handles we used for the events described above.
+ static const size_t kExtraHandles;
+
+ // Protected constructor to ensure single instance and initialize some
+ // members. Set no_thread for testing...
+ explicit ExecutorsManager(bool no_thread);
+
+ // Creates an executor creator in a virtual method so we can override it in
+ // Our unit test code.
+ //
+ // @param executor_creator Where to return the executor creator.
+ virtual HRESULT GetExecutorCreator(
+ ICeeeExecutorCreator** executor_creator);
+
+ // Returns a list of HANDLEs of threads for which we have an executor.
+ //
+ // @param thread_handles Where to return at most @p num_threads handles.
+ // @param thread_ids Where to return at most @p num_threads ThreadIds.
+ // @param num_threads How many handles can fit in @p thread_handles.
+ // @return How many handles have been added in @p thread_handles and the same
+ // ThreadIds have been added to @p thread_ids.
+ virtual size_t GetThreadHandles(CHandle thread_handles[],
+ ThreadId thread_ids[], size_t num_threads);
+
+ // A few seams so that we don't have to mock the kernel functions.
+ virtual DWORD WaitForSingleObject(HANDLE wait_handle, DWORD timeout);
+ virtual DWORD WaitForMultipleObjects(DWORD num_handles,
+ const HANDLE* wait_handles, BOOL wait_all, DWORD timeout);
+
+ // The thread procedure that we use to clean up dead threads from the map.
+ //
+ // @param thread_data A small structure containing this and an event to
+ // signal when the thread has finished initializing itself.
+ static DWORD WINAPI ThreadProc(LPVOID thread_data);
+
+ // The map of executor and their thread handle keyed by thread identifiers.
+ // Thread protected by lock_.
+ ExecutorsMap executors_;
+
+ // We remember the thread identifiers for which we are pending a registration
+ // so that we make sure that we only accept registration that we initiate.
+ // Also, for each pending registration we must wait on a different event
+ // per thread_id that we are waiting for the registration of.
+ // Thread protected by ExecutorsManager::lock_.
+ Tid2Event pending_registrations_;
+
+ // The mapping between a tab_id and the HWND of the window holding the BHO.
+ // In DEBUG, this mapping will grow over time since we don't remove it on
+ // DeleteTabHandle. This is useful for debugging as we know if a mapping has
+ // been deleted and is invalidly used.
+ // Thread protected by ExecutorsManager::lock_.
+ TabIdMap tab_id_map_;
+ HandleMap handle_map_;
+
+ // The handle to the thread running ThreadProc.
+ CHandle thread_;
+
+ // Used to signal the thread to reload the list of thread handles.
+ CHandle update_threads_list_gate_;
+
+ // Used to signal the thread to terminate.
+ CHandle termination_gate_;
+
+ // To protect the access to the maps (ExecutorsManager::executors_ &
+ // ExecutorsManager::pending_registrations_ & tab_id_map_/handle_map_).
+ Lock lock_;
+
+ DISALLOW_EVIL_CONSTRUCTORS(ExecutorsManager);
+};
+
+#endif // CEEE_IE_BROKER_EXECUTORS_MANAGER_H_
diff --git a/ceee/ie/broker/executors_manager_docs.h b/ceee/ie/broker/executors_manager_docs.h
new file mode 100644
index 0000000..3c607a4
--- /dev/null
+++ b/ceee/ie/broker/executors_manager_docs.h
@@ -0,0 +1,208 @@
+// 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_BROKER_EXECUTORS_MANAGER_DOCS_H_ // Mainly for lint
+#define CEEE_IE_BROKER_EXECUTORS_MANAGER_DOCS_H_
+
+/* @page ExecutorsManagerDoc Detailed documentation of the ExecutorsManager.
+
+@section ExecutorsManagerIntro Introduction
+
+The Executors Manager implemented by the ExecutorsManager class,
+is used by the CeeeBroker object to instantiate and hold on to
+Marshaled proxies of CeeeExecutor COM objects running in other processes.
+
+The complexity lies in the fact that the Broker could send concurrent requests
+to access Executor proxies and the creation of new proxies can't be done
+synchronously.
+
+So the Executors Manager must block on a request for a new Executor, and all
+subsequent requests for the same executor until the asynchronous creation of
+the Executor is complete. Once the Executor is completely registered in the
+Executors Manager, it can be returned right away when requested (though a wait
+may need to be done on a lock to access the map of Executors).
+
+Executors are created to execute code in a given thread (called the
+destination thread from now on), so we map the Executor to their associated
+destination thread identifier and reuse existing Executors for subsequent
+requests to execute in the same destination thread. We also keep an open
+handle to the destination thread so that we can wait to it in a separate
+Executors Manager @ref ThreadProc "worker thread" and wake up when the
+destination thread dies, so that we can clean our map.
+
+@section PublicMethods Public methods
+
+The Executors Manager has has 3 public entry points, @ref GetExecutor,
+@ref AddExecutor and @ref RemoveExecutor.
+
+@subsection GetExecutor
+
+The @link ExecutorsManager::GetExecutor GetExecutor @endlink method returns a
+reference to a CeeeExecutor COM object (simply called Executor from now on)
+for a given destination thread. It first checks for an existing Executor in
+the @link ExecutorsManager::executors_ Executors map@endlink, and if it finds
+one, it simply returns it (though it must @link ExecutorsManager::lock_ lock
+@endlink the access to the Executors map).
+
+If the @link ExecutorsManager::executors_ Executors map@endlink doesn't
+already have an Executor for the requested
+destination thread, we must first check if we already have a @link
+ExecutorsManager::pending_registrations_ pending registration @endlink for that
+same destination thread.
+
+As mentioned @ref ExecutorsManagerIntro "above", registration requests can be
+pending, since the creation of new Executors can not be done synchronously.
+
+If there are no existing Executor and no pending registration to create one,
+we can create a new pending registration (all of this, properly locked so that
+we make sure to only start one pending registration for a given destination
+thread).
+
+The creation of new Executors is done via the usage of the @link
+CeeeExecutorCreator Executor Creator @endlink COM object that is exposed by
+a DLL so that it can inject a @link CeeeExecutorCreator::hook_ hook
+@endlink in the destination thread to create the Executor. Once the hook is
+injected, we can only post to the hook (since sending a message creates
+a user-synchronous operation that can't get out of the process, so we won't be
+able to register the newly created Executor in the Broker).
+
+Once the Executor Creator posted a message to the hook to instantiate a new
+Executor and get it registered in the Broker, the Executors Manager must wait
+for the registration to complete. Since the Executors Manager is called by the
+Broker, which is instantiated in a multi-threaded apartment, the registration
+will happen in a free thread while the thread that made the call to GetExecutor
+is blocked, waiting for the pending registration to complete.
+
+At that point, there may be more than one thread calling GetExecutor for the
+same destination thread, so they must all wait on the registration complete
+event. Also, since there can be more than one destination thread with pending
+registrations for a new Executor, there must be multiple events used to signal
+the completion of the registration, one for each destination thread that has a
+pending request to create a new executor. So the Executors Manager keeps a
+@link ExecutorsManager::pending_registrations_ map of destination thread
+identifier to registration completion event@endlink.
+
+Once the registration of a new Executor is complete (via a call to @ref
+RegisterTabExecutor), the event for the destination thread for which the
+Executor is being registered is signaled so that any thread waiting for it can
+wake up, get the new Executor from the map, and return it to their caller.
+
+@subsection RegisterExecutor
+
+The @link ExecutorsManager::RegisterExecutor RegisterExecutor @endlink method is
+called by the Broker when it receives a request to register a new Executor for a
+given destination thread. So the Executors Manager must first confirm that we
+didn't already have an Executor for that destination thread, and also confirm
+that we actually have a pending request for that destination thread.
+
+@note <b>TODO(mad@chromium.org)</b>: We need to change this so that we
+allow the BHO and maybe other modules in the future to preemptively
+register Executors without a pending request.
+
+Once this is confirmed, the new Executor is added to the map (again, in a
+thread safe way, of course), and the event of the pending request to register
+an executor for that destination thread is signaled. We also need to wake up
+the @ref ThreadProc "worker thread", waiting on destination thread handles,
+(using the @ref update_threads_list_gate_ event) so that the new one can be
+added to the list to wait on.
+
+@subsection RemoveExecutor
+
+If for some reason, an executor must remove itself from the Executors Manager's
+map, it can do so by calling the @link
+ICeeeBrokerRegistrar::UnregisterExecutor UnregisterExecutor @endlink method
+on the Broker ICeeeBrokerRegistrar interface which delegates to the
+Executors Manager's @link ExecutorsManager::RemoveExecutor RemoveExecutor
+@endlink method.
+
+This same method is also called from the Executors Manager's @ref ThreadProc
+"worker thread" waiting for the death of the destination thread for which we
+have an executor in the map. When the thread dies, we remove the associated
+executor from the map.
+
+@subsection Terminate
+
+The @link ExecutorsManager::Terminate Terminate @endlink method is to be called
+when we are done with the Executors Manager, and it can clean up and release
+all its resources (including stopping the @ref ThreadProc "worker thread" used
+to wait for the destination threads to die, by setting the @ref
+termination_gate_ event).
+
+@section PrivateMethods Private methods
+
+We use a few private methods to help with the implementation of the public
+methods described @ref PublicMethods "above".
+
+@subsection GetThreadHandles
+
+@link ExecutorsManager::GetThreadHandles GetThreadHandles @endlink parses
+through our @link ExecutorsManager::executors_ Executors map @endlink to return
+the list of handles that the @ref ThreadProc "worker thread" has to wait for.
+
+The caller must provide two pre-allocated arrays, one for thread handles and the
+other one for thread identifiers. So we may not be able to return info for
+all the threads if there isn't enough room. This is OK since the only caller is
+the worker thread procedure (described @ref ThreadProc "below") and it can't
+wait for more than a specific number of handles anyway. The arrays must be kept
+in sync, so that index i in one of them refers to the same thread as index i in
+the other.
+
+@subsection ThreadProc
+
+The @link ExecutorsManager::ThreadProc ThreadProc @endlink is the worker thread
+procedure that is used to get the list of destination threads for
+which we have an executor in the @link ExecutorsManager::executors_ Executors
+map@endlink, and then wait on them to know when they die.
+
+We also need to wait on two events, @ref termination_gate_ for the termination
+of the worker thread (set in the Terminate public method described @ref
+Terminate "above") and @ref update_threads_list_gate_ warning us that there are
+new threads that we must wait on (set in the RegisterExecutor public method
+described @ref RegisterExecutor "above").
+
+@section PrivateDataMembers Private data members
+
+@subsection executors_
+
+@link ExecutorsManager::executors_ executors_ @endlink is a map from a
+destination thread identifier to a couple made of an Executor interface and a
+thread handle. We need to keep an opened thread handle so that we can wait on
+it until it dies.
+
+@subsection pending_registrations_
+
+@link ExecutorsManager::pending_registrations_ pending_registrations_ @endlink
+is another map of destination thread identifier, but this one holds on to
+synchronization events that we will set once the registration of an executor
+for the given destination thread is complete. Since there may be more than one
+thread waiting on this signal, and we use the signal only once, we create it as
+a manual reset event.
+
+@subsection thread_
+
+Of course, we need to keep a @link ExecutorsManager::thread_ handle @endlink on
+our worker thread.
+
+@subsection update_threads_list_gate_
+
+This @link ExecutorsManager::update_threads_list_gate_ event @endlink is used
+to wake the worker thread so that it refreshes its list of thread handles that
+it waits on. It is set from the RegisterExecutor public method described
+@ref RegisterExecutor "above".
+
+@subsection termination_gate_
+
+This other @link ExecutorsManager::termination_gate_ event @endlink is set from
+the Terminate public method described @ref Terminate "above" to notify the
+worker thread to terminate itself.
+
+@subsection lock_
+
+Since the Executors Manager can be called from many different threads, it must
+protect the access to its data (and atomize certain operations) with this @link
+ExecutorsManager::lock_ lock @endlink.
+
+*/
+
+#endif // CEEE_IE_BROKER_EXECUTORS_MANAGER_DOCS_H_
diff --git a/ceee/ie/broker/executors_manager_unittest.cc b/ceee/ie/broker/executors_manager_unittest.cc
new file mode 100644
index 0000000..47c6275
--- /dev/null
+++ b/ceee/ie/broker/executors_manager_unittest.cc
@@ -0,0 +1,664 @@
+// 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 executors manager.
+
+#include <vector>
+
+#include "base/logging.h"
+#include "ceee/ie/broker/executors_manager.h"
+#include "ceee/testing/utils/mock_win32.h"
+#include "ceee/testing/utils/test_utils.h"
+#include "ceee/testing/utils/nt_internals.h"
+#include "testing/gmock/include/gmock/gmock.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+namespace {
+
+using testing::_;
+using testing::DoAll;
+using testing::InvokeWithoutArgs;
+using testing::Return;
+using testing::SetArgumentPointee;
+
+// We mock the IUnknown interface to make sure we properly AddRef and
+// Release the executors in the manager's map.
+// TODO(mad@chromium.org): replace this with the MockExecutorIUnknown
+// in mock_broker_and_friends.h.
+class MockExecutor : public IUnknown {
+ public:
+ MockExecutor() : ref_count_(0) {}
+ STDMETHOD_(ULONG, AddRef)() { return ++ref_count_; }
+ STDMETHOD_(ULONG, Release)() {
+ EXPECT_GT(ref_count_, 0UL);
+ return --ref_count_;
+ }
+ ULONG ref_count() const { return ref_count_; }
+ MOCK_METHOD2_WITH_CALLTYPE(__stdcall, QueryInterface,
+ HRESULT(REFIID, void**));
+ private:
+ ULONG ref_count_;
+};
+
+// We also need to mock the creator or executors for 1) making sure it is
+// called properly, and 2) for it to return our mock executor.
+class MockExecutorCreator: public ICeeeExecutorCreator {
+ public:
+ // No need to mock AddRef, it is not called.
+ STDMETHOD_(ULONG, AddRef)() { return 1; }
+ MOCK_METHOD0_WITH_CALLTYPE(__stdcall, Release, ULONG());
+ STDMETHOD (QueryInterface)(REFIID, LPVOID*) { return S_OK; }
+
+ MOCK_METHOD2_WITH_CALLTYPE(__stdcall, CreateWindowExecutor,
+ HRESULT(long, CeeeWindowHandle));
+ MOCK_METHOD1_WITH_CALLTYPE(__stdcall, Teardown, HRESULT(long));
+};
+
+// We need to override some virtual functions of the executors manager and
+// also provide public access to some protected methods and data.
+class TestingExecutorsManager : public ExecutorsManager {
+ public:
+ TestingExecutorsManager()
+ : ExecutorsManager(true), // no thread.
+ current_executor_(NULL),
+ current_handle_(NULL),
+ current_thread_id_(kThreadId) {
+ }
+
+ ~TestingExecutorsManager() {
+ nt_internals::PUBLIC_OBJECT_BASIC_INFORMATION pobi;
+ ULONG length = 0;
+ for (size_t index = 0; index < opened_handles_.size(); ++index) {
+ nt_internals::NtQueryObject(opened_handles_[index].first,
+ nt_internals::ObjectBasicInformation, &pobi, sizeof(pobi), &length);
+ EXPECT_EQ(length, sizeof(pobi));
+ EXPECT_EQ(1UL, pobi.HandleCount);
+ if (1UL != pobi.HandleCount) {
+ printf("Leaked handle for: %s\n", opened_handles_[index].second);
+ }
+ EXPECT_TRUE(::CloseHandle(opened_handles_[index].first));
+ }
+ }
+
+ MOCK_METHOD2(WaitForSingleObject, DWORD(HANDLE, DWORD));
+ MOCK_METHOD4(WaitForMultipleObjects,
+ DWORD(DWORD, const HANDLE*, BOOL, DWORD));
+
+ HANDLE GetNewHandle(const char* info) {
+ // Some tests depend on the handle to be manual reset.
+ opened_handles_.push_back(std::pair<HANDLE, const char*>(
+ ::CreateEvent(NULL, TRUE, FALSE, NULL), info));
+ HANDLE returned_handle = NULL;
+ EXPECT_TRUE(::DuplicateHandle(::GetCurrentProcess(),
+ opened_handles_.back().first, ::GetCurrentProcess(), &returned_handle,
+ 0, FALSE, DUPLICATE_SAME_ACCESS));
+ return returned_handle;
+ }
+
+ // This is a seem in the base class allowing us ot use our own creator.
+ HRESULT GetExecutorCreator(ICeeeExecutorCreator** executor_creator) {
+ *executor_creator = &executor_creator_;
+ return S_OK;
+ }
+ // provide public access so we can test the method.
+ using ExecutorsManager::GetThreadHandles;
+ // Wrap the call to ThreadPRoc by first creating the necessary info.
+ DWORD CallThreadProc() {
+ // This struct is declared in the unnamed namespace of the implementation.
+ struct ThreadStartData {
+ ExecutorsManager* me;
+ CHandle thread_started_gate;
+ } thread_start_data;
+ thread_start_data.me = this;
+ thread_start_data.thread_started_gate.Attach(GetNewHandle("TSD"));
+ DCHECK(thread_start_data.thread_started_gate != NULL);
+ return ExecutorsManager::ThreadProc(&thread_start_data);
+ }
+
+ // Access to protected handles.
+ HANDLE GetUpdateHandle() {
+ return update_threads_list_gate_.m_h;
+ }
+ HANDLE GetTerminateHandle() {
+ return termination_gate_.m_h;
+ }
+
+ // Fake a pending registration, to properly test that case.
+ void AddPendingRegistration(HANDLE thread_handle,
+ ThreadId thread_id = kThreadId) {
+ pending_registrations_[thread_id].Attach(thread_handle);
+ }
+ void RemovePendingRegistration(ThreadId thread_id = kThreadId) {
+ pending_registrations_.erase(thread_id);
+ }
+
+ // This method is to be Invoked by the Mock of WaitForXXXObject[s].
+ void RegisterExecutorOnWait() {
+ executors_[current_thread_id_] = ExecutorInfo(current_executor_,
+ current_handle_);
+ }
+
+ // Public access to the executors_ map.
+ void FakeRegisterExecutor(HANDLE handle,
+ IUnknown* executor = NULL,
+ ThreadId thread_id = kThreadId) {
+ executors_[thread_id] = ExecutorInfo(executor, handle);
+ }
+ size_t GetNumExecutors() {
+ return executors_.size();
+ }
+ bool IsExecutorRegistered(ThreadId thread_id = kThreadId) {
+ return (executors_.find(thread_id) != executors_.end());
+ }
+
+ IUnknown* current_executor_;
+ HANDLE current_handle_;
+ ThreadId current_thread_id_;
+ MockExecutorCreator executor_creator_;
+ static const ThreadId kThreadId;
+ static const HWND kWindowHwnd;
+ static const CeeeWindowHandle kWindowHandle;
+
+ // Publicize these protected static const values.
+ using ExecutorsManager::kTerminationHandleIndexOffset;
+ using ExecutorsManager::kUpdateHandleIndexOffset;
+
+ std::vector<std::pair<HANDLE, const char*>> opened_handles_;
+};
+
+const TestingExecutorsManager::ThreadId TestingExecutorsManager::kThreadId = 42;
+const HWND TestingExecutorsManager::kWindowHwnd = reinterpret_cast<HWND>(93);
+const CeeeWindowHandle TestingExecutorsManager::kWindowHandle
+ = reinterpret_cast<CeeeWindowHandle>(kWindowHwnd);
+
+class ExecutorsManagerTests: public testing::Test {
+ public:
+ ExecutorsManagerTests() : initial_handle_count_(0) {
+ }
+ // This is not reliable enough, but we keep it here so that we can enable
+ // it from time to time to make sure everything is OK.
+ /*
+ virtual void SetUp() {
+ // This is called from the threadproc and changes the handles count if we
+ // don't call it first.
+ ASSERT_HRESULT_SUCCEEDED(::CoInitializeEx(NULL, COINIT_MULTITHREADED));
+
+ // Acquire the number of handles in the process.
+ ::GetProcessHandleCount(::GetCurrentProcess(), &initial_handle_count_);
+ printf("Initial Count: %d\n", initial_handle_count_);
+ }
+
+ virtual void TearDown() {
+ // Make sure the number of handles in the process didn't change.
+ DWORD new_handle_count = 0;
+ ::GetProcessHandleCount(::GetCurrentProcess(), &new_handle_count);
+ EXPECT_EQ(initial_handle_count_, new_handle_count);
+ printf("Final Count: %d\n", new_handle_count);
+
+ ::CoUninitialize();
+ }
+ */
+ private:
+ DWORD initial_handle_count_;
+};
+
+TEST_F(ExecutorsManagerTests, RegisterExecutor) {
+ testing::LogDisabler no_dchecks;
+ TestingExecutorsManager executors_manager;
+
+ // Invalid arguments.
+ EXPECT_EQ(E_INVALIDARG, executors_manager.RegisterWindowExecutor(
+ TestingExecutorsManager::kThreadId, NULL));
+
+ MockExecutor executor;
+ EXPECT_EQ(E_INVALIDARG, executors_manager.RegisterWindowExecutor(
+ TestingExecutorsManager::kThreadId, &executor));
+ EXPECT_EQ(0, executor.ref_count());
+
+ executors_manager.AddPendingRegistration(
+ executors_manager.GetNewHandle("PendingRegistration1"));
+ EXPECT_EQ(E_INVALIDARG, executors_manager.RegisterWindowExecutor(
+ TestingExecutorsManager::kThreadId, NULL));
+
+ // Dead thread...
+ testing::MockKernel32 kernel32;
+ EXPECT_CALL(kernel32, OpenThread(SYNCHRONIZE, FALSE,
+ TestingExecutorsManager::kThreadId)).WillOnce(Return((HANDLE)NULL));
+ EXPECT_EQ(S_FALSE, executors_manager.RegisterWindowExecutor(
+ TestingExecutorsManager::kThreadId, &executor));
+ EXPECT_EQ(0, executor.ref_count());
+
+ // Failed, already registered.
+ executors_manager.FakeRegisterExecutor(
+ executors_manager.GetNewHandle("FakeExec1"), &executor);
+ EXPECT_EQ(1, executors_manager.GetNumExecutors());
+ EXPECT_TRUE(executors_manager.IsExecutorRegistered());
+ EXPECT_GT(executor.ref_count(), 0UL);
+ ULONG previous_ref_count = executor.ref_count();
+
+ EXPECT_EQ(E_UNEXPECTED, executors_manager.RegisterWindowExecutor(
+ TestingExecutorsManager::kThreadId, &executor));
+ EXPECT_EQ(previous_ref_count, executor.ref_count());
+
+ // Cleanup.
+ EXPECT_EQ(S_OK, executors_manager.RemoveExecutor(
+ TestingExecutorsManager::kThreadId));
+ EXPECT_EQ(S_FALSE, executors_manager.RemoveExecutor(
+ TestingExecutorsManager::kThreadId));
+ EXPECT_EQ(0, executors_manager.GetNumExecutors());
+ EXPECT_FALSE(executors_manager.IsExecutorRegistered());
+ EXPECT_EQ(0, executor.ref_count());
+
+ // Success!!!
+ EXPECT_CALL(kernel32, OpenThread(SYNCHRONIZE, FALSE,
+ TestingExecutorsManager::kThreadId)).WillOnce(
+ Return(executors_manager.GetNewHandle("OpenThread")));
+ EXPECT_EQ(S_OK, executors_manager.RegisterWindowExecutor(
+ TestingExecutorsManager::kThreadId, &executor));
+ EXPECT_EQ(1, executors_manager.GetNumExecutors());
+ EXPECT_TRUE(executors_manager.IsExecutorRegistered());
+ EXPECT_GT(executor.ref_count(), 0UL);
+
+ // Make sure we properly cleanup.
+ executors_manager.RemovePendingRegistration();
+ EXPECT_EQ(S_OK, executors_manager.Terminate());
+ EXPECT_EQ(0, executor.ref_count());
+}
+
+TEST_F(ExecutorsManagerTests, RegisterTabExecutor) {
+ testing::LogDisabler no_dchecks;
+ TestingExecutorsManager executors_manager;
+
+ // Already registered.
+ MockExecutor executor;
+ HANDLE new_handle = executors_manager.GetNewHandle("FakeExec1");
+ executors_manager.FakeRegisterExecutor(new_handle, &executor);
+ EXPECT_EQ(1, executors_manager.GetNumExecutors());
+ EXPECT_TRUE(executors_manager.IsExecutorRegistered());
+ EXPECT_GT(executor.ref_count(), 0UL);
+ ULONG previous_ref_count = executor.ref_count();
+
+ EXPECT_EQ(S_OK, executors_manager.RegisterTabExecutor(
+ TestingExecutorsManager::kThreadId, &executor));
+ EXPECT_EQ(previous_ref_count, executor.ref_count());
+
+ // Cleanup.
+ EXPECT_EQ(S_OK, executors_manager.RemoveExecutor(
+ TestingExecutorsManager::kThreadId));
+ EXPECT_EQ(S_FALSE, executors_manager.RemoveExecutor(
+ TestingExecutorsManager::kThreadId));
+ EXPECT_EQ(0, executors_manager.GetNumExecutors());
+ EXPECT_FALSE(executors_manager.IsExecutorRegistered());
+ EXPECT_EQ(0, executor.ref_count());
+
+ // Dead thread...
+ testing::MockKernel32 kernel32;
+ EXPECT_CALL(kernel32, OpenThread(SYNCHRONIZE, FALSE,
+ TestingExecutorsManager::kThreadId)).WillOnce(Return((HANDLE)NULL));
+ EXPECT_EQ(E_UNEXPECTED, executors_manager.RegisterTabExecutor(
+ TestingExecutorsManager::kThreadId, &executor));
+ EXPECT_EQ(0, executor.ref_count());
+
+ // Success!!!
+ new_handle = executors_manager.GetNewHandle("OpenThread");
+ EXPECT_CALL(kernel32, OpenThread(SYNCHRONIZE, FALSE,
+ TestingExecutorsManager::kThreadId)).WillOnce(Return(new_handle));
+ EXPECT_EQ(S_OK, executors_manager.RegisterTabExecutor(
+ TestingExecutorsManager::kThreadId, &executor));
+ EXPECT_EQ(1, executors_manager.GetNumExecutors());
+ EXPECT_TRUE(executors_manager.IsExecutorRegistered());
+ EXPECT_GT(executor.ref_count(), 0UL);
+
+ // Make sure we properly cleanup.
+ EXPECT_EQ(S_OK, executors_manager.Terminate());
+ EXPECT_EQ(0, executor.ref_count());
+ EXPECT_EQ(0, executors_manager.GetNumExecutors());
+}
+
+TEST_F(ExecutorsManagerTests, GetExecutor) {
+ testing::LogDisabler no_dchecks;
+ TestingExecutorsManager executors_manager;
+
+ // Already in the map.
+ MockExecutor executor;
+ executors_manager.FakeRegisterExecutor(
+ executors_manager.GetNewHandle("FakeExec1"), &executor);
+ EXPECT_GT(executor.ref_count(), 0UL);
+ ULONG previous_ref_count = executor.ref_count();
+
+ IUnknown* result_executor = NULL;
+ EXPECT_CALL(executor, QueryInterface(_, _)).WillOnce(
+ DoAll(SetArgumentPointee<1>(&executor), Return(S_OK)));
+ EXPECT_EQ(S_OK, executors_manager.GetExecutor(
+ TestingExecutorsManager::kThreadId, TestingExecutorsManager::kWindowHwnd,
+ IID_IUnknown, reinterpret_cast<void**>(&result_executor)));
+ EXPECT_EQ(&executor, result_executor);
+ // Since our mocked QI doesn't addref, ref count shouldn't have changed.
+ EXPECT_EQ(previous_ref_count, executor.ref_count());
+
+ // Cleanup.
+ EXPECT_EQ(S_OK, executors_manager.RemoveExecutor(
+ TestingExecutorsManager::kThreadId));
+ // Since our mocked QI doesn't addref, we should be back to 0 now.
+ EXPECT_EQ(0, executor.ref_count());
+
+ // Fail Executor creation.
+ result_executor = NULL;
+ EXPECT_CALL(executors_manager.executor_creator_,
+ CreateWindowExecutor(TestingExecutorsManager::kThreadId,
+ TestingExecutorsManager::kWindowHandle)).
+ WillOnce(Return(E_FAIL));
+ EXPECT_CALL(executors_manager.executor_creator_, Release()).
+ WillOnce(Return(1));
+ EXPECT_EQ(E_FAIL, executors_manager.GetExecutor(
+ TestingExecutorsManager::kThreadId, TestingExecutorsManager::kWindowHwnd,
+ IID_IUnknown, reinterpret_cast<void**>(&result_executor)));
+ EXPECT_EQ(NULL, result_executor);
+
+ // Fail registration.
+ EXPECT_CALL(executors_manager, WaitForSingleObject(_, _)).
+ WillOnce(Return(WAIT_OBJECT_0));
+ EXPECT_CALL(executors_manager.executor_creator_,
+ CreateWindowExecutor(TestingExecutorsManager::kThreadId,
+ TestingExecutorsManager::kWindowHandle)).
+ WillOnce(Return(S_OK));
+ EXPECT_CALL(executors_manager.executor_creator_,
+ Teardown(TestingExecutorsManager::kThreadId)).WillOnce(Return(S_OK));
+ EXPECT_CALL(executors_manager.executor_creator_, Release()).
+ WillOnce(Return(1));
+ EXPECT_EQ(E_UNEXPECTED, executors_manager.GetExecutor(
+ TestingExecutorsManager::kThreadId, TestingExecutorsManager::kWindowHwnd,
+ IID_IUnknown, reinterpret_cast<void**>(&result_executor)));
+ EXPECT_EQ(NULL, result_executor);
+
+ // Pending registration fail.
+ EXPECT_CALL(executors_manager, WaitForSingleObject(_, _)).
+ WillOnce(Return(WAIT_OBJECT_0));
+ executors_manager.AddPendingRegistration(
+ executors_manager.GetNewHandle("PendingReg1"));
+
+ EXPECT_EQ(E_UNEXPECTED, executors_manager.GetExecutor(
+ TestingExecutorsManager::kThreadId, TestingExecutorsManager::kWindowHwnd,
+ IID_IUnknown, reinterpret_cast<void**>(&result_executor)));
+ EXPECT_EQ(NULL, result_executor);
+
+ // Success Creating new.
+ executors_manager.current_executor_ = &executor;
+ EXPECT_CALL(executors_manager, WaitForSingleObject(_, _)).
+ WillOnce(DoAll(InvokeWithoutArgs(&executors_manager,
+ &TestingExecutorsManager::RegisterExecutorOnWait),
+ Return(WAIT_OBJECT_0)));
+ EXPECT_CALL(executors_manager.executor_creator_,
+ CreateWindowExecutor(TestingExecutorsManager::kThreadId,
+ TestingExecutorsManager::kWindowHandle)).
+ WillOnce(Return(S_OK));
+ EXPECT_CALL(executors_manager.executor_creator_,
+ Teardown(TestingExecutorsManager::kThreadId)).WillOnce(Return(S_OK));
+ EXPECT_CALL(executors_manager.executor_creator_, Release()).
+ WillOnce(Return(1));
+ EXPECT_CALL(executor, QueryInterface(_, _)).WillOnce(
+ DoAll(SetArgumentPointee<1>(&executor), Return(S_OK)));
+ EXPECT_EQ(S_OK, executors_manager.GetExecutor(
+ TestingExecutorsManager::kThreadId, TestingExecutorsManager::kWindowHwnd,
+ IID_IUnknown, reinterpret_cast<void**>(&result_executor)));
+ EXPECT_EQ(&executor, result_executor);
+ EXPECT_GT(executor.ref_count(), 0UL);
+
+ // Cleanup.
+ EXPECT_EQ(S_OK, executors_manager.RemoveExecutor(
+ TestingExecutorsManager::kThreadId));
+ // Since our mocked QI doesn't addref, we should be back to 0 now.
+ EXPECT_EQ(0, executor.ref_count());
+
+ // Success with pending registration.
+ EXPECT_CALL(executors_manager, WaitForSingleObject(_, _)).
+ WillOnce(DoAll(InvokeWithoutArgs(&executors_manager,
+ &TestingExecutorsManager::RegisterExecutorOnWait),
+ Return(WAIT_OBJECT_0)));
+ executors_manager.AddPendingRegistration(
+ executors_manager.GetNewHandle("PendingReg2"));
+ EXPECT_CALL(executor, QueryInterface(_, _)).WillOnce(
+ DoAll(SetArgumentPointee<1>(&executor), Return(S_OK)));
+ EXPECT_EQ(S_OK, executors_manager.GetExecutor(
+ TestingExecutorsManager::kThreadId, TestingExecutorsManager::kWindowHwnd,
+ IID_IUnknown, reinterpret_cast<void**>(&result_executor)));
+ EXPECT_EQ(&executor, result_executor);
+ EXPECT_GT(executor.ref_count(), 0UL);
+
+ // Make sure we properly cleanup.
+ EXPECT_EQ(S_OK, executors_manager.Terminate());
+ // Since our mocked QI doesn't addref, we should be back to 0 now.
+ EXPECT_EQ(0, executor.ref_count());
+ EXPECT_EQ(0, executors_manager.GetNumExecutors());
+}
+
+TEST_F(ExecutorsManagerTests, RemoveExecutor) {
+ TestingExecutorsManager executors_manager;
+
+ // Nothing to remove...
+ EXPECT_EQ(0, executors_manager.GetNumExecutors());
+ EXPECT_EQ(S_FALSE, executors_manager.RemoveExecutor(1));
+ EXPECT_EQ(0, executors_manager.GetNumExecutors());
+
+ // Success.
+ MockExecutor executor1;
+ executors_manager.FakeRegisterExecutor(
+ executors_manager.GetNewHandle("Fake0"), &executor1);
+ EXPECT_EQ(1, executors_manager.GetNumExecutors());
+ EXPECT_GT(executor1.ref_count(), 0UL);
+ EXPECT_EQ(S_OK, executors_manager.RemoveExecutor(
+ TestingExecutorsManager::kThreadId));
+ EXPECT_EQ(0, executors_manager.GetNumExecutors());
+ EXPECT_EQ(0, executor1.ref_count());
+ EXPECT_FALSE(executors_manager.IsExecutorRegistered());
+ EXPECT_EQ(S_FALSE, executors_manager.RemoveExecutor(
+ TestingExecutorsManager::kThreadId));
+
+ // Multiple values, removed one at a time...
+ MockExecutor executor2;
+ MockExecutor executor3;
+ executors_manager.FakeRegisterExecutor(
+ executors_manager.GetNewHandle("Fake1"), &executor1, 1);
+ EXPECT_EQ(1, executors_manager.GetNumExecutors());
+ EXPECT_GT(executor1.ref_count(), 0UL);
+ executors_manager.FakeRegisterExecutor(
+ executors_manager.GetNewHandle("Fake2"), &executor2, 2);
+ EXPECT_EQ(2, executors_manager.GetNumExecutors());
+ EXPECT_GT(executor2.ref_count(), 0UL);
+ executors_manager.FakeRegisterExecutor(
+ executors_manager.GetNewHandle("Fake3"), &executor3, 3);
+ EXPECT_EQ(3, executors_manager.GetNumExecutors());
+ EXPECT_GT(executor3.ref_count(), 0UL);
+ EXPECT_EQ(S_OK, executors_manager.RemoveExecutor(2));
+ EXPECT_EQ(2, executors_manager.GetNumExecutors());
+ EXPECT_FALSE(executors_manager.IsExecutorRegistered(2));
+ EXPECT_TRUE(executors_manager.IsExecutorRegistered(1));
+ EXPECT_TRUE(executors_manager.IsExecutorRegistered(3));
+ EXPECT_GT(executor1.ref_count(), 0UL);
+ EXPECT_EQ(0, executor2.ref_count());
+ EXPECT_GT(executor3.ref_count(), 0UL);
+
+ EXPECT_EQ(S_OK, executors_manager.RemoveExecutor(3));
+ EXPECT_EQ(1, executors_manager.GetNumExecutors());
+ EXPECT_FALSE(executors_manager.IsExecutorRegistered(3));
+ EXPECT_TRUE(executors_manager.IsExecutorRegistered(1));
+ EXPECT_GT(executor1.ref_count(), 0UL);
+ EXPECT_EQ(0, executor3.ref_count());
+
+ EXPECT_EQ(S_OK, executors_manager.RemoveExecutor(1));
+ EXPECT_EQ(0, executors_manager.GetNumExecutors());
+ EXPECT_FALSE(executors_manager.IsExecutorRegistered(1));
+ EXPECT_EQ(0, executor1.ref_count());
+}
+
+// Since the returned handle is a duplicate, making sure we have the
+// appropriate handle is a little tricky.
+void VerifyIsSameHandle(HANDLE handle1, HANDLE handle2) {
+ // First make sure neither is set.
+ EXPECT_EQ(WAIT_TIMEOUT, ::WaitForSingleObject(handle1, 0));
+ EXPECT_EQ(WAIT_TIMEOUT, ::WaitForSingleObject(handle2, 0));
+
+ // Now check that setting one also sets the other.
+ ::SetEvent(handle1);
+ EXPECT_EQ(WAIT_OBJECT_0, ::WaitForSingleObject(handle1, 0));
+ EXPECT_EQ(WAIT_OBJECT_0, ::WaitForSingleObject(handle2, 0));
+
+ // Manual reset.
+ ::ResetEvent(handle1);
+ EXPECT_EQ(WAIT_TIMEOUT, ::WaitForSingleObject(handle1, 0));
+ EXPECT_EQ(WAIT_TIMEOUT, ::WaitForSingleObject(handle2, 0));
+}
+
+TEST_F(ExecutorsManagerTests, GetThreadHandles) {
+ TestingExecutorsManager executors_manager;
+
+ // We need to work with invalid, and one other valid value for thread info.
+ static const TestingExecutorsManager::ThreadId kInvalidThreadId = 0xF0F0F0F0;
+ static const TestingExecutorsManager::ThreadId kThreadIdA = 0xAAAAAAAA;
+ static const TestingExecutorsManager::ThreadId kThreadIdB = 0xBBBBBBBB;
+
+ // As for the thread proc code, we work with arrays of handles and thread ids
+ // on the stack.
+ CHandle thread_handles[MAXIMUM_WAIT_OBJECTS];
+ TestingExecutorsManager::ThreadId thread_ids[MAXIMUM_WAIT_OBJECTS];
+
+ // Make sure the arrays are not touched.
+ thread_ids[0] = kInvalidThreadId;
+ size_t num_threads = executors_manager.GetThreadHandles(
+ thread_handles, thread_ids, MAXIMUM_WAIT_OBJECTS);
+ EXPECT_EQ(0, num_threads);
+ EXPECT_EQ(static_cast<HANDLE>(NULL), thread_handles[0]);
+ EXPECT_EQ(kInvalidThreadId, thread_ids[0]);
+
+ // Make sure only the first index will get affected.
+ thread_ids[1] = kInvalidThreadId;
+ HANDLE thread_handle_a = executors_manager.GetNewHandle("A");
+ executors_manager.FakeRegisterExecutor(thread_handle_a, NULL, kThreadIdA);
+ num_threads = executors_manager.GetThreadHandles(thread_handles, thread_ids,
+ MAXIMUM_WAIT_OBJECTS);
+ EXPECT_EQ(1, num_threads);
+ VerifyIsSameHandle(thread_handle_a, thread_handles[0]);
+ EXPECT_EQ(kThreadIdA, thread_ids[0]);
+ EXPECT_EQ(static_cast<HANDLE>(NULL), thread_handles[1]);
+ EXPECT_EQ(kInvalidThreadId, thread_ids[1]);
+ // Need to close active handles since GetThreadHandles expect NULL m_h.
+ thread_handles[0].Close();
+
+ // Now expect 2 values and make sure the third one isn't affected.
+ thread_ids[0] = kInvalidThreadId;
+ thread_ids[2] = kInvalidThreadId; // We asserted above that index 1 is OK.
+ HANDLE thread_handle_b = executors_manager.GetNewHandle("B");
+ executors_manager.FakeRegisterExecutor(thread_handle_b, NULL, kThreadIdB);
+ num_threads = executors_manager.GetThreadHandles(thread_handles, thread_ids,
+ MAXIMUM_WAIT_OBJECTS);
+ EXPECT_EQ(2, num_threads);
+ // We can't be sure of the order in which the map returns the elements.
+ if (thread_ids[0] == kThreadIdA) {
+ VerifyIsSameHandle(thread_handle_a, thread_handles[0]);
+ EXPECT_EQ(kThreadIdB, thread_ids[1]);
+ VerifyIsSameHandle(thread_handle_b, thread_handles[1]);
+ } else {
+ VerifyIsSameHandle(thread_handle_b, thread_handles[0]);
+ EXPECT_EQ(kThreadIdB, thread_ids[0]);
+ VerifyIsSameHandle(thread_handle_a, thread_handles[1]);
+ EXPECT_EQ(kThreadIdA, thread_ids[1]);
+ }
+ EXPECT_EQ(static_cast<HANDLE>(NULL), thread_handles[2]);
+ EXPECT_EQ(kInvalidThreadId, thread_ids[2]);
+ thread_handles[0].Close();
+ thread_handles[1].Close();
+
+ // Now remove threads and make sure they won't be returned again.
+ thread_ids[0] = kInvalidThreadId;
+ thread_ids[1] = kInvalidThreadId; // Asserted index 2 invalid already.
+
+ EXPECT_EQ(S_OK, executors_manager.RemoveExecutor(kThreadIdA));
+ num_threads = executors_manager.GetThreadHandles(thread_handles, thread_ids,
+ MAXIMUM_WAIT_OBJECTS);
+ EXPECT_EQ(1, num_threads);
+ VerifyIsSameHandle(thread_handle_b, thread_handles[0]);
+ EXPECT_EQ(kThreadIdB, thread_ids[0]);
+ EXPECT_EQ(static_cast<HANDLE>(NULL), thread_handles[1]);
+ EXPECT_EQ(kInvalidThreadId, thread_ids[1]);
+ thread_handles[0].Close();
+
+ // Now remove the last one...
+ thread_ids[0] = kInvalidThreadId;
+ EXPECT_EQ(S_OK, executors_manager.RemoveExecutor(kThreadIdB));
+ num_threads = executors_manager.GetThreadHandles(thread_handles, thread_ids,
+ MAXIMUM_WAIT_OBJECTS);
+ EXPECT_EQ(0, num_threads);
+ EXPECT_EQ(static_cast<HANDLE>(NULL), thread_handles[0]);
+ EXPECT_EQ(kInvalidThreadId, thread_ids[0]);
+}
+
+TEST_F(ExecutorsManagerTests, ThreadProc) {
+ testing::LogDisabler no_logs;
+ TestingExecutorsManager executors_manager;
+
+ // Register an object and make sure we wake up to update our list.
+ MockExecutor executor1;
+ executors_manager.current_executor_ = &executor1;
+ executors_manager.current_handle_ = executors_manager.GetNewHandle("1");
+ EXPECT_CALL(executors_manager, WaitForMultipleObjects(2, _, FALSE, INFINITE)).
+ WillOnce(DoAll(InvokeWithoutArgs(&executors_manager,
+ &TestingExecutorsManager::RegisterExecutorOnWait),
+ Return(TestingExecutorsManager::kUpdateHandleIndexOffset)));
+ EXPECT_CALL(executors_manager, WaitForMultipleObjects(3, _, FALSE, INFINITE)).
+ WillOnce(Return(
+ TestingExecutorsManager::kTerminationHandleIndexOffset + 1));
+ EXPECT_EQ(1, executors_manager.CallThreadProc());
+
+ EXPECT_TRUE(executors_manager.IsExecutorRegistered());
+ EXPECT_EQ(1, executors_manager.GetNumExecutors());
+ EXPECT_GT(executor1.ref_count(), 0UL);
+
+ // Add another destination thread.
+ static const TestingExecutorsManager::ThreadId kOtherThreadId = 14;
+ MockExecutor executor2;
+ executors_manager.current_executor_ = &executor2;
+ executors_manager.current_handle_ = executors_manager.GetNewHandle("2");
+ executors_manager.current_thread_id_ = kOtherThreadId;
+ // Number of handles and return index offset needs to be increased by 1
+ // because we already have a destination thread in our map.
+ EXPECT_CALL(executors_manager, WaitForMultipleObjects(3, _, FALSE, INFINITE)).
+ WillOnce(DoAll(InvokeWithoutArgs(&executors_manager,
+ &TestingExecutorsManager::RegisterExecutorOnWait), Return(
+ TestingExecutorsManager::kUpdateHandleIndexOffset + 1))).WillOnce(
+ Return(TestingExecutorsManager::kTerminationHandleIndexOffset + 1));
+ // Now that we have another destination thread in the map,
+ // we wait for 4 handles. We return 1 to remove one of them, and wait will
+ // be called with three handles again, the mock above will be called for the
+ // second time with 3 handles and will return the proper termination offset.
+ EXPECT_CALL(executors_manager, WaitForMultipleObjects(4, _, FALSE, INFINITE)).
+ WillOnce(Return(1));
+ EXPECT_EQ(1, executors_manager.CallThreadProc());
+ EXPECT_EQ(1, executors_manager.GetNumExecutors());
+ // We can't tell which one is at position 1 in the array returned by
+ // GetThreadHandles since they are fetched from an unsorted map.
+ EXPECT_TRUE(executors_manager.IsExecutorRegistered(kOtherThreadId) ||
+ executors_manager.IsExecutorRegistered(
+ TestingExecutorsManager::kThreadId));
+ if (executors_manager.IsExecutorRegistered(kOtherThreadId)) {
+ // We kept the other executor in the map.
+ EXPECT_EQ(0, executor1.ref_count());
+ EXPECT_GT(executor2.ref_count(), 0UL);
+ } else {
+ EXPECT_GT(executor1.ref_count(), 0UL);
+ EXPECT_EQ(0, executor2.ref_count());
+ }
+
+ // Cleanup.
+ executors_manager.Terminate();
+ EXPECT_EQ(0, executor1.ref_count());
+ EXPECT_EQ(0, executor2.ref_count());
+ EXPECT_EQ(0, executors_manager.GetNumExecutors());
+
+ // Test the thread failure path.
+ EXPECT_CALL(executors_manager, WaitForMultipleObjects(2, _, FALSE, INFINITE)).
+ WillOnce(Return(WAIT_FAILED));
+ EXPECT_EQ(1, executors_manager.CallThreadProc());
+}
+
+} // namespace
diff --git a/ceee/ie/broker/infobar_api_module.cc b/ceee/ie/broker/infobar_api_module.cc
new file mode 100644
index 0000000..f857b4a
--- /dev/null
+++ b/ceee/ie/broker/infobar_api_module.cc
@@ -0,0 +1,99 @@
+// 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.
+//
+// Infobar API implementation.
+
+#include "ceee/ie/broker/infobar_api_module.h"
+
+#include <atlbase.h>
+#include <atlcom.h> // Must be included AFTER base.
+
+#include "base/string_number_conversions.h"
+#include "ceee/common/com_utils.h"
+#include "ceee/ie/broker/api_module_constants.h"
+#include "ceee/ie/common/api_registration.h"
+#include "chrome/browser/extensions/extension_infobar_module_constants.h"
+#include "chrome/common/extensions/extension_error_utils.h"
+
+namespace ext = extension_infobar_module_constants;
+
+namespace infobar_api {
+
+const char kOnDocumentCompleteEventName[] = "infobar.onDocumentComplete";
+
+void RegisterInvocations(ApiDispatcher* dispatcher) {
+#define REGISTER_API_FUNCTION(func) do { dispatcher->RegisterInvocation(\
+ func##Function::function_name(), NewApiInvocation< func >); } while (false)
+ REGISTER_INFOBAR_API_FUNCTIONS();
+#undef REGISTER_API_FUNCTION
+}
+
+void ShowInfoBar::Execute(const ListValue& args, int request_id) {
+ scoped_ptr<InfobarApiResult> result(CreateApiResult(request_id));
+
+ // Get the first parameter (object details).
+ DictionaryValue* dict;
+ if (!args.GetDictionary(0, &dict)) {
+ result->PostError(api_module_constants::kInvalidArgumentsError);
+ return;
+ }
+
+ // The dictionary should have both tabId and path properties.
+ int tab_id;
+ std::string path;
+ if (!dict->GetInteger(ext::kTabId, &tab_id) ||
+ !dict->GetString(ext::kHtmlPath, &path)) {
+ result->PostError(api_module_constants::kInvalidArgumentsError);
+ return;
+ }
+
+ ApiDispatcher* dispatcher = GetDispatcher();
+ DCHECK(dispatcher != NULL);
+ HWND tab_window = dispatcher->GetTabHandleFromId(tab_id);
+ if (!result->IsTabWindowClass(tab_window)) {
+ result->PostError(ExtensionErrorUtils::FormatErrorMessage(
+ ext::kTabNotFoundError, base::IntToString(tab_id)).c_str());
+ return;
+ }
+
+ CComPtr<ICeeeInfobarExecutor> executor;
+ GetDispatcher()->GetExecutor(tab_window, IID_ICeeeInfobarExecutor,
+ reinterpret_cast<void**>(&executor));
+ if (executor == NULL) {
+ LOG(WARNING) << "Failed to get an executor to show infobar.";
+ result->PostError(api_module_constants::kInternalErrorError);
+ return;
+ }
+ CeeeWindowHandle window_handle;
+ HRESULT hr = executor->ShowInfobar(CComBSTR(path.data()), &window_handle);
+ if (FAILED(hr)) {
+ // No DCHECK, this may happen if the window/thread dies on the way.
+ LOG(ERROR) << "Can't show infobar. " << com::LogHr(hr) << " path=" << path;
+ result->PostError(api_module_constants::kInternalErrorError);
+ return;
+ }
+
+ // Store the window information returned by ShowInfobar().
+ result->CreateWindowValue(dispatcher->GetWindowHandleFromId(window_handle),
+ false);
+
+ // Now wait until the document is complete before responding.
+ dispatcher->RegisterEphemeralEventHandler(kOnDocumentCompleteEventName,
+ ShowInfoBar::ContinueExecution, result.release());
+}
+
+HRESULT ShowInfoBar::ContinueExecution(
+ const std::string& input_args,
+ ApiDispatcher::InvocationResult* user_data,
+ ApiDispatcher* dispatcher) {
+ DCHECK(user_data != NULL);
+ DCHECK(dispatcher != NULL);
+
+ scoped_ptr<InfobarApiResult> result(
+ static_cast<InfobarApiResult*>(user_data));
+ result->PostResult();
+ return S_OK;
+}
+
+} // namespace infobar_api
diff --git a/ceee/ie/broker/infobar_api_module.h b/ceee/ie/broker/infobar_api_module.h
new file mode 100644
index 0000000..53b3f65
--- /dev/null
+++ b/ceee/ie/broker/infobar_api_module.h
@@ -0,0 +1,43 @@
+// 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.
+//
+// Infobar API implementation.
+
+#ifndef CEEE_IE_BROKER_INFOBAR_API_MODULE_H_
+#define CEEE_IE_BROKER_INFOBAR_API_MODULE_H_
+
+#include <string>
+
+#include "ceee/ie/broker/api_dispatcher.h"
+#include "ceee/ie/broker/common_api_module.h"
+
+namespace infobar_api {
+
+class InfobarApiResult;
+typedef ApiResultCreator<InfobarApiResult> InfobarApiResultCreator;
+
+// Registers all Infobar API invocations with the given dispatcher.
+void RegisterInvocations(ApiDispatcher* dispatcher);
+
+class InfobarApiResult : public common_api::CommonApiResult {
+ public:
+ explicit InfobarApiResult(int request_id)
+ : common_api::CommonApiResult(request_id) {}
+};
+
+typedef IterativeApiResult<InfobarApiResult> IterativeInfobarApiResult;
+
+class ShowInfoBar : public InfobarApiResultCreator {
+ public:
+ virtual void Execute(const ListValue& args, int request_id);
+ // We need to wait for the infobar browser ready event to complete the
+ // result response.
+ static HRESULT ContinueExecution(const std::string& input_args,
+ ApiDispatcher::InvocationResult* user_data,
+ ApiDispatcher* dispatcher);
+};
+
+} // namespace infobar_api
+
+#endif // CEEE_IE_BROKER_INFOBAR_API_MODULE_H_
diff --git a/ceee/ie/broker/infobar_api_module_unittest.cc b/ceee/ie/broker/infobar_api_module_unittest.cc
new file mode 100644
index 0000000..d9bce10
--- /dev/null
+++ b/ceee/ie/broker/infobar_api_module_unittest.cc
@@ -0,0 +1,144 @@
+// 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 API implementation unit tests.
+
+// MockWin32 can't be included after ChromeFrameHost because of an include
+// incompatibility with atlwin.h.
+#include "ceee/testing/utils/mock_win32.h" // NOLINT
+
+#include "ceee/ie/broker/infobar_api_module.h"
+#include "ceee/ie/testing/mock_broker_and_friends.h"
+#include "ceee/testing/utils/mock_window_utils.h"
+#include "ceee/testing/utils/test_utils.h"
+#include "chrome/browser/extensions/extension_infobar_module_constants.h"
+#include "testing/gmock/include/gmock/gmock.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+namespace ext = extension_infobar_module_constants;
+
+namespace {
+
+using infobar_api::InfobarApiResult;
+using infobar_api::RegisterInvocations;
+using infobar_api::ShowInfoBar;
+
+using testing::_;
+using testing::AddRef;
+using testing::AtLeast;
+using testing::MockApiDispatcher;
+using testing::MockApiInvocation;
+using testing::NotNull;
+using testing::Return;
+using testing::SetArgumentPointee;
+using testing::StrEq;
+using testing::StrictMock;
+
+const int kGoodTabWindowId = 99;
+const int kGoodFrameWindowId = 88;
+const int kRequestId = 43;
+const char kHtmlPath[] = "/infobar/test.html";
+
+const HWND kGoodTabWindow = (HWND)(kGoodTabWindowId + 1);
+const HWND kGoodFrameWindow = (HWND)(kGoodFrameWindowId + 1);
+
+TEST(InfobarApi, RegisterInvocations) {
+ StrictMock<MockApiDispatcher> disp;
+ EXPECT_CALL(disp, RegisterInvocation(NotNull(), NotNull())).Times(AtLeast(1));
+ RegisterInvocations(&disp);
+}
+
+class MockInfobarApiResult : public InfobarApiResult {
+ public:
+ explicit MockInfobarApiResult(int request_id)
+ : InfobarApiResult(request_id) {}
+ MOCK_METHOD0(PostResult, void());
+ MOCK_METHOD1(PostError, void(const std::string&));
+
+ virtual ApiDispatcher* GetDispatcher() {
+ return &mock_api_dispatcher_;
+ }
+
+ StrictMock<MockApiDispatcher> mock_api_dispatcher_;
+};
+
+class InfobarApiTests: public testing::Test {
+ public:
+ virtual void SetUp() {
+ EXPECT_HRESULT_SUCCEEDED(testing::MockInfobarExecutor::CreateInitialized(
+ &mock_infobar_executor_, &mock_infobar_executor_keeper_));
+ }
+
+ virtual void TearDown() {
+ // Everything should have been relinquished.
+ mock_infobar_executor_keeper_.Release();
+ ASSERT_EQ(0, testing::InstanceCountMixinBase::all_instance_count());
+ }
+
+ protected:
+ void AlwaysMockGetInfobarExecutor(MockApiDispatcher* api_dispatcher,
+ HWND window) {
+ // We can't use CopyInterfaceToArgument here because GetExecutor takes a
+ // void** argument instead of an interface.
+ EXPECT_CALL(*api_dispatcher, GetExecutor(window, _, NotNull())).
+ WillRepeatedly(DoAll(
+ SetArgumentPointee<2>(mock_infobar_executor_keeper_.p),
+ AddRef(mock_infobar_executor_keeper_.p)));
+ }
+
+ void MockGetInfobarExecutorOnce(MockApiDispatcher* api_dispatcher,
+ HWND window) {
+ // We can't use CopyInterfaceToArgument here because GetExecutor takes a
+ // void** argument instead of an interface.
+ EXPECT_CALL(*api_dispatcher, GetExecutor(window, _, NotNull())).
+ WillOnce(DoAll(SetArgumentPointee<2>(mock_infobar_executor_keeper_.p),
+ AddRef(mock_infobar_executor_keeper_.p)));
+ }
+
+ StrictMock<testing::MockUser32> user32_;
+ // The executor classes are already strict from their base class impl.
+ testing::MockInfobarExecutor* mock_infobar_executor_;
+
+ private:
+ // To control the life span of the tab executor.
+ CComPtr<ICeeeInfobarExecutor> mock_infobar_executor_keeper_;
+};
+
+TEST_F(InfobarApiTests, ShowInfoBarNoErrors) {
+ // TODO(vadimb@google.com): Make the implementation work.
+#if 0
+ testing::LogDisabler no_dchecks;
+
+ DictionaryValue dict;
+ dict.Set(ext::kTabId, Value::CreateIntegerValue(kGoodTabWindowId));
+ dict.Set(ext::kHtmlPath, Value::CreateStringValue(std::string(kHtmlPath)));
+ ListValue good_args;
+ ASSERT_TRUE(good_args.Set(0, dict.DeepCopy()));
+
+ StrictMock<testing::MockWindowUtils> window_utils;
+ EXPECT_CALL(window_utils, IsWindowClass(kGoodTabWindow, _)).
+ WillRepeatedly(Return(true));
+
+ StrictMock<MockApiInvocation<InfobarApiResult, MockInfobarApiResult,
+ ShowInfoBar> > invocation;
+ EXPECT_CALL(invocation.mock_api_dispatcher_,
+ GetTabHandleFromId(kGoodTabWindowId)).
+ WillRepeatedly(Return(kGoodTabWindow));
+ EXPECT_CALL(invocation.mock_api_dispatcher_,
+ GetWindowHandleFromId(kGoodFrameWindowId)).
+ WillRepeatedly(Return(kGoodFrameWindow));
+ MockGetInfobarExecutorOnce(&invocation.mock_api_dispatcher_, kGoodTabWindow);
+ CComBSTR html_path(kHtmlPath);
+ CeeeWindowHandle window_handle =
+ reinterpret_cast<CeeeWindowHandle>(kGoodFrameWindow);
+ EXPECT_CALL(*mock_infobar_executor_, ShowInfobar(StrEq(html_path.m_str), _)).
+ WillOnce(DoAll(SetArgumentPointee<1>(window_handle), Return(S_OK)));
+
+ invocation.AllocateNewResult(kRequestId);
+
+ invocation.Execute(good_args, kRequestId);
+#endif
+}
+
+} // namespace
diff --git a/ceee/ie/broker/resource.h b/ceee/ie/broker/resource.h
new file mode 100644
index 0000000..60c6977
--- /dev/null
+++ b/ceee/ie/broker/resource.h
@@ -0,0 +1,31 @@
+// 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 user broker executable.
+
+#ifndef CEEE_IE_BROKER_RESOURCE_H_
+#define CEEE_IE_BROKER_RESOURCE_H_
+
+
+// {{NO_DEPENDENCIES}}
+// Microsoft Visual C++ generated include file.
+// Used by broker_module.rc
+//
+#define IDS_PROJNAME 100
+#define IDR_BROKER_MODULE 101
+#define IDR_BROKER 102
+
+
+// Next default values for new objects
+#ifdef APSTUDIO_INVOKED
+#ifndef APSTUDIO_READONLY_SYMBOLS
+#define _APS_NEXT_RESOURCE_VALUE 201
+#define _APS_NEXT_COMMAND_VALUE 32768
+#define _APS_NEXT_CONTROL_VALUE 201
+#define _APS_NEXT_SYMED_VALUE 103
+#endif
+#endif
+
+
+#endif // CEEE_IE_BROKER_RESOURCE_H_
diff --git a/ceee/ie/broker/tab_api_module.cc b/ceee/ie/broker/tab_api_module.cc
new file mode 100644
index 0000000..c75e2c1
--- /dev/null
+++ b/ceee/ie/broker/tab_api_module.cc
@@ -0,0 +1,1185 @@
+// 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.
+//
+// Tab API implementation.
+//
+// Tab IDs are the window handle of the "TabWindowClass" window class
+// of the whole tab.
+//
+// To find the chrome.window.* "window ID" we can just get the top-level parent
+// window of the tab window.
+//
+// TODO(joi@chromium.org) Figure out what to do in IE6 (which has no tabs).
+
+#include "ceee/ie/broker/tab_api_module.h"
+
+#include <atlbase.h>
+#include <atlcom.h>
+
+#include "base/json/json_reader.h"
+#include "base/json/json_writer.h"
+#include "base/logging.h"
+#include "base/string_number_conversions.h"
+#include "base/string_util.h"
+#include "base/values.h"
+#include "base/utf_string_conversions.h"
+#include "base/win/windows_version.h"
+#include "ceee/ie/broker/api_module_constants.h"
+#include "ceee/ie/broker/api_module_util.h"
+#include "ceee/ie/common/api_registration.h"
+#include "ceee/ie/common/constants.h"
+#include "ceee/ie/common/ie_util.h"
+#include "chrome/browser/automation/extension_automation_constants.h"
+#include "chrome/browser/extensions/extension_event_names.h"
+#include "chrome/browser/extensions/extension_tabs_module_constants.h"
+#include "chrome/common/extensions/extension_error_utils.h"
+#include "chrome/common/url_constants.h"
+#include "googleurl/src/gurl.h"
+#include "ceee/common/com_utils.h"
+#include "ceee/common/window_utils.h"
+#include "ceee/common/windows_constants.h"
+
+
+namespace ext = extension_tabs_module_constants;
+namespace ext_event_names = extension_event_names;
+namespace keys = extension_automation_constants;
+
+namespace tab_api {
+
+namespace {
+
+// bb3147348
+// Convert the tab_id parameter (always the first one) and verify that the
+// event received has the right number of parameters.
+// NumParam is the number of parameters we expect from events_funnel.
+// AddTabParam if true, we add a Tab object to the converted_args.
+template<int NumParam, bool AddTabParam>
+bool ConvertTabIdEventHandler(const std::string& input_args,
+ std::string* converted_args,
+ ApiDispatcher* dispatcher) {
+ DCHECK(converted_args);
+ *converted_args = input_args;
+
+ // Get the tab ID from the input arguments.
+ scoped_ptr<ListValue> input_list;
+ if (!api_module_util::GetListFromJsonString(input_args, &input_list)) {
+ NOTREACHED() << "Invalid Arguments sent to event.";
+ return false;
+ }
+
+ if (input_list == NULL || input_list->GetSize() != NumParam) {
+ NOTREACHED() << "Invalid Number of Arguments sent to event.";
+ return false;
+ }
+
+ int tab_handle = -1;
+ bool success = input_list->GetInteger(0, &tab_handle);
+ DCHECK(success) << "Failed getting the tab_id value from the list of args.";
+ HWND tab_window = reinterpret_cast<HWND>(tab_handle);
+ int tab_id = dispatcher->GetTabIdFromHandle(tab_window);
+ DCHECK(tab_id != kInvalidChromeSessionId);
+ input_list->Set(0, Value::CreateIntegerValue(tab_id));
+
+ if (AddTabParam) {
+ TabApiResult result(TabApiResult::kNoRequestId);
+ // Don't DCHECK here since we have cases where the tab died beforehand.
+ if (!result.CreateTabValue(tab_id, -1)) {
+ LOG(ERROR) << "Failed to create a value for tab: " << std::hex << tab_id;
+ return false;
+ }
+
+ input_list->Append(result.value()->DeepCopy());
+ }
+
+ base::JSONWriter::Write(input_list.get(), false, converted_args);
+ return true;
+}
+
+bool CeeeUnmapTabEventHandler(const std::string& input_args,
+ std::string* converted_args,
+ ApiDispatcher* dispatcher) {
+ int tab_handle = reinterpret_cast<int>(INVALID_HANDLE_VALUE);
+
+ scoped_ptr<ListValue> input_list;
+ if (!api_module_util::GetListAndIntegerValue(input_args, &input_list,
+ &tab_handle) ||
+ tab_handle == kInvalidChromeSessionId) {
+ NOTREACHED() << "An invalid argument was passed to UnmapTab";
+ return false;
+ }
+#ifdef DEBUG
+ int tab_id = kInvalidChromeSessionId;
+ input_list->GetInteger(1, &tab_id);
+ DCHECK(tab_id == dispatcher->GetTabIdFromHandle(
+ reinterpret_cast<HWND>(tab_handle)));
+#endif // DEBUG
+
+ HWND tab_window = reinterpret_cast<HWND>(tab_handle);
+ dispatcher->DeleteTabHandle(tab_window);
+ return false;
+}
+
+}
+
+void RegisterInvocations(ApiDispatcher* dispatcher) {
+#define REGISTER_API_FUNCTION(func) do { dispatcher->RegisterInvocation(\
+ func##Function::function_name(), NewApiInvocation< func >); }\
+ while (false)
+ REGISTER_TAB_API_FUNCTIONS();
+#undef REGISTER_API_FUNCTION
+ // Registers our private events.
+ dispatcher->RegisterPermanentEventHandler(
+ ceee_event_names::kCeeeOnTabUnmapped, CeeeUnmapTabEventHandler);
+
+ // And now register the permanent event handlers.
+ dispatcher->RegisterPermanentEventHandler(ext_event_names::kOnTabCreated,
+ CreateTab::EventHandler);
+
+ // For OnTabUpdate, we receive 2 from events_funnel, and add a Tab Parameter.
+ dispatcher->RegisterPermanentEventHandler(ext_event_names::kOnTabUpdated,
+ ConvertTabIdEventHandler<2, true>);
+ dispatcher->RegisterPermanentEventHandler(ext_event_names::kOnTabAttached,
+ ConvertTabIdEventHandler<2, false>);
+ dispatcher->RegisterPermanentEventHandler(ext_event_names::kOnTabDetached,
+ ConvertTabIdEventHandler<2, false>);
+ dispatcher->RegisterPermanentEventHandler(ext_event_names::kOnTabMoved,
+ ConvertTabIdEventHandler<2, false>);
+ dispatcher->RegisterPermanentEventHandler(ext_event_names::kOnTabRemoved,
+ ConvertTabIdEventHandler<1, false>);
+ dispatcher->RegisterPermanentEventHandler(
+ ext_event_names::kOnTabSelectionChanged,
+ ConvertTabIdEventHandler<2, false>);
+}
+
+bool TabApiResult::CreateTabValue(int tab_id, long index) {
+ ApiDispatcher* dispatcher = GetDispatcher();
+ DCHECK(dispatcher != NULL);
+ HWND tab_window = dispatcher->GetTabHandleFromId(tab_id);
+
+ if (window_utils::WindowHasNoThread(tab_window)) {
+ PostError(ExtensionErrorUtils::FormatErrorMessage(
+ ext::kTabNotFoundError, base::IntToString(tab_id)));
+ return false;
+ }
+
+ if (!IsTabWindowClass(tab_window)) {
+ PostError(ExtensionErrorUtils::FormatErrorMessage(
+ ext::kTabNotFoundError, base::IntToString(tab_id)));
+ return false;
+ }
+
+ CComPtr<ICeeeTabExecutor> executor;
+ dispatcher->GetExecutor(tab_window, IID_ICeeeTabExecutor,
+ reinterpret_cast<void**>(&executor));
+ if (executor == NULL) {
+ LOG(WARNING) << "Failed to get an executor to get tab info.";
+ PostError(api_module_constants::kInternalErrorError);
+ return false;
+ }
+
+ TabInfo tab_info;
+ HRESULT hr = executor->GetTabInfo(&tab_info);
+ if (FAILED(hr)) {
+ LOG(WARNING) << "Executor failed to get tab info." << com::LogHr(hr);
+ PostError(api_module_constants::kInternalErrorError);
+ return false;
+ }
+
+ scoped_ptr<DictionaryValue> result(new DictionaryValue());
+ result->SetInteger(ext::kIdKey, tab_id);
+
+ // The window ID is just the window handle of the frame window, which is the
+ // top-level ancestor of this window.
+ HWND frame_window = window_utils::GetTopLevelParent(tab_window);
+ if (frame_window == tab_window ||
+ !window_utils::IsWindowClass(frame_window,
+ windows::kIeFrameWindowClass)) {
+ // If we couldn't get a valid parent frame window, then it must be because
+ // the frame window (and the tab then) has been closed by now or it lives
+ // under the hidden IE window.
+ DCHECK(!::IsWindow(tab_window) || window_utils::IsWindowClass(frame_window,
+ windows::kHiddenIeFrameWindowClass));
+ PostError(ExtensionErrorUtils::FormatErrorMessage(
+ ext::kTabNotFoundError, base::IntToString(tab_id)));
+ return false;
+ }
+ int frame_window_id = dispatcher->GetWindowIdFromHandle(frame_window);
+ result->SetInteger(ext::kWindowIdKey, frame_window_id);
+
+ // Only the currently selected tab has the VS_VISIBLE style.
+ result->SetBoolean(ext::kSelectedKey, TRUE == ::IsWindowVisible(tab_window));
+
+ result->SetString(ext::kUrlKey, com::ToString(tab_info.url));
+ result->SetString(ext::kTitleKey, com::ToString(tab_info.title));
+
+ std::string status = ext::kStatusValueComplete;
+ if (tab_info.status == kCeeeTabStatusLoading)
+ status = ext::kStatusValueLoading;
+ else
+ DCHECK(tab_info.status == kCeeeTabStatusComplete) << "New Status???";
+
+ result->SetString(ext::kStatusKey, status);
+
+ if (tab_info.fav_icon_url != NULL) {
+ result->SetString(ext::kFavIconUrlKey,
+ com::ToString(tab_info.fav_icon_url));
+ }
+
+ // When enumerating all tabs, we already have the index
+ // so we can save an IPC call.
+ if (index == -1) {
+ // We need another executor to get the index from the frame window thread.
+ CComPtr<ICeeeWindowExecutor> executor;
+ dispatcher->GetExecutor(frame_window, IID_ICeeeWindowExecutor,
+ reinterpret_cast<void**>(&executor));
+ if (executor == NULL) {
+ LOG(WARNING) << "Failed to get an executor to get tab index.";
+ PostError(api_module_constants::kInternalErrorError);
+ return false;
+ }
+
+ hr = executor->GetTabIndex(reinterpret_cast<CeeeWindowHandle>(tab_window),
+ &index);
+ if (FAILED(hr)) {
+ LOG(ERROR) << "Failed to get tab info for tab: " << std::hex << tab_id <<
+ ". " << com::LogHr(hr);
+ PostError(api_module_constants::kInternalErrorError);
+ return false;
+ }
+ }
+ result->SetInteger(ext::kIndexKey, static_cast<int>(index));
+
+ result->SetBoolean(ext::kIncognitoKey, ie_util::GetIEIsInPrivateBrowsing());
+
+ if (value_ == NULL) {
+ value_.reset(result.release());
+ } else {
+ DCHECK(value_->IsType(Value::TYPE_LIST));
+ ListValue* list = reinterpret_cast<ListValue*>(value_.get());
+ list->Append(result.release());
+ }
+ return true;
+}
+
+bool TabApiResult::IsTabFromSameOrUnspecifiedFrameWindow(
+ const DictionaryValue& input_dict,
+ const Value* saved_window_value,
+ HWND* tab_window,
+ ApiDispatcher* dispatcher) {
+ int tab_id = 0;
+ bool success = input_dict.GetInteger(ext::kIdKey, &tab_id);
+ DCHECK(success && tab_id != 0) << "The input_dict MUST have a tab ID!!!";
+ DCHECK(dispatcher != NULL);
+ HWND input_tab_window = dispatcher->GetTabHandleFromId(tab_id);
+ if (tab_window != NULL)
+ *tab_window = input_tab_window;
+
+ if (saved_window_value == NULL)
+ return true;
+
+ DCHECK(saved_window_value->IsType(Value::TYPE_INTEGER));
+ int saved_window_id = 0;
+ success = saved_window_value->GetAsInteger(&saved_window_id);
+ DCHECK(success && saved_window_id != 0);
+
+ HWND frame_window = NULL;
+ int frame_window_id = 0;
+ if (!input_dict.GetInteger(ext::kWindowIdKey, &frame_window_id)) {
+ // If the parent window is not specified, it is easy to fetch it ourselves.
+ frame_window = window_utils::GetTopLevelParent(input_tab_window);
+ frame_window_id = dispatcher->GetWindowIdFromHandle(frame_window);
+ DCHECK_NE(0, frame_window_id);
+ } else {
+ frame_window = dispatcher->GetWindowHandleFromId(frame_window_id);
+ DCHECK_EQ(window_utils::GetTopLevelParent(input_tab_window), frame_window);
+ }
+
+ return frame_window_id == saved_window_id;
+}
+
+bool GetIntegerFromValue(
+ const Value& value, const char* key_name, int* out_value) {
+ switch (value.GetType()) {
+ case Value::TYPE_INTEGER: {
+ return value.GetAsInteger(out_value);
+ }
+ case Value::TYPE_DICTIONARY: {
+ const DictionaryValue* dict = static_cast<const DictionaryValue*>(&value);
+ if (dict->HasKey(key_name))
+ return dict->GetInteger(key_name, out_value);
+ *out_value = 0;
+ return true;
+ }
+ case Value::TYPE_LIST: {
+ const ListValue* args_list = static_cast<const ListValue*>(&value);
+ Value* anonymous_value = NULL;
+ if (!args_list->Get(0, &anonymous_value)) {
+ // If given an empty list value, we return 0 so that the frame window is
+ // fetched.
+ *out_value = 0;
+ return true;
+ }
+ DCHECK(anonymous_value != NULL);
+ return GetIntegerFromValue(*anonymous_value, key_name, out_value);
+ }
+ case Value::TYPE_NULL: {
+ // If given an empty list value, we return 0 so that the frame window is
+ // fetched.
+ *out_value = 0;
+ return true;
+ }
+ default: {
+ return false;
+ }
+ }
+}
+
+HWND TabApiResult::GetSpecifiedOrCurrentFrameWindow(const Value& args,
+ bool* specified) {
+ int window_id = 0;
+ if (!GetIntegerFromValue(args, ext::kWindowIdKey, &window_id)) {
+ NOTREACHED() << "Invalid Arguments.";
+ return NULL;
+ }
+
+ HWND frame_window = NULL;
+ ApiDispatcher* dispatcher = GetDispatcher();
+ DCHECK(dispatcher != NULL);
+ if (window_id != 0)
+ frame_window = dispatcher->GetWindowHandleFromId(window_id);
+
+ if (!frame_window) {
+ // TODO(mad@chromium.org): We currently don't have access to the
+ // actual 'current' window from the point of view of the extension
+ // API caller. Use one of the top windows for now. bb2255140
+ window_utils::FindDescendentWindow(NULL, windows::kIeFrameWindowClass,
+ true, &frame_window);
+ if (specified != NULL)
+ *specified = false;
+ } else {
+ if (specified != NULL)
+ *specified = true;
+ }
+
+ if (!frame_window) {
+ return NULL;
+ }
+
+ if (!window_utils::IsWindowClass(frame_window, windows::kIeFrameWindowClass))
+ return NULL;
+
+ return frame_window;
+}
+
+void GetTab::Execute(const ListValue& args, int request_id) {
+ scoped_ptr<TabApiResult> result(CreateApiResult(request_id));
+ int tab_id = kInvalidChromeSessionId;
+ if (!args.GetInteger(0, &tab_id)) {
+ result->PostError(api_module_constants::kInvalidArgumentsError);
+ return;
+ }
+ ApiDispatcher* dispatcher = GetDispatcher();
+ DCHECK(dispatcher != NULL);
+
+ HWND tab_window = dispatcher->GetTabHandleFromId(tab_id);
+ if (!result->IsTabWindowClass(tab_window)) {
+ result->PostError(ExtensionErrorUtils::FormatErrorMessage(
+ ext::kTabNotFoundError, base::IntToString(tab_id)));
+ return;
+ }
+
+ // -1 when we don't know the index.
+ if (result->CreateTabValue(tab_id, -1)) {
+ // CreateTabValue called PostError if it returned false.
+ result->PostResult();
+ }
+}
+
+void GetSelectedTab::Execute(const ListValue& args, int request_id) {
+ scoped_ptr<TabApiResult> result(CreateApiResult(request_id));
+
+ bool specified = false;
+ HWND frame_window = result->GetSpecifiedOrCurrentFrameWindow(args,
+ &specified);
+ if (!frame_window) {
+ result->PostError(ext::kNoCurrentWindowError);
+ return;
+ }
+
+ // The selected tab is the only visible "TabWindowClass" window
+ // that is a child of the frame window. Enumerate child windows to find it,
+ // and fill in the value_ when we do.
+ HWND selected_tab = NULL;
+ ApiDispatcher* dispatcher = GetDispatcher();
+ DCHECK(dispatcher != NULL);
+ if (!window_utils::FindDescendentWindow(
+ frame_window, windows::kIeTabWindowClass, true, &selected_tab)) {
+ if (specified) {
+ int frame_window_id = dispatcher->GetWindowIdFromHandle(frame_window);
+ // We remember the frame window if it was specified so that we only
+ // react asynchronously to new tabs created in the same frame window.
+ result->SetValue(ext::kWindowIdKey,
+ Value::CreateIntegerValue(frame_window_id));
+ }
+ DCHECK(dispatcher != NULL);
+ dispatcher->RegisterEphemeralEventHandler(
+ ext_event_names::kOnTabCreated,
+ GetSelectedTab::ContinueExecution,
+ // We don't want to destroy the result in the scoped_ptr when we pass
+ // it as user_data to GetSelectedTab::ContinueExecution().
+ result.release());
+ } else {
+ int tab_id = dispatcher->GetTabIdFromHandle(selected_tab);
+ DCHECK(tab_id != kInvalidChromeSessionId);
+ if (result->CreateTabValue(tab_id, -1))
+ result->PostResult();
+ }
+}
+
+HRESULT GetSelectedTab::ContinueExecution(
+ const std::string& input_args,
+ ApiDispatcher::InvocationResult* user_data,
+ ApiDispatcher* dispatcher) {
+ DCHECK(dispatcher != NULL);
+ DCHECK(user_data != NULL);
+
+ // Any tab is good for us, so relaunch the search for a selected tab
+ // by using the frame window of the newly created tab.
+ scoped_ptr<TabApiResult> result(static_cast<TabApiResult*>(user_data));
+ scoped_ptr<ListValue> args_list;
+ DictionaryValue* input_dict =
+ api_module_util::GetListAndDictionaryValue(input_args, &args_list);
+ if (input_dict == NULL) {
+ DCHECK(false) << "Event arguments are not a list with a dictionary in it.";
+ result->PostError(api_module_constants::kInternalErrorError);
+ return E_INVALIDARG;
+ }
+
+ HWND tab_window = NULL;
+ if (!TabApiResult::IsTabFromSameOrUnspecifiedFrameWindow(*input_dict,
+ result->GetValue(ext::kWindowIdKey), &tab_window, dispatcher)) {
+ // These are not the droids you are looking for. :-)
+ result.release(); // The ApiDispatcher will keep it alive.
+ return S_FALSE;
+ }
+
+ // We must reset the value and start from scratch in CreateTabValue.
+ // TODO(mad@chromium.org): We might be able to save a few steps if
+ // we support adding to existing value... Maybe...
+ int tab_id = dispatcher->GetTabIdFromHandle(tab_window);
+ DCHECK(tab_id != kInvalidChromeSessionId);
+ result->set_value(NULL);
+ if (result->CreateTabValue(tab_id, -1))
+ result->PostResult();
+ return S_OK;
+}
+
+bool GetAllTabsInWindowResult::Execute(BSTR tab_handles) {
+ // This is a list of tab_handles as it comes from the executor, not Chrome.
+ DCHECK(tab_handles);
+ scoped_ptr<ListValue> tabs_list;
+ if (!api_module_util::GetListFromJsonString(CW2A(tab_handles).m_psz,
+ &tabs_list)) {
+ NOTREACHED() << "Invalid tabs list BSTR: " << tab_handles;
+ PostError(api_module_constants::kInternalErrorError);
+ return false;
+ }
+
+ size_t num_values = tabs_list->GetSize();
+ if (num_values % 2 != 0) {
+ // Values should come in pairs, one for the id and another one for the
+ // index.
+ NOTREACHED() << "Invalid tabs list BSTR: " << tab_handles;
+ PostError(api_module_constants::kInternalErrorError);
+ return false;
+ }
+
+ ApiDispatcher* dispatcher = GetDispatcher();
+ DCHECK(dispatcher != NULL);
+
+ // This will get populated by the calls to CreateTabValue in the loop below.
+ value_.reset(new ListValue());
+ num_values /= 2;
+ for (size_t index = 0; index < num_values; ++index) {
+ int tab_handle = 0;
+ tabs_list->GetInteger(index * 2, &tab_handle);
+ int tab_index = -1;
+ tabs_list->GetInteger(index * 2 + 1, &tab_index);
+ int tab_id = dispatcher->GetTabIdFromHandle(
+ reinterpret_cast<HWND>(tab_handle));
+ DCHECK(tab_id != kInvalidChromeSessionId);
+ if (!CreateTabValue(tab_id, tab_index)) {
+ return false;
+ }
+ }
+ return true;
+}
+
+void GetAllTabsInWindow::Execute(const ListValue& args, int request_id) {
+ scoped_ptr<GetAllTabsInWindowResult> result(CreateApiResult(request_id));
+ HWND frame_window = result->GetSpecifiedOrCurrentFrameWindow(args, NULL);
+ if (!frame_window) {
+ result->PostError(ext::kNoCurrentWindowError);
+ return;
+ }
+
+ ApiDispatcher* dispatcher = GetDispatcher();
+ DCHECK(dispatcher != NULL);
+ CComPtr<ICeeeWindowExecutor> executor;
+ dispatcher->GetExecutor(frame_window, IID_ICeeeWindowExecutor,
+ reinterpret_cast<void**>(&executor));
+ if (executor == NULL) {
+ LOG(WARNING) << "Failed to get an executor to get list of tabs.";
+ result->PostError("Internal Error while getting all tabs in window.");
+ return;
+ }
+
+ long num_tabs = 0;
+ CComBSTR tab_handles;
+ HRESULT hr = executor->GetTabs(&tab_handles);
+ if (FAILED(hr)) {
+ DCHECK(tab_handles == NULL);
+ LOG(ERROR) << "Failed to get list of tabs for window: " << std::hex <<
+ frame_window << ". " << com::LogHr(hr);
+ result->PostError("Internal Error while getting all tabs in window.");
+ return;
+ }
+
+ // Execute posted an error if it returns false.
+ if (result->Execute(tab_handles))
+ result->PostResult();
+}
+
+void UpdateTab::Execute(const ListValue& args, int request_id) {
+ scoped_ptr<TabApiResult> result(CreateApiResult(request_id));
+ int tab_id = 0;
+ if (!args.GetInteger(0, &tab_id)) {
+ result->PostError(api_module_constants::kInvalidArgumentsError);
+ return;
+ }
+
+ ApiDispatcher* dispatcher = GetDispatcher();
+ DCHECK(dispatcher != NULL);
+ HWND tab_window = dispatcher->GetTabHandleFromId(tab_id);
+ if (!result->IsTabWindowClass(tab_window)) {
+ result->PostError(ExtensionErrorUtils::FormatErrorMessage(
+ ext::kTabNotFoundError, base::IntToString(tab_id)));
+ return;
+ }
+
+ if (window_utils::WindowHasNoThread(tab_window)) {
+ result->PostError(ExtensionErrorUtils::FormatErrorMessage(
+ ext::kTabNotFoundError, base::IntToString(tab_id)));
+ return;
+ }
+
+ DictionaryValue* update_props = NULL;
+ if (!args.GetDictionary(1, &update_props)) {
+ result->PostError(api_module_constants::kInvalidArgumentsError);
+ return;
+ }
+
+ if (update_props->HasKey(ext::kUrlKey)) {
+ std::wstring url;
+ if (!update_props->GetString(ext::kUrlKey, &url)) {
+ result->PostError(api_module_constants::kInvalidArgumentsError);
+ return;
+ }
+
+ CComPtr<ICeeeTabExecutor> executor;
+ dispatcher->GetExecutor(tab_window, IID_ICeeeTabExecutor,
+ reinterpret_cast<void**>(&executor));
+ if (executor == NULL) {
+ LOG(WARNING) << "Failed to get an executor to navigate tab.";
+ result->PostError("Internal error trying to update tab.");
+ return;
+ }
+ HRESULT hr = executor->Navigate(CComBSTR(url.c_str()), 0,
+ CComBSTR(L"_top"));
+ // Don't DCHECK here, see the comment at the bottom of
+ // CeeeExecutor::Navigate().
+ if (FAILED(hr)) {
+ LOG(ERROR) << "Failed to navigate tab: " << std::hex << tab_id <<
+ " to " << url << ". " << com::LogHr(hr);
+ result->PostError("Internal error trying to update tab.");
+ return;
+ }
+ }
+
+ if (update_props->HasKey(ext::kSelectedKey)) {
+ bool selected = false;
+ if (!update_props->GetBoolean(ext::kSelectedKey, &selected)) {
+ result->PostError(api_module_constants::kInvalidArgumentsError);
+ return;
+ }
+
+ // We only take action if the user wants to select the tab; this function
+ // does not actually let you deselect a tab.
+ if (selected) {
+ CComPtr<ICeeeWindowExecutor> frame_executor;
+ dispatcher->GetExecutor(window_utils::GetTopLevelParent(tab_window),
+ IID_ICeeeWindowExecutor,
+ reinterpret_cast<void**>(&frame_executor));
+ if (frame_executor == NULL) {
+ LOG(WARNING) << "Failed to get a frame executor to select tab.";
+ result->PostError("Internal error trying to select tab.");
+ return;
+ }
+ HRESULT hr = frame_executor->SelectTab(
+ reinterpret_cast<CeeeWindowHandle>(tab_window));
+ if (FAILED(hr)) {
+ LOG(ERROR) << "Failed to select tab: " << std::hex << tab_id << ". " <<
+ com::LogHr(hr);
+ result->PostError("Internal error trying to select tab.");
+ return;
+ }
+ }
+ }
+ // TODO(mad@chromium.org): Check if we need to wait for the
+ // tabs.onUpdated event to make sure that the update was fully
+ // completed (e.g., Navigate above is async).
+ if (result->CreateTabValue(tab_id, -1))
+ result->PostResult();
+}
+
+void RemoveTab::Execute(const ListValue& args, int request_id) {
+ scoped_ptr<TabApiResult> result(CreateApiResult(request_id));
+ int tab_id;
+ if (!args.GetInteger(0, &tab_id)) {
+ result->PostError(api_module_constants::kInvalidArgumentsError);
+ return;
+ }
+
+ ApiDispatcher* dispatcher = GetDispatcher();
+ DCHECK(dispatcher != NULL);
+ HWND tab_window = dispatcher->GetTabHandleFromId(tab_id);
+ if (!result->IsTabWindowClass(tab_window)) {
+ result->PostError(ExtensionErrorUtils::FormatErrorMessage(
+ ext::kTabNotFoundError, base::IntToString(tab_id)));
+ return;
+ }
+
+ CComPtr<ICeeeWindowExecutor> frame_executor;
+ dispatcher->GetExecutor(window_utils::GetTopLevelParent(tab_window),
+ IID_ICeeeWindowExecutor,
+ reinterpret_cast<void**>(&frame_executor));
+ if (frame_executor == NULL) {
+ LOG(WARNING) << "Failed to get a frame executor to select tab.";
+ result->PostError("Internal error trying to select tab.");
+ return;
+ }
+ HRESULT hr = frame_executor->RemoveTab(
+ reinterpret_cast<CeeeWindowHandle>(tab_window));
+ if (FAILED(hr)) {
+ LOG(ERROR) << "Failed to remove tab: " << std::hex << tab_id << ". " <<
+ com::LogHr(hr);
+ result->PostError("Internal error trying to remove tab.");
+ return;
+ }
+
+ // Now we must wait for the tab removal to be completely done before
+ // posting the response back to Chrome Frame.
+ // And we remember the tab identifier so that we can recognize the event.
+ result->SetValue(ext::kTabIdKey, Value::CreateIntegerValue(tab_id));
+ dispatcher->RegisterEphemeralEventHandler(ext_event_names::kOnTabRemoved,
+ RemoveTab::ContinueExecution,
+ result.release());
+}
+
+HRESULT RemoveTab::ContinueExecution(const std::string& input_args,
+ ApiDispatcher::InvocationResult* user_data,
+ ApiDispatcher* dispatcher) {
+ DCHECK(user_data != NULL);
+ DCHECK(dispatcher != NULL);
+
+ scoped_ptr<TabApiResult> result(static_cast<TabApiResult*>(user_data));
+
+ scoped_ptr<ListValue> args_list;
+ int tab_id = 0;
+ if (!api_module_util::GetListAndIntegerValue(input_args, &args_list,
+ &tab_id)) {
+ NOTREACHED() << "Event arguments are not a list with an integer in it.";
+ result->PostError(api_module_constants::kInternalErrorError);
+ return E_INVALIDARG;
+ }
+
+ const Value* saved_tab_value = result->GetValue(ext::kTabIdKey);
+ DCHECK(saved_tab_value != NULL &&
+ saved_tab_value->IsType(Value::TYPE_INTEGER));
+ int saved_tab_id = 0;
+ bool success = saved_tab_value->GetAsInteger(&saved_tab_id);
+ DCHECK(success && saved_tab_id != 0);
+ if (saved_tab_id == tab_id) {
+ // The tabs.remove callback doesn't have any arguments.
+ result->set_value(NULL);
+ result->PostResult();
+ return S_OK;
+ } else {
+ // release doesn't destroy result, we need to keep it for next try.
+ result.release();
+ return S_FALSE; // S_FALSE keeps us in the queue.
+ }
+}
+
+void CreateTab::Execute(const ListValue& args, int request_id) {
+ // TODO(joi@chromium.org) Handle setting remaining tab properties
+ // ('title' and 'favIconUrl') if/when CE adds them (this is per a
+ // TODO for rafaelw@chromium.org in the extensions code).
+ scoped_ptr<TabApiResult> result(CreateApiResult(request_id));
+ DictionaryValue* input_dict = NULL;
+ if (!args.GetDictionary(0, &input_dict)) {
+ result->PostError(api_module_constants::kInvalidArgumentsError);
+ return;
+ }
+ bool specified = false;
+ HWND frame_window = result->GetSpecifiedOrCurrentFrameWindow(*input_dict,
+ &specified);
+ if (!frame_window) {
+ result->PostError(ext::kNoCurrentWindowError);
+ return;
+ }
+
+ ApiDispatcher* dispatcher = GetDispatcher();
+ DCHECK(dispatcher != NULL);
+ // In case the frame window wasn't specified, we must remember it for later
+ // use when we react to events below.
+ if (specified) {
+ int frame_window_id = dispatcher->GetWindowIdFromHandle(frame_window);
+ result->SetValue(
+ ext::kWindowIdKey, Value::CreateIntegerValue(frame_window_id));
+ }
+
+ std::string url_string(chrome::kAboutBlankURL); // default if no URL provided
+ if (input_dict->HasKey(ext::kUrlKey)) {
+ if (!input_dict->GetString(ext::kUrlKey, &url_string)) {
+ result->PostError(api_module_constants::kInvalidArgumentsError);
+ return;
+ }
+
+ GURL url(url_string);
+ if (!url.is_valid()) {
+ // TODO(joi@chromium.org) See if we can support absolute paths in IE (see
+ // extension_tabs_module.cc, AbsolutePath function and its uses)
+ result->PostError(ExtensionErrorUtils::FormatErrorMessage(
+ ext::kInvalidUrlError, url_string));
+ return;
+ }
+ // Remember the URL, we will use it to recognize the event below.
+ result->SetValue(ext::kUrlKey, Value::CreateStringValue(url_string));
+ }
+
+ bool selected = true;
+ if (input_dict->HasKey(ext::kSelectedKey)) {
+ if (!input_dict->GetBoolean(ext::kSelectedKey, &selected)) {
+ result->PostError(api_module_constants::kInvalidArgumentsError);
+ return;
+ }
+ }
+
+ if (input_dict->HasKey(ext::kIndexKey)) {
+ int index = -1;
+ if (!input_dict->GetInteger(ext::kIndexKey, &index)) {
+ result->PostError(api_module_constants::kInvalidArgumentsError);
+ return;
+ }
+ result->SetValue(ext::kIndexKey, Value::CreateIntegerValue(index));
+ }
+
+ // We will have some work pending, even after we completed the tab creation,
+ // because the tab creation itself is asynchronous and we must wait for it
+ // to complete before we can post the complete result.
+
+ // UNFORTUNATELY, this scheme doesn't work in protected mode for some reason.
+ // So bb2284073 & bb2492252 might still occur there.
+ std::wstring url_wstring = UTF8ToWide(url_string);
+ if (base::win::GetVersion() < base::win::VERSION_VISTA) {
+ CComPtr<IWebBrowser2> browser;
+ HRESULT hr = ie_util::GetWebBrowserForTopLevelIeHwnd(
+ frame_window, NULL, &browser);
+ DCHECK(SUCCEEDED(hr)) << "Can't get the browser for window: " <<
+ frame_window;
+ if (FAILED(hr)) {
+ result->PostError(api_module_constants::kInternalErrorError);
+ return;
+ }
+
+ long flags = selected ? navOpenInNewTab : navOpenInBackgroundTab;
+ hr = browser->Navigate(CComBSTR(url_wstring.c_str()),
+ &CComVariant(flags),
+ &CComVariant(L"_blank"),
+ &CComVariant(), // Post data
+ &CComVariant()); // Headers
+ DCHECK(SUCCEEDED(hr)) << "Failed to create tab. " << com::LogHr(hr);
+ if (FAILED(hr)) {
+ result->PostError("Internal error while trying to create tab.");
+ return;
+ }
+ } else {
+ // To create a new tab, we find an existing tab in the desired window (there
+ // is always at least one), and use it to navigate to a new tab.
+ HWND existing_tab = NULL;
+ bool success = window_utils::FindDescendentWindow(
+ frame_window, windows::kIeTabWindowClass, false, &existing_tab);
+ DCHECK(success && existing_tab != NULL) <<
+ "Can't find an existing tab for" << frame_window;
+
+ CComPtr<ICeeeTabExecutor> executor;
+ dispatcher->GetExecutor(existing_tab, IID_ICeeeTabExecutor,
+ reinterpret_cast<void**>(&executor));
+ if (executor == NULL) {
+ LOG(WARNING) << "Failed to get an executor to create a tab.";
+ result->PostError("Internal error while trying to create tab.");
+ return;
+ }
+
+ long flags = selected ? navOpenInNewTab : navOpenInBackgroundTab;
+ HRESULT hr = executor->Navigate(CComBSTR(url_wstring.c_str()),
+ flags, CComBSTR(L"_blank"));
+ // We can DCHECK here because navigating to a new tab shouldn't fail as
+ // described in the comment at the bottom of CeeeExecutor::Navigate().
+ DCHECK(SUCCEEDED(hr)) << "Failed to create tab. " << com::LogHr(hr);
+ if (FAILED(hr)) {
+ result->PostError("Internal error while trying to create tab.");
+ return;
+ }
+ }
+
+ // And now we must wait for the new tab to be created before we can respond.
+ dispatcher->RegisterEphemeralEventHandler(
+ ext_event_names::kOnTabCreated,
+ CreateTab::ContinueExecution,
+ // We don't want to destroy the result in the scoped_ptr when we pass
+ // it as user_data to CreateTab::ContinueExecution().
+ result.release());
+}
+
+HRESULT CreateTab::ContinueExecution(const std::string& input_args,
+ ApiDispatcher::InvocationResult* user_data,
+ ApiDispatcher* dispatcher) {
+ DCHECK(user_data != NULL);
+ DCHECK(dispatcher != NULL);
+
+ scoped_ptr<TabApiResult> result(static_cast<TabApiResult*>(user_data));
+ // Check if it has been created with the same info we were created for.
+ scoped_ptr<ListValue> args_list;
+ DictionaryValue* input_dict =
+ api_module_util::GetListAndDictionaryValue(input_args, &args_list);
+ if (input_dict == NULL) {
+ DCHECK(false) << "Event arguments are not a list with a dictionary in it.";
+ result->PostError(api_module_constants::kInvalidArgumentsError);
+ return E_INVALIDARG;
+ }
+
+ HWND tab_window = NULL;
+ if (!TabApiResult::IsTabFromSameOrUnspecifiedFrameWindow(*input_dict,
+ result->GetValue(ext::kWindowIdKey), &tab_window, dispatcher)) {
+ // These are not the droids you are looking for. :-)
+ result.release(); // The ApiDispatcher will keep it alive.
+ return S_FALSE;
+ }
+
+ std::string event_url;
+ bool success = input_dict->GetString(ext::kUrlKey, &event_url);
+ DCHECK(success) << "The event MUST send a URL!!!";
+ // if we didn't specify a URL, we should have navigated to about blank.
+ std::string requested_url(chrome::kAboutBlankURL);
+ // Ignore failures here, we fall back to the default about blank.
+ const Value* url_value = result->GetValue(ext::kUrlKey);
+ DCHECK(url_value != NULL && url_value->IsType(Value::TYPE_STRING));
+ if (url_value != NULL && url_value->IsType(Value::TYPE_STRING)) {
+ bool success = url_value->GetAsString(&requested_url);
+ DCHECK(success) << "url_value->GetAsString() Failed!";
+ }
+
+ if (GURL(event_url) != GURL(requested_url)) {
+ result.release(); // The ApiDispatcher will keep it alive.
+ return S_FALSE;
+ }
+
+ // We can't rely on selected, since it may have changed if
+ // another tab creation was made before we got to broadcast the completion
+ // of this one, so we will assume this one is ours.
+
+ // Now move the tab to desired index if specified, we couldn't do it until we
+ // had a tab_id.
+ long destination_index = -1;
+ const Value* index_value = result->GetValue(ext::kIndexKey);
+ if (index_value != NULL) {
+ DCHECK(index_value->IsType(Value::TYPE_INTEGER));
+ int destination_index_int = -1;
+ bool success = index_value->GetAsInteger(&destination_index_int);
+ DCHECK(success) << "index_value->GetAsInteger()";
+
+ HWND frame_window = window_utils::GetTopLevelParent(tab_window);
+ CComPtr<ICeeeWindowExecutor> frame_executor;
+ dispatcher->GetExecutor(frame_window, __uuidof(ICeeeWindowExecutor),
+ reinterpret_cast<void**>(&frame_executor));
+ if (frame_executor == NULL) {
+ LOG(WARNING) << "Failed to get an executor for the frame.";
+ result->PostError("Internal error while trying to move created tab.");
+ return E_UNEXPECTED;
+ }
+
+ destination_index = static_cast<long>(destination_index_int);
+ HRESULT hr = frame_executor->MoveTab(
+ reinterpret_cast<CeeeWindowHandle>(tab_window), destination_index);
+ if (FAILED(hr)) {
+ LOG(ERROR) << "Failed to move tab: " << std::hex << tab_window << ". " <<
+ com::LogHr(hr);
+ result->PostError("Internal error while trying to move created tab.");
+ return E_UNEXPECTED;
+ }
+ }
+
+ // We must reset current state before calling CreateTabValue.
+ result->set_value(NULL);
+ // TODO(mad@chromium.org): Do we need to go through CreateTabValue?
+ // Maybe we already have enough info available to create the
+ // response???
+ int tab_id = dispatcher->GetTabIdFromHandle(tab_window);
+ DCHECK(tab_id != kInvalidChromeSessionId);
+ if (result->CreateTabValue(tab_id, destination_index))
+ result->PostResult();
+ return S_OK;
+}
+
+bool CreateTab::EventHandler(const std::string& input_args,
+ std::string* converted_args,
+ ApiDispatcher* dispatcher) {
+ DCHECK(converted_args);
+ *converted_args = input_args;
+
+ scoped_ptr<ListValue> input_list;
+ DictionaryValue* input_dict =
+ api_module_util::GetListAndDictionaryValue(input_args, &input_list);
+ if (input_dict == NULL) {
+ DCHECK(false) << "Input arguments are not a list with a dictionary in it.";
+ return false;
+ }
+
+ // Check if we got the index, this would mean we already have all we need.
+ int int_value = -1;
+ if (input_dict->GetInteger(ext::kIndexKey, &int_value)) {
+ // We should also have all other non-optional values
+ DCHECK(input_dict->GetInteger(ext::kWindowIdKey, &int_value));
+ bool bool_value = false;
+ DCHECK(input_dict->GetBoolean(ext::kSelectedKey, &bool_value));
+ return false;
+ }
+
+ // Get the complete tab info from the tab_handle coming from IE
+ int tab_handle = reinterpret_cast<int>(INVALID_HANDLE_VALUE);
+ bool success = input_dict->GetInteger(ext::kIdKey, &tab_handle);
+ DCHECK(success) << "Couldn't get the tab ID key from the input args.";
+ int tab_id = dispatcher->GetTabIdFromHandle(
+ reinterpret_cast<HWND>(tab_handle));
+ DCHECK(tab_id != kInvalidChromeSessionId);
+
+ TabApiResult result(TabApiResult::kNoRequestId);
+ if (result.CreateTabValue(tab_id, -1)) {
+ input_list->Set(0, result.value()->DeepCopy());
+ base::JSONWriter::Write(input_list.get(), false, converted_args);
+ return true;
+ } else {
+ // Don't DCHECK, this can happen if we close the window while tabs are
+ // being created.
+ // TODO(mad@chromium.org): Find a way to DCHECK that the window is
+ // actually closing.
+ return false;
+ }
+}
+
+void MoveTab::Execute(const ListValue& args, int request_id) {
+ scoped_ptr<TabApiResult> result(CreateApiResult(request_id));
+ int tab_id = 0;
+ if (!args.GetInteger(0, &tab_id)) {
+ result->PostError(api_module_constants::kInvalidArgumentsError);
+ return;
+ }
+
+ ApiDispatcher* dispatcher = GetDispatcher();
+ DCHECK(dispatcher != NULL);
+ HWND tab_window = dispatcher->GetTabHandleFromId(tab_id);
+ if (!result->IsTabWindowClass(tab_window)) {
+ result->PostError(ExtensionErrorUtils::FormatErrorMessage(
+ ext::kTabNotFoundError, base::IntToString(tab_id)));
+ return;
+ }
+
+ DictionaryValue* update_props = NULL;
+ if (!args.GetDictionary(1, &update_props)) {
+ NOTREACHED() << "Can't get update properties from dictionary argument";
+ result->PostError(api_module_constants::kInvalidArgumentsError);
+ return;
+ }
+
+ if (update_props->HasKey(ext::kWindowIdKey)) {
+ // TODO(joi@chromium.org) Move to shared constants file
+ result->PostError("Moving tabs between windows is not supported.");
+ return;
+ }
+
+ int new_index = -1;
+ if (!update_props->GetInteger(ext::kIndexKey, &new_index)) {
+ NOTREACHED() << "Can't get tab index from update properties.";
+ result->PostError(api_module_constants::kInvalidArgumentsError);
+ return;
+ }
+
+ HWND frame_window = window_utils::GetTopLevelParent(tab_window);
+
+ CComPtr<ICeeeWindowExecutor> frame_executor;
+ dispatcher->GetExecutor(frame_window, IID_ICeeeWindowExecutor,
+ reinterpret_cast<void**>(&frame_executor));
+ if (frame_executor == NULL) {
+ LOG(WARNING) << "Failed to get an executor for the frame.";
+ result->PostError("Internal Error while trying to move tab.");
+ return;
+ }
+
+ HRESULT hr = frame_executor->MoveTab(
+ reinterpret_cast<CeeeWindowHandle>(tab_window), new_index);
+ if (FAILED(hr)) {
+ LOG(ERROR) << "Failed to move tab: " << std::hex << tab_id << ". " <<
+ com::LogHr(hr);
+ result->PostError("Internal Error while trying to move tab.");
+ return;
+ }
+ if (result->CreateTabValue(tab_id, new_index))
+ result->PostResult();
+}
+
+ApiDispatcher::InvocationResult* TabsInsertCode::ExecuteImpl(
+ const ListValue& args,
+ int request_id,
+ CeeeTabCodeType type,
+ int* tab_id,
+ HRESULT* hr) {
+ scoped_ptr<ApiDispatcher::InvocationResult> result(
+ CreateApiResult(request_id));
+ // TODO(ericdingle@chromium.org): This needs to support when NULL is
+ // sent in as the first parameter.
+ if (!args.GetInteger(0, tab_id)) {
+ result->PostError(api_module_constants::kInvalidArgumentsError);
+ return NULL;
+ }
+
+ DictionaryValue* dict;
+ if (!args.GetDictionary(1, &dict)) {
+ result->PostError(api_module_constants::kInvalidArgumentsError);
+ return NULL;
+ }
+
+ // The dictionary should have either a code property or a file property,
+ // but not both.
+ std::string code;
+ std::string file;
+ if (dict->HasKey(ext::kCodeKey) && dict->HasKey(ext::kFileKey)) {
+ result->PostError(ext::kMoreThanOneValuesError);
+ return NULL;
+ } else if (dict->HasKey(ext::kCodeKey)) {
+ dict->GetString(ext::kCodeKey, &code);
+ } else if (dict->HasKey(ext::kFileKey)) {
+ dict->GetString(ext::kFileKey, &file);
+ } else {
+ result->PostError(ext::kNoCodeOrFileToExecuteError);
+ return NULL;
+ }
+
+ // All frames is optional. If not specified, the default value is false.
+ bool all_frames;
+ if (!dict->GetBoolean(ext::kAllFramesKey, &all_frames))
+ all_frames = false;
+
+ ApiDispatcher* dispatcher = GetDispatcher();
+ DCHECK(dispatcher != NULL);
+ HWND tab_window = dispatcher->GetTabHandleFromId(*tab_id);
+ if (!TabApiResult::IsTabWindowClass(tab_window)) {
+ result->PostError(ExtensionErrorUtils::FormatErrorMessage(
+ ext::kTabNotFoundError, base::IntToString(*tab_id)));
+ return NULL;
+ }
+
+ CComPtr<ICeeeTabExecutor> tab_executor;
+ dispatcher->GetExecutor(tab_window, IID_ICeeeTabExecutor,
+ reinterpret_cast<void**>(&tab_executor));
+ if (tab_executor == NULL) {
+ LOG(WARNING) << "Failed to get an executor for the frame.";
+ result->PostError("Internal Error while trying to insert code in tab.");
+ return NULL;
+ }
+
+ *hr = tab_executor->InsertCode(CComBSTR(code.c_str()),
+ CComBSTR(file.c_str()),
+ all_frames,
+ type);
+ return result.release();
+}
+
+void TabsExecuteScript::Execute(const ListValue& args, int request_id) {
+ int tab_id;
+ HRESULT hr = S_OK;
+ scoped_ptr<ApiDispatcher::InvocationResult> result(
+ TabsInsertCode::ExecuteImpl(
+ args, request_id, kCeeeTabCodeTypeJs, &tab_id, &hr));
+ if (result.get() == NULL)
+ return;
+
+ if (FAILED(hr)) {
+ LOG(ERROR) << "Failed to execute script in tab: " <<
+ std::hex <<
+ tab_id <<
+ ". " <<
+ com::LogHr(hr);
+ result->PostError("Internal Error while trying to execute script in tab.");
+ } else {
+ result->PostResult();
+ }
+}
+
+void TabsInsertCSS::Execute(const ListValue& args, int request_id) {
+ int tab_id;
+ HRESULT hr = S_OK;
+ scoped_ptr<ApiDispatcher::InvocationResult> result(
+ TabsInsertCode::ExecuteImpl(
+ args, request_id, kCeeeTabCodeTypeCss, &tab_id, &hr));
+ if (result.get() == NULL)
+ return;
+
+ if (FAILED(hr)) {
+ LOG(ERROR) << "Failed to insert CSS in tab: " <<
+ std::hex <<
+ tab_id <<
+ ". " <<
+ com::LogHr(hr);
+ result->PostError("Internal Error while trying to insert CSS in tab.");
+ } else {
+ result->PostResult();
+ }
+}
+
+TabInfo::TabInfo() {
+ url = NULL;
+ title = NULL;
+ status = kCeeeTabStatusLoading;
+ fav_icon_url = NULL;
+}
+
+TabInfo::~TabInfo() {
+ Clear();
+}
+
+void TabInfo::Clear() {
+ // SysFreeString accepts NULL pointers.
+ ::SysFreeString(url);
+ url = NULL;
+ ::SysFreeString(title);
+ title = NULL;
+ ::SysFreeString(fav_icon_url);
+ fav_icon_url = NULL;
+
+ status = kCeeeTabStatusLoading;
+}
+
+} // namespace tab_api
diff --git a/ceee/ie/broker/tab_api_module.h b/ceee/ie/broker/tab_api_module.h
new file mode 100644
index 0000000..50c0f69
--- /dev/null
+++ b/ceee/ie/broker/tab_api_module.h
@@ -0,0 +1,148 @@
+// 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.
+//
+// Tab API implementation.
+
+#ifndef CEEE_IE_BROKER_TAB_API_MODULE_H_
+#define CEEE_IE_BROKER_TAB_API_MODULE_H_
+
+#include <string>
+
+#include "ceee/ie/broker/api_dispatcher.h"
+#include "ceee/ie/broker/common_api_module.h"
+
+#include "toolband.h" //NOLINT
+
+namespace tab_api {
+
+class TabApiResult;
+typedef ApiResultCreator<TabApiResult> TabApiResultCreator;
+
+// Registers all Tab API Invocations with the given dispatcher.
+void RegisterInvocations(ApiDispatcher* dispatcher);
+
+class TabApiResult : public common_api::CommonApiResult {
+ public:
+ explicit TabApiResult(int request_id)
+ : common_api::CommonApiResult(request_id) {
+ }
+
+ // Retrieves the frame window to use from the arguments provided, or the
+ // current frame window if none was specified in arguments.
+ // Will set @p specified to true if the window was specified and @p specified
+ // isn't NULL. It can be NULL if caller doesn't need to know.
+ virtual HWND GetSpecifiedOrCurrentFrameWindow(const Value& args,
+ bool* specified);
+
+ // Creates a value object with the information for a tab, as specified
+ // by the API definition.
+ // @param tab_id The ID of the tab we want the create a value for.
+ // @param index The index of the tab if already known, -1 otherwise.
+ // @return true for success, false for failure and will also call
+ // ApiDispatcher::InvocationResult::PostError on failures.
+ virtual bool CreateTabValue(int tab_id, long index);
+
+ // Check if saved_dict has a specified frame window and if so compares it
+ // to the frame window that is contained in the given input_dict or the
+ // grand parent of the tab window found in the input_dict and returned in
+ // tab_window.
+ static bool IsTabFromSameOrUnspecifiedFrameWindow(
+ const DictionaryValue& input_dict,
+ const Value* saved_window_value,
+ HWND* tab_window,
+ ApiDispatcher* dispatcher);
+};
+
+class GetTab : public TabApiResultCreator {
+ public:
+ virtual void Execute(const ListValue& args, int request_id);
+};
+
+class GetSelectedTab : public TabApiResultCreator {
+ public:
+ virtual void Execute(const ListValue& args, int request_id);
+ static HRESULT ContinueExecution(const std::string& input_args,
+ ApiDispatcher::InvocationResult* user_data,
+ ApiDispatcher* dispatcher);
+};
+
+class GetAllTabsInWindowResult : public TabApiResult {
+ public:
+ explicit GetAllTabsInWindowResult(int request_id) : TabApiResult(request_id) {
+ }
+
+ // Populates the result with all tabs in the given JSON encoded list.
+ virtual bool Execute(BSTR tab_handles);
+};
+
+class GetAllTabsInWindow
+ : public ApiResultCreator<GetAllTabsInWindowResult> {
+ public:
+ virtual void Execute(const ListValue& args, int request_id);
+};
+
+class UpdateTab : public TabApiResultCreator {
+ public:
+ virtual void Execute(const ListValue& args, int request_id);
+};
+
+class RemoveTab : public TabApiResultCreator {
+ public:
+ virtual void Execute(const ListValue& args, int request_id);
+ static HRESULT ContinueExecution(const std::string& input_args,
+ ApiDispatcher::InvocationResult* user_data,
+ ApiDispatcher* dispatcher);
+};
+
+class CreateTab : public TabApiResultCreator {
+ public:
+ virtual void Execute(const ListValue& args, int request_id);
+ // We need to wait for the new tab to complete the result response.
+ static HRESULT ContinueExecution(const std::string& input_args,
+ ApiDispatcher::InvocationResult* user_data,
+ ApiDispatcher* dispatcher);
+ // And we also have a PermanentEventHandler too...
+ // To properly convert and complete the event arguments.
+ static bool EventHandler(const std::string& input_args,
+ std::string* converted_args,
+ ApiDispatcher* dispatcher);
+};
+
+class MoveTab : public TabApiResultCreator {
+ public:
+ virtual void Execute(const ListValue& args, int request_id);
+};
+
+class TabsInsertCode : public ApiResultCreator<> {
+ protected:
+ // Will return NULL after calling PostError on the result upon failure.
+ ApiDispatcher::InvocationResult* ExecuteImpl(const ListValue& args,
+ int request_id,
+ CeeeTabCodeType type,
+ int* tab_id,
+ HRESULT* hr);
+};
+
+class TabsExecuteScript : public TabsInsertCode {
+ public:
+ virtual void Execute(const ListValue& args, int request_id);
+};
+
+class TabsInsertCSS : public TabsInsertCode {
+ public:
+ virtual void Execute(const ListValue& args, int request_id);
+};
+
+// Helper class to handle the data cleanup.
+class TabInfo : public CeeeTabInfo {
+ public:
+ TabInfo();
+ ~TabInfo();
+ // Useful for reuse in unit tests.
+ void Clear();
+};
+
+} // namespace tab_api
+
+#endif // CEEE_IE_BROKER_TAB_API_MODULE_H_
diff --git a/ceee/ie/broker/tab_api_module_unittest.cc b/ceee/ie/broker/tab_api_module_unittest.cc
new file mode 100644
index 0000000..8f1f4df
--- /dev/null
+++ b/ceee/ie/broker/tab_api_module_unittest.cc
@@ -0,0 +1,1452 @@
+// 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.
+//
+// Tab API implementation unit tests.
+
+// MockWin32 can't be included after ChromeFrameHost because of an include
+// incompatibility with atlwin.h.
+#include "ceee/testing/utils/mock_win32.h" // NOLINT
+
+#include "base/json/json_writer.h"
+#include "base/json/json_reader.h"
+#include "base/win/windows_version.h"
+#include "ceee/common/initializing_coclass.h"
+#include "ceee/ie/broker/api_dispatcher.h"
+#include "ceee/ie/broker/chrome_postman.h"
+#include "ceee/ie/broker/tab_api_module.h"
+#include "ceee/ie/common/ie_util.h"
+#include "ceee/ie/testing/mock_broker_and_friends.h"
+#include "ceee/testing/utils/instance_count_mixin.h"
+#include "ceee/testing/utils/mock_com.h"
+#include "ceee/testing/utils/mock_window_utils.h"
+#include "ceee/testing/utils/test_utils.h"
+#include "chrome/browser/extensions/extension_event_names.h"
+#include "chrome/browser/extensions/extension_tabs_module_constants.h"
+#include "chrome/common/extensions/extension_error_utils.h"
+#include "testing/gmock/include/gmock/gmock.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+namespace ext = extension_tabs_module_constants;
+namespace ext_event_names = extension_event_names;
+
+namespace {
+
+using tab_api::CreateTab;
+using tab_api::GetAllTabsInWindow;
+using tab_api::GetAllTabsInWindowResult;
+using tab_api::GetSelectedTab;
+using tab_api::GetTab;
+using tab_api::MoveTab;
+using tab_api::RegisterInvocations;
+using tab_api::RemoveTab;
+using tab_api::TabApiResult;
+using tab_api::TabsInsertCode;
+using tab_api::UpdateTab;
+
+using testing::_;
+using testing::AddRef;
+using testing::AtLeast;
+using testing::CopyInterfaceToArgument;
+using testing::CopyStringToArgument;
+using testing::Gt;
+using testing::InstanceCountMixin;
+using testing::MockApiDispatcher;
+using testing::MockApiInvocation;
+using testing::NotNull;
+using testing::Return;
+using testing::Sequence;
+using testing::SetArgumentPointee;
+using testing::StrEq;
+using testing::StrictMock;
+using testing::VariantPointerEq;
+
+const int kGoodTabWindowId = 99;
+const int kGoodFrameWindowId = 88;
+const int kBadWindowId = 666;
+
+const CeeeWindowHandle kGoodTabWindowHandle = kGoodTabWindowId + 1;
+const CeeeWindowHandle kGoodFrameWindowHandle = kGoodFrameWindowId + 1;
+const CeeeWindowHandle kBadWindowHandle = kBadWindowId + 1;
+
+const HWND kGoodTabWindow = reinterpret_cast<HWND>(kGoodTabWindowHandle);
+const HWND kGoodFrameWindow = reinterpret_cast<HWND>(kGoodFrameWindowHandle);
+const HWND kBadWindow = reinterpret_cast<HWND>(kBadWindowHandle);
+
+const int kTabIndex = 26;
+const wchar_t kClassName[] = L"TabWindowClass";
+const int kRequestId = 43;
+
+TEST(TabApi, RegisterInvocations) {
+ StrictMock<MockApiDispatcher> disp;
+ EXPECT_CALL(disp, RegisterInvocation(NotNull(), NotNull())).Times(AtLeast(7));
+ RegisterInvocations(&disp);
+}
+
+class MockTabApiResult : public TabApiResult {
+ public:
+ explicit MockTabApiResult(int request_id) : TabApiResult(request_id) {}
+ MOCK_METHOD2(GetSpecifiedOrCurrentFrameWindow, HWND(const Value&, bool*));
+ MOCK_METHOD2(CreateTabValue, bool(int, long));
+ MOCK_METHOD0(PostResult, void());
+ MOCK_METHOD1(PostError, void(const std::string&));
+
+ virtual ApiDispatcher* GetDispatcher() {
+ return &mock_api_dispatcher_;
+ }
+
+ // Need to overload to bypass the mock.
+ HWND CallGetSpecifiedOrCurrentFrameWindow(const Value& args,
+ bool* specified) {
+ return TabApiResult::GetSpecifiedOrCurrentFrameWindow(args, specified);
+ }
+ bool CallCreateTabValue(int tab_id, long index) {
+ return TabApiResult::CreateTabValue(tab_id, index);
+ }
+
+ StrictMock<MockApiDispatcher> mock_api_dispatcher_;
+};
+
+// TODO(mad@chromium.org) TODO(hansl@google.com): Unify tests between
+// {window,tab}_module_api. Create a base class for executor mocker
+// tests, since we now have two very similar classes. Consider adding
+// the cookie and infobar API tests too.
+class TabApiTests: public testing::Test {
+ public:
+ virtual void SetUp() {
+ EXPECT_HRESULT_SUCCEEDED(testing::MockTabExecutor::CreateInitialized(
+ &mock_tab_executor_, &mock_tab_executor_keeper_));
+ EXPECT_HRESULT_SUCCEEDED(testing::MockWindowExecutor::CreateInitialized(
+ &mock_window_executor_, &mock_window_executor_keeper_));
+ }
+
+ virtual void TearDown() {
+ // Everything should have been relinquished.
+ mock_window_executor_keeper_.Release();
+ mock_tab_executor_keeper_.Release();
+ ASSERT_EQ(0, testing::InstanceCountMixinBase::all_instance_count());
+ }
+
+ protected:
+ void AlwaysMockGetTabExecutor(MockApiDispatcher* api_dispatcher,
+ HWND window) {
+ // We can't use CopyInterfaceToArgument here because GetExecutor takes a
+ // void** argument instead of an interface.
+ EXPECT_CALL(*api_dispatcher, GetExecutor(window, _, NotNull())).
+ WillRepeatedly(DoAll(
+ SetArgumentPointee<2>(mock_tab_executor_keeper_.p),
+ AddRef(mock_tab_executor_keeper_.p)));
+ }
+
+ void MockGetTabExecutorOnce(MockApiDispatcher* api_dispatcher, HWND window) {
+ // We can't use CopyInterfaceToArgument here because GetExecutor takes a
+ // void** argument instead of an interface.
+ EXPECT_CALL(*api_dispatcher, GetExecutor(window, _, NotNull())).
+ WillOnce(DoAll(SetArgumentPointee<2>(mock_tab_executor_keeper_.p),
+ AddRef(mock_tab_executor_keeper_.p)));
+ }
+
+ void MockGetWindowExecutorOnce(MockApiDispatcher* api_dispatcher,
+ HWND window) {
+ EXPECT_CALL(*api_dispatcher, GetExecutor(window, _, NotNull())).
+ WillOnce(DoAll(
+ SetArgumentPointee<2>(mock_window_executor_keeper_.p),
+ AddRef(mock_window_executor_keeper_.p)));
+ }
+
+ void AlwaysMockGetWindowExecutor(MockApiDispatcher* api_dispatcher,
+ HWND window) {
+ EXPECT_CALL(*api_dispatcher, GetExecutor(window, _, NotNull())).
+ WillRepeatedly(DoAll(
+ SetArgumentPointee<2>(mock_window_executor_keeper_.p),
+ AddRef(mock_window_executor_keeper_.p)));
+ }
+
+ StrictMock<testing::MockUser32> user32_;
+ // The executor classes are already strict from their base class impl.
+ testing::MockTabExecutor* mock_tab_executor_;
+ testing::MockWindowExecutor* mock_window_executor_;
+
+ private:
+ class MockChromePostman : public ChromePostman {
+ public:
+ MOCK_METHOD2(PostMessage, void(BSTR, BSTR));
+ };
+ // We should never get to the postman, we mock all the calls getting there.
+ // So we simply instantiate it strict and it will register itself as the
+ // one and only singleton to use all the time.
+ CComObjectStackEx< StrictMock< MockChromePostman > > postman_;
+ // To control the life span of the tab executor.
+ CComPtr<ICeeeTabExecutor> mock_tab_executor_keeper_;
+ CComPtr<ICeeeWindowExecutor> mock_window_executor_keeper_;
+};
+
+TEST_F(TabApiTests, CreateTabValueErrorHandling) {
+ testing::LogDisabler no_dchecks;
+
+ // Window with no thread.
+ StrictMock<testing::MockWindowUtils> window_utils;
+ EXPECT_CALL(window_utils, WindowHasNoThread(kGoodTabWindow)).
+ WillOnce(Return(true));
+
+ StrictMock<MockTabApiResult> invocation_result(TabApiResult::kNoRequestId);
+ EXPECT_CALL(invocation_result.mock_api_dispatcher_,
+ GetTabHandleFromId(kGoodTabWindowId)).
+ WillRepeatedly(Return(kGoodTabWindow));
+ EXPECT_CALL(invocation_result.mock_api_dispatcher_,
+ GetWindowIdFromHandle(kGoodFrameWindow)).
+ WillRepeatedly(Return(kGoodFrameWindowId));
+
+ EXPECT_CALL(invocation_result, PostError(_)).Times(1);
+ EXPECT_FALSE(invocation_result.CallCreateTabValue(kGoodTabWindowId, -1));
+
+ // Not an IE Frame.
+ invocation_result.set_value(NULL);
+ EXPECT_CALL(window_utils, WindowHasNoThread(kGoodTabWindow)).
+ WillRepeatedly(Return(false));
+ EXPECT_CALL(window_utils, IsWindowClass(_, _)).WillOnce(Return(false));
+ EXPECT_CALL(invocation_result, PostError(_)).Times(1);
+ EXPECT_FALSE(invocation_result.CallCreateTabValue(kGoodTabWindowId, -1));
+
+ // No Executor for the tab window.
+ invocation_result.set_value(NULL);
+ EXPECT_CALL(window_utils, GetTopLevelParent(_)).
+ WillRepeatedly(Return(kGoodFrameWindow));
+ EXPECT_CALL(window_utils, IsWindowClass(_, _)).WillRepeatedly(Return(true));
+ EXPECT_CALL(user32_, IsWindowVisible(_)).WillRepeatedly(Return(FALSE));
+ EXPECT_CALL(invocation_result.mock_api_dispatcher_,
+ GetExecutor(kGoodTabWindow, _, _)).
+ WillOnce(SetArgumentPointee<2>(static_cast<void*>(NULL)));
+ EXPECT_CALL(invocation_result, PostError(_)).Times(1);
+ EXPECT_FALSE(invocation_result.CallCreateTabValue(kGoodTabWindowId, -1));
+
+ // Failing executor for the tab info.
+ invocation_result.set_value(NULL);
+ MockGetTabExecutorOnce(&invocation_result.mock_api_dispatcher_,
+ kGoodTabWindow);
+ EXPECT_CALL(*mock_tab_executor_, GetTabInfo(NotNull())).
+ WillOnce(Return(E_FAIL));
+ EXPECT_CALL(invocation_result, PostError(_)).Times(1);
+ EXPECT_FALSE(invocation_result.CallCreateTabValue(kGoodTabWindowId, -1));
+
+ // No Executor for the frame window.
+ // We only mock once at a time so that the executor for the tab doesn't get
+ // confused by the one for the frame window, or vice versa.
+ invocation_result.set_value(NULL);
+ MockGetTabExecutorOnce(&invocation_result.mock_api_dispatcher_,
+ kGoodTabWindow);
+ EXPECT_CALL(*mock_tab_executor_, GetTabInfo(NotNull())).
+ WillRepeatedly(Return(S_OK));
+ EXPECT_CALL(invocation_result.mock_api_dispatcher_,
+ GetExecutor(kGoodFrameWindow, _, NotNull())).
+ WillOnce(SetArgumentPointee<2>(static_cast<void*>(NULL)));
+ EXPECT_CALL(invocation_result, PostError(_)).Times(1);
+ EXPECT_FALSE(invocation_result.CallCreateTabValue(kGoodTabWindowId, -1));
+
+ // Failing executor for the tab index.
+ invocation_result.set_value(NULL);
+ AlwaysMockGetWindowExecutor(&invocation_result.mock_api_dispatcher_,
+ kGoodFrameWindow);
+ AlwaysMockGetTabExecutor(&invocation_result.mock_api_dispatcher_,
+ kGoodTabWindow);
+ EXPECT_CALL(*mock_window_executor_, GetTabIndex(kGoodTabWindowHandle,
+ NotNull())).WillOnce(Return(E_FAIL));
+ EXPECT_CALL(invocation_result, PostError(_)).Times(1);
+ EXPECT_FALSE(invocation_result.CallCreateTabValue(kGoodTabWindowId, -1));
+}
+
+TEST_F(TabApiTests, CreateTabValue) {
+ // Setup all we need from other for success.
+ testing::LogDisabler no_dchecks;
+ StrictMock<testing::MockWindowUtils> window_utils;
+ EXPECT_CALL(window_utils, WindowHasNoThread(kGoodTabWindow)).
+ WillRepeatedly(Return(false));
+ EXPECT_CALL(window_utils, IsWindowClass(_, _)).WillRepeatedly(Return(true));
+ EXPECT_CALL(window_utils, GetTopLevelParent(_)).
+ WillRepeatedly(Return(kGoodFrameWindow));
+
+ // Try a few different combination or parameters.
+ struct {
+ bool window_selected;
+ const char* status;
+ const char* fav_icon_url;
+ const char* title;
+ const char* url;
+ int index;
+ } tests[] = {
+ { false, ext::kStatusValueLoading, NULL, "Mine", "http://icietla", 1 },
+ { false, ext::kStatusValueComplete, "Here", "Yours", "https://secure", 32 },
+ { true, ext::kStatusValueLoading, "There", "There's", "Just a string", -1 },
+ { true, ext::kStatusValueComplete, NULL, "Unknown", "Boo!!!", -1 },
+ };
+
+ for (int i = 0; i < arraysize(tests); ++i) {
+ EXPECT_CALL(user32_, IsWindowVisible(_)).WillOnce(
+ Return(tests[i].window_selected ? TRUE : FALSE));
+ CeeeTabInfo tab_info;
+ // The allocation within the tab_info will be freed by the caller of
+ // GetTabInfo which we mock below...
+ tab_info.url = ::SysAllocString(CA2W(tests[i].url).m_psz);
+ tab_info.title = ::SysAllocString(CA2W(tests[i].title).m_psz);
+
+ if (strcmp(tests[i].status, ext::kStatusValueLoading) == 0)
+ tab_info.status = kCeeeTabStatusLoading;
+ else
+ tab_info.status = kCeeeTabStatusComplete;
+
+ if (tests[i].fav_icon_url) {
+ tab_info.fav_icon_url =
+ ::SysAllocString(CA2W(tests[i].fav_icon_url).m_psz);
+ } else {
+ tab_info.fav_icon_url = NULL;
+ }
+
+ StrictMock<MockTabApiResult> invocation_result(TabApiResult::kNoRequestId);
+ AlwaysMockGetTabExecutor(&invocation_result.mock_api_dispatcher_,
+ kGoodTabWindow);
+ AlwaysMockGetWindowExecutor(&invocation_result.mock_api_dispatcher_,
+ kGoodFrameWindow);
+ EXPECT_CALL(*mock_tab_executor_, GetTabInfo(NotNull())).
+ WillOnce(DoAll(SetArgumentPointee<0>(tab_info), Return(S_OK)));
+ EXPECT_CALL(invocation_result.mock_api_dispatcher_,
+ GetTabHandleFromId(kGoodTabWindowId)).WillOnce(Return(kGoodTabWindow));
+ EXPECT_CALL(invocation_result.mock_api_dispatcher_,
+ GetWindowIdFromHandle(kGoodFrameWindow)).
+ WillOnce(Return(kGoodFrameWindowId));
+
+ if (tests[i].index == -1) {
+ EXPECT_CALL(*mock_window_executor_, GetTabIndex(kGoodTabWindowHandle,
+ NotNull())).WillOnce(DoAll(SetArgumentPointee<1>(kTabIndex),
+ Return(S_OK)));
+ }
+ if (i % 2 == 0)
+ invocation_result.set_value(new ListValue);
+ EXPECT_TRUE(invocation_result.CallCreateTabValue(
+ kGoodTabWindowId, tests[i].index));
+ EXPECT_NE(static_cast<Value*>(NULL), invocation_result.value());
+ const Value* result = NULL;
+ if (i % 2 == 0) {
+ EXPECT_TRUE(invocation_result.value()->IsType(Value::TYPE_LIST));
+ const ListValue* list = static_cast<const ListValue*>(
+ invocation_result.value());
+ Value * non_const_result = NULL;
+ EXPECT_TRUE(list->Get(0, &non_const_result));
+ result = non_const_result;
+ } else {
+ result = invocation_result.value();
+ }
+ EXPECT_TRUE(result->IsType(Value::TYPE_DICTIONARY));
+ const DictionaryValue* dict = static_cast<const DictionaryValue*>(result);
+
+ int tab_id;
+ EXPECT_TRUE(dict->GetInteger(ext::kIdKey, &tab_id));
+ EXPECT_EQ(kGoodTabWindowId, tab_id);
+
+ int window_id;
+ EXPECT_TRUE(dict->GetInteger(ext::kWindowIdKey, &window_id));
+ EXPECT_EQ(kGoodFrameWindowId, window_id);
+
+ bool selected = false;
+ EXPECT_TRUE(dict->GetBoolean(ext::kSelectedKey, &selected));
+ EXPECT_EQ(tests[i].window_selected, selected);
+
+ std::string url;
+ EXPECT_TRUE(dict->GetString(ext::kUrlKey, &url));
+ EXPECT_STREQ(tests[i].url, url.c_str());
+
+ std::string title;
+ EXPECT_TRUE(dict->GetString(ext::kTitleKey, &title));
+ EXPECT_STREQ(tests[i].title, title.c_str());
+
+ std::string status;
+ EXPECT_TRUE(dict->GetString(ext::kStatusKey, &status));
+ EXPECT_STREQ(tests[i].status, status.c_str());
+
+ std::string fav_icon_url;
+ if (tests[i].fav_icon_url == NULL) {
+ EXPECT_FALSE(dict->GetString(ext::kFavIconUrlKey, &fav_icon_url));
+ } else {
+ EXPECT_TRUE(dict->GetString(ext::kFavIconUrlKey, &fav_icon_url));
+ EXPECT_STREQ(tests[i].fav_icon_url, fav_icon_url.c_str());
+ }
+
+ int index = -1;
+ EXPECT_TRUE(dict->GetInteger(ext::kIndexKey, &index));
+ if (tests[i].index == -1)
+ EXPECT_EQ(kTabIndex, index);
+ else
+ EXPECT_EQ(tests[i].index, index);
+ }
+}
+
+TEST(TabApi, IsTabWindowClassWithNull) {
+ testing::LogDisabler no_dchecks;
+ TabApiResult invocation_result(TabApiResult::kNoRequestId);
+ EXPECT_FALSE(invocation_result.IsTabWindowClass(NULL));
+}
+
+TEST(TabApi, IsTabWindowClassWithNonWindow) {
+ testing::LogDisabler no_dchecks;
+ StrictMock<testing::MockUser32> user32;
+ EXPECT_CALL(user32, IsWindow(NotNull())).WillRepeatedly(Return(FALSE));
+ TabApiResult invocation_result(TabApiResult::kNoRequestId);
+ EXPECT_FALSE(invocation_result.IsTabWindowClass(reinterpret_cast<HWND>(1)));
+}
+
+TEST(TabApi, IsTabWindowClassWrongClassName) {
+ const wchar_t kBadClassName[] = L"BadWindowClass";
+ testing::LogDisabler no_dchecks;
+ StrictMock<testing::MockUser32> user32;
+ EXPECT_CALL(user32, IsWindow(NotNull())).WillOnce(Return(TRUE));
+ EXPECT_CALL(user32, GetClassName(NotNull(), NotNull(), Gt(0))).WillOnce(
+ DoAll(CopyStringToArgument<1>(kBadClassName),
+ Return(arraysize(kBadClassName))));
+ TabApiResult invocation_result(TabApiResult::kNoRequestId);
+ EXPECT_FALSE(invocation_result.IsTabWindowClass(reinterpret_cast<HWND>(1)));
+}
+
+TEST(TabApi, IsTabWindowClassStraightline) {
+ testing::LogDisabler no_dchecks;
+ StrictMock<testing::MockUser32> user32;
+ EXPECT_CALL(user32, IsWindow(NotNull())).WillOnce(Return(TRUE));
+ EXPECT_CALL(user32, GetClassName(NotNull(), NotNull(), Gt(0))).WillOnce(
+ DoAll(CopyStringToArgument<1>(kClassName),
+ Return(arraysize(kClassName))));
+ TabApiResult invocation_result(TabApiResult::kNoRequestId);
+ EXPECT_TRUE(invocation_result.IsTabWindowClass(reinterpret_cast<HWND>(1)));
+}
+
+TEST(TabApi, GetSpecifiedOrCurrentFrameWindow) {
+ testing::LogDisabler no_dchecks;
+
+ StrictMock<testing::MockWindowUtils> window_utils;
+ scoped_ptr<Value> bad_args(Value::CreateRealValue(4.7));
+ DictionaryValue bad_dict_args; // no window ID key.
+ DictionaryValue good_dict_args;
+ int window1_id = 77;
+ HWND window1 = reinterpret_cast<HWND>(window1_id);
+ good_dict_args.SetInteger(ext::kWindowIdKey, window1_id);
+
+ StrictMock<MockTabApiResult> invocation_result(TabApiResult::kNoRequestId);
+
+ // First, fail because no method finds us a window.
+ EXPECT_CALL(window_utils, FindDescendentWindow(_, _, _, _))
+ .WillOnce(Return(false));
+ HWND null_window = NULL;
+ EXPECT_EQ(null_window, invocation_result.CallGetSpecifiedOrCurrentFrameWindow(
+ ListValue(), NULL));
+
+ // Then, fail because we find a window that's not a window.
+ HWND window2 = reinterpret_cast<HWND>(88);
+ EXPECT_CALL(window_utils, FindDescendentWindow(_, _, _, _)).WillRepeatedly(
+ DoAll(SetArgumentPointee<3>(window2), Return(true)));
+ EXPECT_CALL(window_utils, IsWindowClass(window2, _)).WillOnce(Return(false));
+ EXPECT_EQ(null_window, invocation_result.CallGetSpecifiedOrCurrentFrameWindow(
+ ListValue(), NULL));
+
+ // From now on, all windows are valid.
+ EXPECT_CALL(window_utils, IsWindowClass(_, _)).WillRepeatedly(Return(true));
+
+ // So succeed for once - we're using null args so we get the
+ // window from the FindDescendentWindow.
+ bool specified = true;
+ EXPECT_EQ(window2, invocation_result.CallGetSpecifiedOrCurrentFrameWindow(
+ ListValue(), &specified));
+ EXPECT_FALSE(specified);
+
+ ListValue good_list_with_null;
+ ASSERT_TRUE(good_list_with_null.Set(0, Value::CreateNullValue()));
+ specified = true;
+ EXPECT_EQ(window2, invocation_result.CallGetSpecifiedOrCurrentFrameWindow(
+ good_list_with_null, &specified));
+ EXPECT_FALSE(specified);
+
+ ListValue good_list_with_null_dict;
+ ASSERT_TRUE(good_list_with_null_dict.Set(0, new DictionaryValue()));
+ specified = true;
+ EXPECT_EQ(window2, invocation_result.CallGetSpecifiedOrCurrentFrameWindow(
+ good_list_with_null_dict, &specified));
+ EXPECT_FALSE(specified);
+
+ // From now on, we can expect those to always return consistent values.
+ EXPECT_CALL(invocation_result.mock_api_dispatcher_,
+ GetWindowHandleFromId(kGoodTabWindowId)).
+ WillRepeatedly(Return(kGoodTabWindow));
+ EXPECT_CALL(invocation_result.mock_api_dispatcher_,
+ GetWindowHandleFromId(window1_id)).WillRepeatedly(Return(window1));
+
+ // Get window from good args.
+ scoped_ptr<Value> good_int_args(Value::CreateIntegerValue(kGoodTabWindowId));
+ ListValue good_list_with_int;
+ ASSERT_TRUE(good_list_with_int.Set(0, Value::CreateIntegerValue(window1_id)));
+ ListValue good_list_with_dict;
+ ASSERT_TRUE(good_list_with_dict.Set(0, good_dict_args.DeepCopy()));
+
+ EXPECT_EQ(kGoodTabWindow, invocation_result.
+ CallGetSpecifiedOrCurrentFrameWindow(*good_int_args.get(), &specified));
+ EXPECT_TRUE(specified);
+ specified = false;
+ EXPECT_EQ(window1, invocation_result.CallGetSpecifiedOrCurrentFrameWindow(
+ good_dict_args, &specified));
+ EXPECT_TRUE(specified);
+ specified = false;
+ EXPECT_EQ(window1, invocation_result.CallGetSpecifiedOrCurrentFrameWindow(
+ good_list_with_int, &specified));
+ EXPECT_TRUE(specified);
+ specified = false;
+ EXPECT_EQ(window1, invocation_result.CallGetSpecifiedOrCurrentFrameWindow(
+ good_list_with_dict, &specified));
+ EXPECT_TRUE(specified);
+}
+
+TEST_F(TabApiTests, GetTab) {
+ testing::LogDisabler no_dchecks;
+
+ ListValue good_args;
+ ASSERT_TRUE(good_args.Set(0, Value::CreateIntegerValue(kGoodTabWindowId)));
+ ListValue bad_window;
+ ASSERT_TRUE(bad_window.Set(0, Value::CreateIntegerValue(kBadWindowId)));
+
+ // Mocking IsTabWindowClass.
+ StrictMock<testing::MockWindowUtils> window_utils;
+ EXPECT_CALL(window_utils, IsWindowClass(kGoodTabWindow, _)).
+ WillRepeatedly(Return(true));
+ EXPECT_CALL(window_utils, IsWindowClass(kBadWindow, _)).
+ WillRepeatedly(Return(false));
+
+ StrictMock<MockApiInvocation<TabApiResult, MockTabApiResult, GetTab> >
+ invocation;
+ invocation.AllocateNewResult(kRequestId);
+
+ // Start with success.
+ EXPECT_CALL(*invocation.invocation_result_,
+ CreateTabValue(kGoodTabWindowId, _)).WillOnce(Return(true));
+ EXPECT_CALL(*invocation.invocation_result_, PostResult()).Times(1);
+ EXPECT_CALL(invocation.mock_api_dispatcher_,
+ GetTabHandleFromId(kGoodTabWindowId)).WillOnce(Return(kGoodTabWindow));
+ invocation.Execute(good_args, kRequestId);
+
+ // No more successful calls.
+ invocation.AllocateNewResult(kRequestId);
+ EXPECT_CALL(*invocation.invocation_result_, PostError(_)).Times(1);
+ EXPECT_CALL(invocation.mock_api_dispatcher_,
+ GetTabHandleFromId(kBadWindowId)).WillOnce(Return(kBadWindow));
+ invocation.Execute(bad_window, kRequestId);
+ invocation.AllocateNewResult(kRequestId);
+ EXPECT_CALL(*invocation.invocation_result_, PostError(_)).Times(1);
+ invocation.Execute(ListValue(), kRequestId);
+}
+
+TEST_F(TabApiTests, GetSelectedTab) {
+ testing::LogDisabler no_dchecks;
+ StrictMock<MockApiInvocation<TabApiResult, MockTabApiResult, GetSelectedTab> >
+ invocation;
+ // No frame window.
+ invocation.AllocateNewResult(kRequestId);
+ EXPECT_CALL(*invocation.invocation_result_,
+ GetSpecifiedOrCurrentFrameWindow(_, _)).WillOnce(
+ Return(static_cast<HWND>(NULL)));
+ EXPECT_CALL(*invocation.invocation_result_, PostError(_)).Times(1);
+ invocation.Execute(ListValue(), kRequestId);
+
+ // Success
+ invocation.AllocateNewResult(kRequestId);
+ EXPECT_CALL(*invocation.invocation_result_,
+ GetSpecifiedOrCurrentFrameWindow(_, NotNull())).
+ WillRepeatedly(DoAll(SetArgumentPointee<1>(true),
+ Return(kGoodFrameWindow)));
+ StrictMock<testing::MockWindowUtils> window_utils;
+ EXPECT_CALL(window_utils, FindDescendentWindow(
+ kGoodFrameWindow, _, _, NotNull())).
+ WillOnce(DoAll(SetArgumentPointee<3>(kGoodTabWindow), Return(true)));
+
+ EXPECT_CALL(*invocation.invocation_result_,
+ CreateTabValue(kGoodTabWindowId, _)).WillOnce(Return(true));
+ EXPECT_CALL(*invocation.invocation_result_, PostResult()).Times(1);
+ EXPECT_CALL(invocation.mock_api_dispatcher_,
+ GetTabIdFromHandle(kGoodTabWindow)).WillOnce(Return(kGoodTabWindowId));
+ invocation.Execute(ListValue(), kRequestId);
+
+ // TODO(mad@chromium.org): Try the async case.
+}
+
+class MockGetAllTabsInWindowResult : public GetAllTabsInWindowResult {
+ public:
+ explicit MockGetAllTabsInWindowResult(int request_id)
+ : GetAllTabsInWindowResult(request_id) {}
+ MOCK_METHOD2(CreateTabValue, bool(int, long));
+ MOCK_METHOD2(GetSpecifiedOrCurrentFrameWindow, HWND(const Value&, bool*));
+ MOCK_METHOD1(Execute, bool(BSTR));
+ MOCK_METHOD0(PostResult, void());
+ MOCK_METHOD1(PostError, void(const std::string&));
+
+ virtual ApiDispatcher* GetDispatcher() {
+ return &mock_api_dispatcher_;
+ }
+
+ bool CallExecute(BSTR tabs) {
+ return GetAllTabsInWindowResult::Execute(tabs);
+ }
+
+ StrictMock<MockApiDispatcher> mock_api_dispatcher_;
+};
+
+TEST_F(TabApiTests, GetAllTabsInWindowResult) {
+ testing::LogDisabler no_dchecks;
+ StrictMock<MockGetAllTabsInWindowResult> invocation_result(
+ TabApiResult::kNoRequestId);
+
+ // Start with a few failure cases.
+ // Not a proper JSON string.
+ EXPECT_CALL(invocation_result, PostError(_)).Times(1);
+ invocation_result.CallExecute(L"Bla bla bla");
+ invocation_result.set_value(NULL);
+
+ // Proper JSON string, not being a list.
+ EXPECT_CALL(invocation_result, PostError(_)).Times(1);
+ invocation_result.CallExecute(L"{}");
+ invocation_result.set_value(NULL);
+
+ // Wrong number of elements in the list.
+ EXPECT_CALL(invocation_result, PostError(_)).Times(1);
+ invocation_result.CallExecute(L"[23]");
+ invocation_result.set_value(NULL);
+
+ // Empty is valid yet doesn't get CreateTabValue called.
+ EXPECT_TRUE(invocation_result.CallExecute(L"[]"));
+ invocation_result.set_value(NULL);
+
+ // Successes.
+ int index = 24;
+ EXPECT_CALL(invocation_result, CreateTabValue(kGoodTabWindowId, index)).
+ WillOnce(Return(true));
+ EXPECT_CALL(invocation_result.mock_api_dispatcher_,
+ GetTabIdFromHandle(kGoodTabWindow)).WillOnce(Return(kGoodTabWindowId));
+ {
+ std::wostringstream args;
+ args << L"[" << kGoodTabWindowHandle << L"," << index << L"]";
+ EXPECT_TRUE(invocation_result.CallExecute((BSTR)(args.str().c_str())));
+ EXPECT_NE(static_cast<Value*>(NULL), invocation_result.value());
+ invocation_result.set_value(NULL);
+ }
+
+ {
+ std::wostringstream args;
+ Sequence seq;
+ struct id_map_struct {
+ int id;
+ HWND handle;
+ int value;
+ } const test_id_map[] = {
+ { 1, reinterpret_cast<HWND>(2), 3 },
+ { 4, reinterpret_cast<HWND>(5), 6 },
+ { 7, reinterpret_cast<HWND>(8), 9 },
+ { 0 }
+ };
+
+ // Build the JSON list from the above test map. We also want to expect the
+ // correct underlying calls.
+ args << L"[";
+ for (int i = 0; test_id_map[i].id != 0; ++i) {
+ const id_map_struct& item = test_id_map[i];
+ EXPECT_CALL(invocation_result.mock_api_dispatcher_,
+ GetTabIdFromHandle(item.handle)).WillOnce(Return(item.id));
+ EXPECT_CALL(invocation_result, CreateTabValue(item.id, item.value)).
+ InSequence(seq).WillOnce(Return(true));
+ if (i)
+ args << L",";
+ args << reinterpret_cast<int>(item.handle) << L"," << item.value;
+ }
+ args << L"]";
+ EXPECT_TRUE(invocation_result.CallExecute((BSTR)(args.str().c_str())));
+ EXPECT_NE(static_cast<Value*>(NULL), invocation_result.value());
+ }
+}
+
+TEST_F(TabApiTests, GetAllTabsInWindow) {
+ testing::LogDisabler no_dchecks;
+ StrictMock<MockApiInvocation<GetAllTabsInWindowResult,
+ MockGetAllTabsInWindowResult, GetAllTabsInWindow>> invocation;
+
+ // No frame window.
+ invocation.AllocateNewResult(kRequestId);
+ EXPECT_CALL(*invocation.invocation_result_,
+ GetSpecifiedOrCurrentFrameWindow(_, _)).WillOnce(
+ Return(static_cast<HWND>(NULL)));
+ EXPECT_CALL(*invocation.invocation_result_, PostError(_)).Times(1);
+ invocation.Execute(ListValue(), kRequestId);
+
+ // No Executor.
+ invocation.AllocateNewResult(kRequestId);
+ EXPECT_CALL(*invocation.invocation_result_,
+ GetSpecifiedOrCurrentFrameWindow(_, _)).
+ WillRepeatedly(Return(kGoodFrameWindow));
+ EXPECT_CALL(invocation.mock_api_dispatcher_,
+ GetExecutor(kGoodFrameWindow, _, NotNull())).
+ WillOnce(SetArgumentPointee<2>(static_cast<void*>(NULL)));
+ EXPECT_CALL(*invocation.invocation_result_, PostError(_)).Times(1);
+ invocation.Execute(ListValue(), kRequestId);
+
+ invocation.AllocateNewResult(kRequestId);
+ EXPECT_CALL(*invocation.invocation_result_,
+ GetSpecifiedOrCurrentFrameWindow(_, _)).
+ WillRepeatedly(Return(kGoodFrameWindow));
+
+ // Failing Executor.
+ // The executor classes are already strict from their base class impl.
+ testing::MockWindowExecutor* mock_window_executor;
+ CComPtr<ICeeeWindowExecutor> mock_window_executor_keeper_;
+ EXPECT_HRESULT_SUCCEEDED(testing::MockWindowExecutor::CreateInitialized(
+ &mock_window_executor, &mock_window_executor_keeper_));
+ EXPECT_CALL(invocation.mock_api_dispatcher_,
+ GetExecutor(kGoodFrameWindow, _, NotNull())).
+ WillRepeatedly(DoAll(SetArgumentPointee<2>(
+ mock_window_executor_keeper_.p),
+ AddRef(mock_window_executor_keeper_.p)));
+ EXPECT_CALL(*mock_window_executor, GetTabs(NotNull())).
+ WillOnce(Return(E_FAIL));
+ EXPECT_CALL(*invocation.invocation_result_, PostError(_)).Times(1);
+ invocation.Execute(ListValue(), kRequestId);
+
+ // Successes.
+ invocation.AllocateNewResult(kRequestId);
+ BSTR tab_ids = ::SysAllocString(L"");
+ EXPECT_CALL(*invocation.invocation_result_,
+ GetSpecifiedOrCurrentFrameWindow(_, _)).
+ WillOnce(Return(kGoodFrameWindow));
+ EXPECT_CALL(*mock_window_executor, GetTabs(NotNull())).
+ WillOnce(DoAll(SetArgumentPointee<0>(tab_ids), Return(S_OK)));
+ EXPECT_CALL(*invocation.invocation_result_, Execute(tab_ids)).
+ WillOnce(Return(true));
+ EXPECT_CALL(*invocation.invocation_result_, PostResult()).Times(1);
+ invocation.Execute(ListValue(), kRequestId);
+ // Execute freed tab_ids...
+ tab_ids = NULL;
+}
+
+// TODO(mad@chromium.org): Test the event handling.
+class MockUpdateTab : public StrictMock<MockApiInvocation<TabApiResult,
+ MockTabApiResult,
+ UpdateTab> > {
+};
+
+TEST_F(TabApiTests, UpdateTab) {
+ ListValue bad_window;
+ bad_window.Append(Value::CreateIntegerValue(kBadWindowId));
+
+ ListValue wrong_second_arg;
+ wrong_second_arg.Append(Value::CreateIntegerValue(kGoodTabWindowId));
+ wrong_second_arg.Append(Value::CreateStringValue(L"Angus"));
+
+ ListValue wrong_url_type;
+ DictionaryValue* wrong_url_type_dictionary = new DictionaryValue();
+ wrong_url_type_dictionary->SetInteger(ext::kUrlKey, 1);
+ wrong_url_type.Append(Value::CreateIntegerValue(kGoodTabWindowId));
+ wrong_url_type.Append(wrong_url_type_dictionary);
+
+ ListValue wrong_selected_type;
+ DictionaryValue* wrong_selected_type_dictionary = new DictionaryValue();
+ wrong_selected_type_dictionary->SetString(ext::kSelectedKey, L"yes");
+ wrong_selected_type.Append(Value::CreateIntegerValue(kGoodTabWindowId));
+ wrong_selected_type.Append(wrong_selected_type_dictionary);
+
+ ListValue good_url;
+ DictionaryValue* good_url_dictionary = new DictionaryValue();
+ good_url_dictionary->SetString(ext::kUrlKey, "http://google.com");
+ good_url.Append(Value::CreateIntegerValue(kGoodTabWindowId));
+ good_url.Append(good_url_dictionary);
+
+ ListValue selected_true;
+ DictionaryValue* selected_true_dictionary = new DictionaryValue();
+ selected_true_dictionary->SetBoolean(ext::kSelectedKey, true);
+ selected_true.Append(Value::CreateIntegerValue(kGoodTabWindowId));
+ selected_true.Append(selected_true_dictionary);
+
+ testing::LogDisabler no_dchecks;
+
+ MockUpdateTab invocation;
+ ListValue empty_list;
+ invocation.AllocateNewResult(kRequestId);
+ EXPECT_CALL(*invocation.invocation_result_, PostError(_)).Times(1);
+ invocation.Execute(empty_list, kRequestId);
+
+ EXPECT_CALL(invocation.mock_api_dispatcher_,
+ GetTabHandleFromId(kBadWindowId)).WillRepeatedly(Return(kBadWindow));
+ EXPECT_CALL(invocation.mock_api_dispatcher_,
+ GetTabHandleFromId(kGoodTabWindowId)).
+ WillRepeatedly(Return(kGoodTabWindow));
+
+ // Not an IeServerClass.
+ invocation.AllocateNewResult(kRequestId);
+ StrictMock<testing::MockWindowUtils> window_utils;
+ EXPECT_CALL(window_utils, IsWindowClass(kBadWindow, _)).
+ WillOnce(Return(false));
+ EXPECT_CALL(*invocation.invocation_result_, PostError(_)).Times(1);
+ invocation.Execute(bad_window, kRequestId);
+
+ // Window has no thread.
+ invocation.AllocateNewResult(kRequestId);
+ EXPECT_CALL(window_utils, IsWindowClass(_, _)).WillRepeatedly(Return(true));
+ EXPECT_CALL(window_utils, WindowHasNoThread(kBadWindow)).
+ WillOnce(Return(true));
+ EXPECT_CALL(*invocation.invocation_result_, PostError(_)).Times(1);
+ invocation.Execute(bad_window, kRequestId);
+
+ EXPECT_CALL(window_utils, WindowHasNoThread(_)).WillRepeatedly(Return(false));
+
+ invocation.AllocateNewResult(kRequestId);
+ EXPECT_CALL(*invocation.invocation_result_, PostError(_)).Times(1);
+ invocation.Execute(wrong_second_arg, kRequestId);
+ invocation.AllocateNewResult(kRequestId);
+ EXPECT_CALL(*invocation.invocation_result_, PostError(_)).Times(1);
+ invocation.Execute(wrong_url_type, kRequestId);
+ invocation.AllocateNewResult(kRequestId);
+ EXPECT_CALL(*invocation.invocation_result_, PostError(_)).Times(1);
+ invocation.Execute(wrong_selected_type, kRequestId);
+
+ // Can't get an executor for Navigate.
+ invocation.AllocateNewResult(kRequestId);
+ EXPECT_CALL(invocation.mock_api_dispatcher_,
+ GetExecutor(kGoodTabWindow, _, NotNull())).
+ WillOnce(SetArgumentPointee<2>(static_cast<void*>(NULL)));
+ EXPECT_CALL(*invocation.invocation_result_, PostError(_)).Times(1);
+ invocation.Execute(good_url, kRequestId);
+
+ // Failing Executor.
+ invocation.AllocateNewResult(kRequestId);
+ AlwaysMockGetTabExecutor(&invocation.mock_api_dispatcher_, kGoodTabWindow);
+ EXPECT_CALL(*mock_tab_executor_, Navigate(_, _, _)).
+ WillOnce(Return(E_FAIL));
+ EXPECT_CALL(*invocation.invocation_result_, PostError(_)).Times(1);
+ invocation.Execute(good_url, kRequestId);
+
+ // Can't get an executor for SelectTab.
+ invocation.AllocateNewResult(kRequestId);
+ EXPECT_CALL(window_utils, GetTopLevelParent(kGoodTabWindow)).
+ WillRepeatedly(Return(kGoodFrameWindow));
+ EXPECT_CALL(invocation.mock_api_dispatcher_,
+ GetExecutor(kGoodFrameWindow, _, NotNull())).
+ WillOnce(SetArgumentPointee<2>(static_cast<void*>(NULL)));
+ EXPECT_CALL(*invocation.invocation_result_, PostError(_)).Times(1);
+ invocation.Execute(selected_true, kRequestId);
+
+ // Failing Executor.
+ invocation.AllocateNewResult(kRequestId);
+ AlwaysMockGetWindowExecutor(&invocation.mock_api_dispatcher_,
+ kGoodFrameWindow);
+ EXPECT_CALL(*mock_window_executor_, SelectTab(kGoodTabWindowHandle)).
+ WillOnce(Return(E_FAIL));
+ EXPECT_CALL(*invocation.invocation_result_, PostError(_)).Times(1);
+ invocation.Execute(selected_true, kRequestId);
+
+ // Successful Navigate.
+ invocation.AllocateNewResult(kRequestId);
+ EXPECT_CALL(*mock_tab_executor_, Navigate(_, _, _)).
+ WillOnce(Return(S_OK));
+ EXPECT_CALL(*invocation.invocation_result_,
+ CreateTabValue(kGoodTabWindowId, -1)).WillOnce(Return(true));
+ EXPECT_CALL(*invocation.invocation_result_, PostResult()).Times(1);
+ invocation.Execute(good_url, kRequestId);
+
+ // Successful SelectTab.
+ invocation.AllocateNewResult(kRequestId);
+ EXPECT_CALL(*mock_window_executor_, SelectTab(kGoodTabWindowHandle)).
+ WillOnce(Return(S_OK));
+ EXPECT_CALL(*invocation.invocation_result_,
+ CreateTabValue(kGoodTabWindowId, -1)).WillOnce(Return(true));
+ EXPECT_CALL(*invocation.invocation_result_, PostResult()).Times(1);
+ invocation.Execute(selected_true, kRequestId);
+}
+
+class MockRemoveTab : public StrictMock<MockApiInvocation<TabApiResult,
+ MockTabApiResult,
+ RemoveTab> > {
+};
+
+TEST_F(TabApiTests, RemoveTabExecute) {
+ testing::LogDisabler no_dchecks;
+
+ MockRemoveTab invocation;
+ ListValue list_args;
+
+ // Bad arguments.
+ invocation.AllocateNewResult(kRequestId);
+ EXPECT_CALL(*invocation.invocation_result_, PostError(_));
+ invocation.Execute(list_args, kRequestId);
+
+ // Not a IeServerClass.
+ invocation.AllocateNewResult(kRequestId);
+
+ StrictMock<testing::MockWindowUtils> window_utils;
+ EXPECT_CALL(window_utils, IsWindowClass(_, _)).WillOnce(Return(false));
+
+ list_args.Append(Value::CreateIntegerValue(kGoodTabWindowId));
+ EXPECT_CALL(*invocation.invocation_result_, PostError(_));
+ EXPECT_CALL(invocation.mock_api_dispatcher_,
+ GetTabHandleFromId(kGoodTabWindowId)).WillOnce(Return(kGoodTabWindow));
+ invocation.Execute(list_args, kRequestId);
+
+ // Fail to get the executor.
+ invocation.AllocateNewResult(kRequestId);
+ EXPECT_CALL(window_utils, IsWindowClass(_, _)).WillRepeatedly(Return(true));
+ EXPECT_CALL(window_utils, GetTopLevelParent(kGoodTabWindow)).
+ WillRepeatedly(Return(kGoodFrameWindow));
+ EXPECT_CALL(invocation.mock_api_dispatcher_,
+ GetExecutor(kGoodFrameWindow, _, NotNull())).
+ WillOnce(SetArgumentPointee<2>(static_cast<void*>(NULL)));
+ EXPECT_CALL(*invocation.invocation_result_, PostError(_));
+ EXPECT_CALL(invocation.mock_api_dispatcher_,
+ GetTabHandleFromId(kGoodTabWindowId)).WillOnce(Return(kGoodTabWindow));
+ invocation.Execute(list_args, kRequestId);
+
+ // Failing executor.
+ invocation.AllocateNewResult(kRequestId);
+ AlwaysMockGetWindowExecutor(&invocation.mock_api_dispatcher_,
+ kGoodFrameWindow);
+ EXPECT_CALL(*mock_window_executor_, RemoveTab(kGoodTabWindowHandle)).
+ WillOnce(Return(E_FAIL));
+ EXPECT_CALL(*invocation.invocation_result_, PostError(_));
+ EXPECT_CALL(invocation.mock_api_dispatcher_,
+ GetTabHandleFromId(kGoodTabWindowId)).WillOnce(Return(kGoodTabWindow));
+ invocation.Execute(list_args, kRequestId);
+
+ // Success.
+ invocation.AllocateNewResult(kRequestId);
+ EXPECT_CALL(*mock_window_executor_, RemoveTab(kGoodTabWindowHandle)).
+ WillOnce(Return(S_OK));
+ EXPECT_CALL(invocation.mock_api_dispatcher_, RegisterEphemeralEventHandler(
+ StrEq(ext_event_names::kOnTabRemoved),
+ RemoveTab::ContinueExecution,
+ invocation.invocation_result_.get()));
+ EXPECT_CALL(invocation.mock_api_dispatcher_,
+ GetTabHandleFromId(kGoodTabWindowId)).WillOnce(Return(kGoodTabWindow));
+ // This will cause the invocation result to be released at the end of the
+ // test since the success case purposely does not delete it.
+ scoped_ptr<ApiDispatcher::InvocationResult> result(
+ invocation.invocation_result_.get());
+ invocation.Execute(list_args, kRequestId);
+}
+
+TEST_F(TabApiTests, RemoveTabContinueExecution) {
+ testing::LogDisabler no_dchecks;
+
+ // Failure cases.
+ MockRemoveTab invocation;
+
+ ListValue list_value;
+ list_value.Append(Value::CreateIntegerValue(kGoodTabWindowId));
+ std::string input_args;
+ base::JSONWriter::Write(&list_value, false, &input_args);
+
+ // Bad arguments.
+ invocation.AllocateNewResult(kRequestId);
+ EXPECT_CALL(*invocation.invocation_result_, PostError(_));
+ HRESULT hr = invocation.CallContinueExecution("");
+ EXPECT_HRESULT_FAILED(hr);
+
+ // Mismatched tab IDs.
+ invocation.AllocateNewResult(kRequestId);
+ invocation.invocation_result_->SetValue(
+ ext::kTabIdKey, Value::CreateIntegerValue(1234));
+ hr = invocation.CallContinueExecution(input_args);
+ EXPECT_HRESULT_SUCCEEDED(hr);
+
+ // Success.
+ invocation.AllocateNewResult(kRequestId);
+ invocation.invocation_result_->SetValue(
+ ext::kTabIdKey, Value::CreateIntegerValue(kGoodTabWindowId));
+ EXPECT_CALL(*invocation.invocation_result_, PostResult());
+ hr = invocation.CallContinueExecution(input_args);
+ EXPECT_HRESULT_SUCCEEDED(hr);
+}
+
+// Mock the CoCreateInstance call that is used to create a new IE window.
+MOCK_STATIC_CLASS_BEGIN(MockIeUtil)
+ MOCK_STATIC_INIT_BEGIN(MockIeUtil)
+ MOCK_STATIC_INIT2(ie_util::GetWebBrowserForTopLevelIeHwnd,
+ GetWebBrowserForTopLevelIeHwnd);
+ MOCK_STATIC_INIT_END()
+ MOCK_STATIC3(HRESULT, , GetWebBrowserForTopLevelIeHwnd,
+ HWND, IWebBrowser2*, IWebBrowser2**);
+MOCK_STATIC_CLASS_END(MockIeUtil)
+
+// Mock static calls to TabApiResult.
+MOCK_STATIC_CLASS_BEGIN(MockStaticTabApiResult)
+ MOCK_STATIC_INIT_BEGIN(MockStaticTabApiResult)
+ MOCK_STATIC_INIT2(TabApiResult::IsTabFromSameOrUnspecifiedFrameWindow,
+ IsTabFromSameOrUnspecifiedFrameWindow);
+ MOCK_STATIC_INIT_END()
+ MOCK_STATIC4(bool, , IsTabFromSameOrUnspecifiedFrameWindow,
+ const DictionaryValue&, const Value*, HWND*, ApiDispatcher*);
+MOCK_STATIC_CLASS_END(MockStaticTabApiResult)
+
+// TODO(mad@chromium.org): Test the asynchronicity and the event handling.
+class MockCreateTab : public StrictMock<MockApiInvocation<TabApiResult,
+ MockTabApiResult,
+ CreateTab> > {
+};
+
+TEST_F(TabApiTests, CreateTabExecute) {
+ testing::LogDisabler no_dchecks;
+
+ // Failure cases.
+ MockCreateTab invocation;
+
+ ListValue list_value;
+ DictionaryValue* list_dict = new DictionaryValue;
+ // list_dict will be freed by ListValue, yet we need to keep it around
+ // to set values into it.
+ list_value.Append(list_dict);
+
+ // No Frame window.
+ invocation.AllocateNewResult(kRequestId);
+ EXPECT_CALL(*invocation.invocation_result_,
+ GetSpecifiedOrCurrentFrameWindow(_, _)).WillOnce(Return(
+ static_cast<HWND>(NULL)));
+ EXPECT_CALL(*invocation.invocation_result_, PostError(_));
+ invocation.Execute(list_value, kRequestId);
+
+ // Can't get Executor/IWebBrowser2.
+ invocation.AllocateNewResult(kRequestId);
+ EXPECT_CALL(*invocation.invocation_result_,
+ GetSpecifiedOrCurrentFrameWindow(_, _)).
+ WillRepeatedly(Return(kGoodFrameWindow));
+ StrictMock<MockIeUtil> mock_ie_util;
+ StrictMock<testing::MockWindowUtils> mock_window_utils;
+ bool pre_vista = base::win::GetVersion() < base::win::VERSION_VISTA;
+ if (pre_vista) {
+ EXPECT_CALL(mock_ie_util, GetWebBrowserForTopLevelIeHwnd(
+ kGoodFrameWindow, _, NotNull())).WillOnce(Return(E_FAIL));
+ } else {
+ EXPECT_CALL(mock_window_utils, FindDescendentWindow(
+ kGoodFrameWindow, _, _, NotNull())).WillRepeatedly(
+ DoAll(SetArgumentPointee<3>(kGoodTabWindow), Return(true)));
+ EXPECT_CALL(invocation.mock_api_dispatcher_,
+ GetExecutor(kGoodTabWindow, _, _)).
+ WillOnce(SetArgumentPointee<2>(static_cast<void*>(NULL)));
+ }
+ EXPECT_CALL(*invocation.invocation_result_, PostError(_));
+ invocation.Execute(list_value, kRequestId);
+
+ // Navigate Fails.
+ invocation.AllocateNewResult(kRequestId);
+ EXPECT_CALL(*invocation.invocation_result_,
+ GetSpecifiedOrCurrentFrameWindow(_, _)).
+ WillOnce(Return(kGoodFrameWindow));
+ CComObject<StrictMock<testing::MockIWebBrowser2>>* browser;
+ CComObject<StrictMock<testing::MockIWebBrowser2>>::CreateInstance(
+ &browser);
+ DCHECK(browser != NULL);
+ CComPtr<IWebBrowser2> browser_keeper = browser;
+ if (pre_vista) {
+ EXPECT_CALL(mock_ie_util, GetWebBrowserForTopLevelIeHwnd(
+ kGoodFrameWindow, _, NotNull())).WillRepeatedly(DoAll(
+ CopyInterfaceToArgument<2>(browser_keeper.p), Return(S_OK)));
+ EXPECT_CALL(*browser, Navigate(_, _, _, _, _)).WillOnce(Return(E_FAIL));
+ } else {
+ AlwaysMockGetTabExecutor(&invocation.mock_api_dispatcher_, kGoodTabWindow);
+ EXPECT_CALL(*mock_tab_executor_, Navigate(_, _, _)).
+ WillOnce(Return(E_FAIL));
+ }
+ EXPECT_CALL(*invocation.invocation_result_, PostError(_));
+ invocation.Execute(list_value, kRequestId);
+
+ // Success!
+ invocation.AllocateNewResult(kRequestId);
+ EXPECT_CALL(*invocation.invocation_result_,
+ GetSpecifiedOrCurrentFrameWindow(_, _)).
+ WillOnce(Return(kGoodFrameWindow));
+ if (pre_vista) {
+ EXPECT_CALL(*browser, Navigate(_, _, _, _, _)).WillOnce(Return(S_OK));
+ } else {
+ EXPECT_CALL(*mock_tab_executor_, Navigate(_, _, _)).
+ WillOnce(Return(S_OK));
+ }
+ EXPECT_CALL(invocation.mock_api_dispatcher_, RegisterEphemeralEventHandler(
+ StrEq(ext_event_names::kOnTabCreated),
+ CreateTab::ContinueExecution,
+ invocation.invocation_result_.get()));
+
+ // This will cause the invocation result to be released at the end of the
+ // test since the success case purposely does not delete it.
+ scoped_ptr<ApiDispatcher::InvocationResult> result(
+ invocation.invocation_result_.get());
+ invocation.Execute(list_value, kRequestId);
+}
+
+TEST_F(TabApiTests, CreateTabContinueExecution) {
+ testing::LogDisabler no_dchecks;
+
+ // Failure cases.
+ MockCreateTab invocation;
+ const char kUrl[] = "http://url/";
+ const int kIndex = 0;
+
+ ListValue list_value;
+ DictionaryValue* dict_value = new DictionaryValue;
+ list_value.Append(dict_value);
+ dict_value->SetInteger(ext::kWindowIdKey, kGoodTabWindowId);
+ dict_value->SetString(ext::kUrlKey, kUrl);
+ dict_value->SetInteger(ext::kIndexKey, kIndex);
+
+ std::string input_args;
+ base::JSONWriter::Write(&list_value, false, &input_args);
+
+ // No input dictionary.
+ invocation.AllocateNewResult(kRequestId);
+ EXPECT_CALL(*invocation.invocation_result_, PostError(_));
+ HRESULT hr = invocation.CallContinueExecution("");
+ EXPECT_HRESULT_FAILED(hr);
+
+ // IsTabFromSameOrUnspecifiedFrameWindow returns false.
+ invocation.AllocateNewResult(kRequestId);
+ StrictMock<MockStaticTabApiResult> tab_api_result;
+ EXPECT_CALL(tab_api_result,
+ IsTabFromSameOrUnspecifiedFrameWindow(_, _, _, NotNull())).
+ WillOnce(Return(false));
+
+ hr = invocation.CallContinueExecution(input_args);
+ EXPECT_HRESULT_SUCCEEDED(hr);
+
+ // Mistmatched URLs.
+ invocation.AllocateNewResult(kRequestId);
+ invocation.invocation_result_->SetValue(
+ ext::kUrlKey, Value::CreateStringValue("http://other/"));
+
+ EXPECT_CALL(tab_api_result,
+ IsTabFromSameOrUnspecifiedFrameWindow(_, _, _, NotNull())).
+ WillOnce(Return(true));
+
+ hr = invocation.CallContinueExecution(input_args);
+ EXPECT_HRESULT_SUCCEEDED(hr);
+
+ // Success with no index.
+ invocation.AllocateNewResult(kRequestId);
+ invocation.invocation_result_->SetValue(
+ ext::kUrlKey, Value::CreateStringValue(kUrl));
+
+ EXPECT_CALL(tab_api_result,
+ IsTabFromSameOrUnspecifiedFrameWindow(_, _, _, NotNull())).
+ WillOnce(DoAll(SetArgumentPointee<2>(kGoodTabWindow), Return(true)));
+ EXPECT_CALL(invocation.mock_api_dispatcher_,
+ GetTabIdFromHandle(kGoodTabWindow)).WillOnce(Return(kGoodTabWindowId));
+
+ EXPECT_CALL(*invocation.invocation_result_,
+ CreateTabValue(kGoodTabWindowId, -1)).
+ WillOnce(Return(true));
+ EXPECT_CALL(*invocation.invocation_result_, PostResult());
+
+ hr = invocation.CallContinueExecution(input_args);
+ EXPECT_HRESULT_SUCCEEDED(hr);
+
+ // Success with an index.
+ invocation.AllocateNewResult(kRequestId);
+ ApiDispatcher::InvocationResult* result = invocation.invocation_result_.get();
+ result->SetValue(ext::kUrlKey, Value::CreateStringValue(kUrl));
+ result->SetValue(ext::kIndexKey, Value::CreateIntegerValue(kIndex));
+
+ EXPECT_CALL(tab_api_result,
+ IsTabFromSameOrUnspecifiedFrameWindow(_, _, _, NotNull())).
+ WillOnce(DoAll(SetArgumentPointee<2>(kGoodTabWindow), Return(true)));
+ EXPECT_CALL(invocation.mock_api_dispatcher_,
+ GetTabIdFromHandle(kGoodTabWindow)).WillOnce(Return(kGoodTabWindowId));
+
+ StrictMock<testing::MockWindowUtils> window_utils;
+ AlwaysMockGetWindowExecutor(&invocation.mock_api_dispatcher_, kGoodTabWindow);
+ EXPECT_CALL(window_utils, GetTopLevelParent(kGoodTabWindow)).
+ WillOnce(Return(kGoodTabWindow));
+
+ EXPECT_CALL(*mock_window_executor_,
+ MoveTab(reinterpret_cast<CeeeWindowHandle>(kGoodTabWindow), kIndex)).
+ WillOnce(Return(S_OK));
+
+ EXPECT_CALL(*invocation.invocation_result_,
+ CreateTabValue(kGoodTabWindowId, kIndex)).
+ WillOnce(Return(true));
+ EXPECT_CALL(*invocation.invocation_result_, PostResult());
+
+ hr = invocation.CallContinueExecution(input_args);
+ EXPECT_HRESULT_SUCCEEDED(hr);
+}
+
+class MockMoveTab : public StrictMock<MockApiInvocation<TabApiResult,
+ MockTabApiResult,
+ MoveTab> > {
+};
+
+TEST_F(TabApiTests, MoveTab) {
+ testing::LogDisabler no_dchecks;
+
+ // Empty list is not valid.
+ StrictMock<testing::MockWindowUtils> window_utils;
+ MockMoveTab invocation;
+ ListValue good_args;
+ invocation.AllocateNewResult(kRequestId);
+ EXPECT_CALL(*invocation.invocation_result_, PostError(_)).Times(1);
+ invocation.Execute(good_args, kRequestId);
+
+ // First entry should be an Integer.
+ invocation.AllocateNewResult(kRequestId);
+ DictionaryValue* good_args_dict = new DictionaryValue();
+ good_args.Append(good_args_dict);
+ EXPECT_CALL(*invocation.invocation_result_, PostError(_)).Times(1);
+ invocation.Execute(good_args, kRequestId);
+
+ // Good arg format, but not an IE Frame.
+ invocation.AllocateNewResult(kRequestId);
+ EXPECT_TRUE(good_args.Insert(0, Value::CreateIntegerValue(kGoodTabWindowId)));
+ EXPECT_CALL(window_utils, IsWindowClass(kGoodTabWindow, _)).
+ WillOnce(Return(false));
+ EXPECT_CALL(invocation.mock_api_dispatcher_,
+ GetTabHandleFromId(kGoodTabWindowId)).WillOnce(Return(kGoodTabWindow));
+ EXPECT_CALL(*invocation.invocation_result_, PostError(_)).Times(1);
+ invocation.Execute(good_args, kRequestId);
+
+ // Wrong second list value.
+ invocation.AllocateNewResult(kRequestId);
+ EXPECT_CALL(window_utils, IsWindowClass(kGoodTabWindow, _)).
+ WillRepeatedly(Return(true));
+ EXPECT_TRUE(good_args.Insert(1, Value::CreateNullValue()));
+ EXPECT_CALL(*invocation.invocation_result_, PostError(_)).Times(1);
+ EXPECT_CALL(invocation.mock_api_dispatcher_,
+ GetTabHandleFromId(kGoodTabWindowId)).WillOnce(Return(kGoodTabWindow));
+ invocation.Execute(good_args, kRequestId);
+
+ // Unsupported window id key.
+ invocation.AllocateNewResult(kRequestId);
+ good_args.Remove(1, NULL);
+ good_args_dict->SetInteger(ext::kWindowIdKey, kBadWindowId);
+ EXPECT_CALL(*invocation.invocation_result_, PostError(_)).Times(1);
+ EXPECT_CALL(invocation.mock_api_dispatcher_,
+ GetTabHandleFromId(kGoodTabWindowId)).WillOnce(Return(kGoodTabWindow));
+ invocation.Execute(good_args, kRequestId);
+
+ // Wrong format for index key
+ invocation.AllocateNewResult(kRequestId);
+ good_args_dict->Remove(ext::kWindowIdKey, NULL);
+ good_args_dict->SetBoolean(ext::kIndexKey, false);
+ EXPECT_CALL(*invocation.invocation_result_, PostError(_)).Times(1);
+ EXPECT_CALL(invocation.mock_api_dispatcher_,
+ GetTabHandleFromId(kGoodTabWindowId)).WillOnce(Return(kGoodTabWindow));
+ invocation.Execute(good_args, kRequestId);
+
+ // Can't get an executor
+ invocation.AllocateNewResult(kRequestId);
+ good_args_dict->SetInteger(ext::kIndexKey, kTabIndex);
+ EXPECT_CALL(window_utils, GetTopLevelParent(kGoodTabWindow)).
+ WillRepeatedly(Return(kGoodFrameWindow));
+ EXPECT_CALL(invocation.mock_api_dispatcher_,
+ GetExecutor(kGoodFrameWindow, _, _)).
+ WillOnce(SetArgumentPointee<2>(static_cast<void*>(NULL)));
+ EXPECT_CALL(invocation.mock_api_dispatcher_,
+ GetTabHandleFromId(kGoodTabWindowId)).WillOnce(Return(kGoodTabWindow));
+ EXPECT_CALL(*invocation.invocation_result_, PostError(_)).Times(1);
+ invocation.Execute(good_args, kRequestId);
+
+ // Get the Executor to fail
+ invocation.AllocateNewResult(kRequestId);
+ AlwaysMockGetWindowExecutor(&invocation.mock_api_dispatcher_,
+ kGoodFrameWindow);
+ EXPECT_CALL(*mock_window_executor_,
+ MoveTab(reinterpret_cast<CeeeWindowHandle>(kGoodTabWindow), kTabIndex)).
+ WillOnce(Return(E_FAIL));
+ EXPECT_CALL(*invocation.invocation_result_, PostError(_)).Times(1);
+ EXPECT_CALL(invocation.mock_api_dispatcher_,
+ GetTabHandleFromId(kGoodTabWindowId)).WillOnce(Return(kGoodTabWindow));
+ invocation.Execute(good_args, kRequestId);
+
+ // Success
+ invocation.AllocateNewResult(kRequestId);
+ EXPECT_CALL(*mock_window_executor_,
+ MoveTab(reinterpret_cast<CeeeWindowHandle>(kGoodTabWindow), kTabIndex)).
+ WillOnce(Return(S_OK));
+ EXPECT_CALL(*invocation.invocation_result_,
+ CreateTabValue(kGoodTabWindowId, kTabIndex)).
+ WillOnce(Return(true));
+ EXPECT_CALL(*invocation.invocation_result_, PostResult()).Times(1);
+ EXPECT_CALL(invocation.mock_api_dispatcher_,
+ GetTabHandleFromId(kGoodTabWindowId)).WillOnce(Return(kGoodTabWindow));
+ invocation.Execute(good_args, kRequestId);
+}
+
+class MockTabsInsertCode
+ : public StrictMock<MockApiInvocation<TabApiResult, MockTabApiResult,
+ TabsInsertCode> > {
+ public:
+ // Overloaded to change the type of the return value for easier testing.
+ StrictMock<MockTabApiResult>* CallExecuteImpl(const ListValue& args,
+ int request_id,
+ CeeeTabCodeType type,
+ int* tab_id,
+ HRESULT* hr) {
+ return static_cast<StrictMock<MockTabApiResult>*>(ExecuteImpl(
+ args, request_id, type, tab_id, hr));
+ }
+ // Declare this so that this is not an abstract class.
+ virtual void Execute(const ListValue& args, int request_id) {}
+};
+
+TEST_F(TabApiTests, TabsInsertCode) {
+ testing::LogDisabler no_dchecks;
+ StrictMock<testing::MockWindowUtils> window_utils;
+ int tab_id;
+ HRESULT hr;
+
+ // Empty list is not valid.
+ MockTabsInsertCode invocation;
+ invocation.AllocateNewResult(kRequestId);
+ EXPECT_CALL(*invocation.invocation_result_, PostError(_)).Times(1);
+ EXPECT_EQ(NULL, invocation.CallExecuteImpl(
+ ListValue(), kRequestId, kCeeeTabCodeTypeCss, &tab_id, &hr));
+
+ // First parameter should be an Integer.
+ ListValue good_args;
+ DictionaryValue* good_args_dict = new DictionaryValue();
+ good_args.Append(good_args_dict);
+ invocation.AllocateNewResult(kRequestId);
+ EXPECT_CALL(*invocation.invocation_result_, PostError(_)).Times(1);
+ EXPECT_EQ(NULL, invocation.CallExecuteImpl(
+ good_args, kRequestId, kCeeeTabCodeTypeCss, &tab_id, &hr));
+
+ // Good arg format, but no values in dictionary.
+ EXPECT_TRUE(good_args.Insert(0, Value::CreateIntegerValue(kGoodTabWindowId)));
+ invocation.AllocateNewResult(kRequestId);
+ EXPECT_CALL(*invocation.invocation_result_, PostError(_)).Times(1);
+ EXPECT_EQ(NULL, invocation.CallExecuteImpl(
+ good_args, kRequestId, kCeeeTabCodeTypeCss, &tab_id, &hr));
+
+ // Good arg format, but both code and file values in dictionary.
+ good_args_dict->SetString(ext::kCodeKey, "alert(5);");
+ good_args_dict->SetString(ext::kFileKey, "test.js");
+ invocation.AllocateNewResult(kRequestId);
+ EXPECT_CALL(*invocation.invocation_result_, PostError(_)).Times(1);
+ EXPECT_EQ(NULL, invocation.CallExecuteImpl(
+ good_args, kRequestId, kCeeeTabCodeTypeCss, &tab_id, &hr));
+
+ // Good arg format, but not an IE Frame.
+ good_args_dict->Remove(ext::kFileKey, NULL);
+ EXPECT_CALL(window_utils, IsWindowClass(kGoodTabWindow, _)).
+ WillOnce(Return(false));
+ invocation.AllocateNewResult(kRequestId);
+ EXPECT_CALL(*invocation.invocation_result_, PostError(_)).Times(1);
+ EXPECT_CALL(invocation.mock_api_dispatcher_,
+ GetTabHandleFromId(kGoodTabWindowId)).WillOnce(Return(kGoodTabWindow));
+ EXPECT_EQ(NULL, invocation.CallExecuteImpl(
+ good_args, kRequestId, kCeeeTabCodeTypeCss, &tab_id, &hr));
+
+ // Can't get an executor.
+ EXPECT_CALL(window_utils, IsWindowClass(kGoodTabWindow, _)).
+ WillRepeatedly(Return(true));
+ EXPECT_CALL(invocation.mock_api_dispatcher_,
+ GetExecutor(kGoodTabWindow, _, _)).
+ WillOnce(SetArgumentPointee<2>(static_cast<void*>(NULL)));
+ invocation.AllocateNewResult(kRequestId);
+ EXPECT_CALL(*invocation.invocation_result_, PostError(_)).Times(1);
+ EXPECT_CALL(invocation.mock_api_dispatcher_,
+ GetTabHandleFromId(kGoodTabWindowId)).WillOnce(Return(kGoodTabWindow));
+ EXPECT_EQ(NULL, invocation.CallExecuteImpl(
+ good_args, kRequestId, kCeeeTabCodeTypeCss, &tab_id, &hr));
+
+ // Executor failure, all_frames defaulted to false.
+ AlwaysMockGetTabExecutor(&invocation.mock_api_dispatcher_, kGoodTabWindow);
+ EXPECT_CALL(*mock_tab_executor_,
+ InsertCode(_, _, false, kCeeeTabCodeTypeCss)).
+ WillOnce(Return(E_FAIL));
+ EXPECT_CALL(invocation.mock_api_dispatcher_,
+ GetTabHandleFromId(kGoodTabWindowId)).WillOnce(Return(kGoodTabWindow));
+ invocation.AllocateNewResult(kRequestId);
+ scoped_ptr<StrictMock<MockTabApiResult>> result(invocation.CallExecuteImpl(
+ good_args, kRequestId, kCeeeTabCodeTypeCss, &tab_id, &hr));
+ EXPECT_EQ(kGoodTabWindowId, tab_id);
+ EXPECT_EQ(E_FAIL, hr);
+
+ // Success, all_frames defaulted to false.
+ AlwaysMockGetTabExecutor(&invocation.mock_api_dispatcher_, kGoodTabWindow);
+ EXPECT_CALL(*mock_tab_executor_,
+ InsertCode(_, _, false, kCeeeTabCodeTypeCss)).
+ WillRepeatedly(Return(S_OK));
+ EXPECT_CALL(invocation.mock_api_dispatcher_,
+ GetTabHandleFromId(kGoodTabWindowId)).WillOnce(Return(kGoodTabWindow));
+ invocation.AllocateNewResult(kRequestId);
+ result.reset(invocation.CallExecuteImpl(
+ good_args, kRequestId, kCeeeTabCodeTypeCss, &tab_id, &hr));
+ EXPECT_EQ(tab_id, kGoodTabWindowId);
+ EXPECT_HRESULT_SUCCEEDED(hr);
+
+ // Success, set all_frames to true.
+ good_args_dict->SetBoolean(ext::kAllFramesKey, true);
+ EXPECT_CALL(*mock_tab_executor_, InsertCode(_, _, true,
+ kCeeeTabCodeTypeJs)).WillOnce(Return(S_OK));
+ EXPECT_CALL(invocation.mock_api_dispatcher_,
+ GetTabHandleFromId(kGoodTabWindowId)).WillOnce(Return(kGoodTabWindow));
+ invocation.AllocateNewResult(kRequestId);
+ result.reset(invocation.CallExecuteImpl(
+ good_args, kRequestId, kCeeeTabCodeTypeJs, &tab_id, &hr));
+ EXPECT_EQ(tab_id, kGoodTabWindowId);
+ EXPECT_HRESULT_SUCCEEDED(hr);
+}
+
+TEST_F(TabApiTests, IsTabFromSameOrUnspecifiedFrameWindow) {
+ // We need a mock for this now.
+ StrictMock<MockApiDispatcher> mock_api_dispatcher;
+
+ // We expect these calls repeatedly
+ EXPECT_CALL(mock_api_dispatcher, GetTabHandleFromId(kGoodTabWindowId)).
+ WillRepeatedly(Return(kGoodTabWindow));
+ EXPECT_CALL(mock_api_dispatcher, GetWindowIdFromHandle(kGoodFrameWindow)).
+ WillRepeatedly(Return(kGoodFrameWindowId));
+ EXPECT_CALL(mock_api_dispatcher, GetWindowHandleFromId(kGoodFrameWindowId)).
+ WillRepeatedly(Return(kGoodFrameWindow));
+
+ // We always need a kIdKey value in the input_dict.
+ DictionaryValue input_dict;
+ input_dict.SetInteger(ext::kIdKey, kGoodTabWindowId);
+ // Start with no saved dict, so any input value is good.
+ EXPECT_TRUE(TabApiResult::IsTabFromSameOrUnspecifiedFrameWindow(
+ input_dict, NULL, NULL, &mock_api_dispatcher));
+ // Also test that we are properly returned the input value.
+ HWND tab_window = NULL;
+ EXPECT_TRUE(TabApiResult::IsTabFromSameOrUnspecifiedFrameWindow(
+ input_dict, NULL, &tab_window, &mock_api_dispatcher));
+ EXPECT_EQ(kGoodTabWindow, tab_window);
+
+ // Now check with the same value found as a grand parent.
+ FundamentalValue saved_window(kGoodFrameWindowId);
+ StrictMock<testing::MockWindowUtils> window_utils;
+ EXPECT_CALL(window_utils, GetTopLevelParent(kGoodTabWindow)).
+ WillRepeatedly(Return(kGoodFrameWindow));
+ EXPECT_TRUE(TabApiResult::IsTabFromSameOrUnspecifiedFrameWindow(
+ input_dict, &saved_window, NULL, &mock_api_dispatcher));
+ tab_window = NULL;
+ EXPECT_TRUE(TabApiResult::IsTabFromSameOrUnspecifiedFrameWindow(
+ input_dict, &saved_window, &tab_window, &mock_api_dispatcher));
+ EXPECT_EQ(kGoodTabWindow, tab_window);
+
+ // Now check with the same value provided in the input_dict.
+ input_dict.SetInteger(ext::kWindowIdKey, kGoodFrameWindowId);
+ EXPECT_TRUE(TabApiResult::IsTabFromSameOrUnspecifiedFrameWindow(
+ input_dict, &saved_window, NULL, &mock_api_dispatcher));
+ tab_window = NULL;
+ EXPECT_TRUE(TabApiResult::IsTabFromSameOrUnspecifiedFrameWindow(
+ input_dict, &saved_window, &tab_window, &mock_api_dispatcher));
+ EXPECT_EQ(kGoodTabWindow, tab_window);
+
+ // And now check the cases where they differ.
+ FundamentalValue other_saved_window(kGoodFrameWindowId + 1);
+ EXPECT_FALSE(TabApiResult::IsTabFromSameOrUnspecifiedFrameWindow(
+ input_dict, &other_saved_window, NULL, &mock_api_dispatcher));
+ tab_window = NULL;
+ EXPECT_FALSE(TabApiResult::IsTabFromSameOrUnspecifiedFrameWindow(
+ input_dict, &other_saved_window, &tab_window, &mock_api_dispatcher));
+ EXPECT_EQ(kGoodTabWindow, tab_window);
+
+ input_dict.Remove(ext::kWindowIdKey, NULL);
+ EXPECT_FALSE(TabApiResult::IsTabFromSameOrUnspecifiedFrameWindow(
+ input_dict, &other_saved_window, NULL, &mock_api_dispatcher));
+ tab_window = NULL;
+ EXPECT_FALSE(TabApiResult::IsTabFromSameOrUnspecifiedFrameWindow(
+ input_dict, &other_saved_window, &tab_window, &mock_api_dispatcher));
+ EXPECT_EQ(kGoodTabWindow, tab_window);
+}
+
+} // namespace
diff --git a/ceee/ie/broker/webnavigation_api_module.cc b/ceee/ie/broker/webnavigation_api_module.cc
new file mode 100644
index 0000000..e35bc51
--- /dev/null
+++ b/ceee/ie/broker/webnavigation_api_module.cc
@@ -0,0 +1,40 @@
+// 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.
+//
+// The part of WebNavigation implementation that needs to reside on the broker
+// side.
+
+#include "ceee/ie/broker/webnavigation_api_module.h"
+
+#include "ceee/ie/broker/api_dispatcher.h"
+#include "ceee/ie/broker/api_module_util.h"
+#include "chrome/browser/extensions/extension_webnavigation_api_constants.h"
+
+namespace ext = extension_webnavigation_api_constants;
+
+namespace webnavigation_api {
+
+void RegisterInvocations(ApiDispatcher* dispatcher) {
+ // Register the permanent event handlers.
+ dispatcher->RegisterPermanentEventHandler(
+ ext::kOnBeforeNavigate,
+ api_module_util::ConvertTabIdInDictionary<ext::kTabIdKey>);
+ dispatcher->RegisterPermanentEventHandler(
+ ext::kOnBeforeRetarget,
+ api_module_util::ConvertTabIdInDictionary<ext::kSourceTabIdKey>);
+ dispatcher->RegisterPermanentEventHandler(
+ ext::kOnCommitted,
+ api_module_util::ConvertTabIdInDictionary<ext::kTabIdKey>);
+ dispatcher->RegisterPermanentEventHandler(
+ ext::kOnCompleted,
+ api_module_util::ConvertTabIdInDictionary<ext::kTabIdKey>);
+ dispatcher->RegisterPermanentEventHandler(
+ ext::kOnDOMContentLoaded,
+ api_module_util::ConvertTabIdInDictionary<ext::kTabIdKey>);
+ dispatcher->RegisterPermanentEventHandler(
+ ext::kOnErrorOccurred,
+ api_module_util::ConvertTabIdInDictionary<ext::kTabIdKey>);
+}
+
+} // namespace webnavigation_api
diff --git a/ceee/ie/broker/webnavigation_api_module.h b/ceee/ie/broker/webnavigation_api_module.h
new file mode 100644
index 0000000..5f7c24e
--- /dev/null
+++ b/ceee/ie/broker/webnavigation_api_module.h
@@ -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.
+//
+// The part of WebNavigation implementation that needs to reside on the broker
+// side.
+
+#ifndef CEEE_IE_BROKER_WEBNAVIGATION_API_MODULE_H_
+#define CEEE_IE_BROKER_WEBNAVIGATION_API_MODULE_H_
+
+class ApiDispatcher;
+
+namespace webnavigation_api {
+
+// Registers permanent event handlers to convert tab window handles in event
+// arguments into actual tab IDs.
+void RegisterInvocations(ApiDispatcher* dispatcher);
+
+} // namespace webnavigation_api
+
+#endif // CEEE_IE_BROKER_WEBNAVIGATION_API_MODULE_H_
diff --git a/ceee/ie/broker/webrequest_api_module.cc b/ceee/ie/broker/webrequest_api_module.cc
new file mode 100644
index 0000000..77c1177
--- /dev/null
+++ b/ceee/ie/broker/webrequest_api_module.cc
@@ -0,0 +1,25 @@
+// 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.
+//
+// The part of WebRequest implementation that needs to reside on the broker
+// side.
+
+#include "ceee/ie/broker/webrequest_api_module.h"
+
+#include "ceee/ie/broker/api_dispatcher.h"
+#include "ceee/ie/broker/api_module_util.h"
+#include "chrome/browser/extensions/extension_webrequest_api_constants.h"
+
+namespace ext = extension_webrequest_api_constants;
+
+namespace webrequest_api {
+
+void RegisterInvocations(ApiDispatcher* dispatcher) {
+ // Register the permanent event handler.
+ dispatcher->RegisterPermanentEventHandler(
+ ext::kOnBeforeRequest,
+ api_module_util::ConvertTabIdInDictionary<ext::kTabIdKey>);
+}
+
+} // namespace webrequest_api
diff --git a/ceee/ie/broker/webrequest_api_module.h b/ceee/ie/broker/webrequest_api_module.h
new file mode 100644
index 0000000..9ace96c
--- /dev/null
+++ b/ceee/ie/broker/webrequest_api_module.h
@@ -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.
+//
+// The part of WebRequest implementation that needs to reside on the broker
+// side.
+
+#ifndef CEEE_IE_BROKER_WEBREQUEST_API_MODULE_H_
+#define CEEE_IE_BROKER_WEBREQUEST_API_MODULE_H_
+
+class ApiDispatcher;
+
+namespace webrequest_api {
+
+// Registers permanent event handlers to convert tab window handles in event
+// arguments into actual tab IDs.
+void RegisterInvocations(ApiDispatcher* dispatcher);
+
+} // namespace webrequest_api
+
+#endif // CEEE_IE_BROKER_WEBREQUEST_API_MODULE_H_
diff --git a/ceee/ie/broker/window_api_module.cc b/ceee/ie/broker/window_api_module.cc
new file mode 100644
index 0000000..2115163
--- /dev/null
+++ b/ceee/ie/broker/window_api_module.cc
@@ -0,0 +1,639 @@
+// 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 API implementation.
+//
+// Window IDs are the HWND of the top-level frame window of IE.
+
+#include "ceee/ie/broker/window_api_module.h"
+
+#include <atlbase.h>
+#include <iepmapi.h>
+#include <sddl.h>
+#include <set>
+
+#include "base/json/json_reader.h"
+#include "base/logging.h"
+#include "base/string_number_conversions.h"
+#include "base/string_util.h"
+#include "base/utf_string_conversions.h"
+#include "ceee/common/com_utils.h"
+#include "ceee/common/process_utils_win.h"
+#include "ceee/common/windows_constants.h"
+#include "ceee/common/window_utils.h"
+#include "ceee/ie/broker/api_module_constants.h"
+#include "ceee/ie/broker/api_module_util.h"
+#include "ceee/ie/broker/tab_api_module.h"
+#include "ceee/ie/common/ie_util.h"
+#include "ceee/ie/common/api_registration.h"
+#include "chrome/browser/extensions/extension_event_names.h"
+#include "chrome/browser/extensions/extension_tabs_module_constants.h"
+#include "chrome/common/url_constants.h"
+#include "chrome/common/extensions/extension_error_utils.h"
+#include "googleurl/src/gurl.h"
+
+namespace ext = extension_tabs_module_constants;
+namespace ext_event_names = extension_event_names;
+
+namespace window_api {
+
+// We sometimes need to wait for window creation and removal to be completed
+// even though we receive an event about them, these events sometimes arrive
+// before the creation/removal is really totally completed.
+const int kMaxDelayMs = 5000; // 5 seconds may be needed on slow machines.
+const int kDelayMs = 50;
+
+void RegisterInvocations(ApiDispatcher* dispatcher) {
+#define REGISTER_API_FUNCTION(func) do { dispatcher->RegisterInvocation(\
+ func##Function::function_name(), NewApiInvocation< func >); } while (false)
+ REGISTER_WINDOW_API_FUNCTIONS();
+#undef REGISTER_API_FUNCTION
+ dispatcher->RegisterInvocation(CreateWindowFunction::function_name(),
+ NewApiInvocation< CreateWindowFunc >);
+ // And now register the permanent event handlers.
+ dispatcher->RegisterPermanentEventHandler(ext_event_names::kOnWindowRemoved,
+ RemoveWindow::EventHandler);
+ dispatcher->RegisterPermanentEventHandler(ext_event_names::kOnWindowCreated,
+ CreateWindowFunc::EventHandler);
+}
+
+bool WindowApiResult::UpdateWindowRect(HWND window,
+ const DictionaryValue* window_props) {
+ ApiDispatcher* dispatcher = GetDispatcher();
+ DCHECK(dispatcher != NULL);
+ int window_id = dispatcher->GetWindowIdFromHandle(window);
+ if (window_utils::WindowHasNoThread(window)) {
+ PostError(ExtensionErrorUtils::FormatErrorMessage(
+ ext::kWindowNotFoundError, base::IntToString(window_id)));
+ return false;
+ }
+
+ if (!IsIeFrameClass(window)) {
+ PostError(ExtensionErrorUtils::FormatErrorMessage(
+ ext::kWindowNotFoundError, base::IntToString(window_id)));
+ return false;
+ }
+
+ // Unspecified entries are set to -1 to let the executor know not to change
+ // them.
+ long left = -1;
+ long top = -1;
+ long width = -1;
+ long height = -1;
+
+ if (window_props) {
+ int dim;
+ if (window_props->HasKey(ext::kLeftKey)) {
+ if (!window_props->GetInteger(ext::kLeftKey, &dim)) {
+ PostError("bad request");
+ return false;
+ }
+ left = dim;
+ }
+
+ if (window_props->HasKey(ext::kTopKey)) {
+ if (!window_props->GetInteger(ext::kTopKey, &dim)) {
+ PostError("bad request");
+ return false;
+ }
+ top = dim;
+ }
+
+ if (window_props->HasKey(ext::kWidthKey)) {
+ if (!window_props->GetInteger(ext::kWidthKey, &dim) || dim < 0) {
+ PostError("bad request");
+ return false;
+ }
+ width = dim;
+ }
+
+ if (window_props->HasKey(ext::kHeightKey)) {
+ if (!window_props->GetInteger(ext::kHeightKey, &dim) || dim < 0) {
+ PostError("bad request");
+ return false;
+ }
+ height = dim;
+ }
+ }
+
+ common_api::WindowInfo window_info;
+ if (left != -1 || top != -1 || width != -1 || height != -1) {
+ CComPtr<ICeeeWindowExecutor> executor;
+ dispatcher->GetExecutor(window, IID_ICeeeWindowExecutor,
+ reinterpret_cast<void**>(&executor));
+ if (executor == NULL) {
+ LOG(WARNING) << "Failed to get an executor to update window.";
+ PostError(api_module_constants::kInternalErrorError);
+ return false;
+ }
+
+ HRESULT hr = executor->UpdateWindow(left, top, width, height, &window_info);
+ if (FAILED(hr)) {
+ LOG(ERROR) << "Couldn't update window: " << std::hex << window << ". " <<
+ com::LogHr(hr);
+ PostError(api_module_constants::kInternalErrorError);
+ return false;
+ }
+ SetResultFromWindowInfo(window, window_info, false);
+ return true;
+ } else {
+ return CreateWindowValue(window, false);
+ }
+}
+
+void GetWindow::Execute(const ListValue& args, int request_id) {
+ scoped_ptr<WindowApiResult> result(CreateApiResult(request_id));
+ int window_id = 0;
+ if (!args.GetInteger(0, &window_id)) {
+ NOTREACHED() << "bad message";
+ result->PostError(api_module_constants::kInvalidArgumentsError);
+ return;
+ }
+
+ // CreateWindowValue validates the HWND.
+ ApiDispatcher* dispatcher = GetDispatcher();
+ DCHECK(dispatcher != NULL);
+ HWND window = dispatcher->GetWindowHandleFromId(window_id);
+ if (result->CreateWindowValue(window, false))
+ result->PostResult();
+}
+
+void GetCurrentWindow::Execute(const ListValue& args, int request_id) {
+ // TODO(mad@chromium.org): We currently don't have access to the
+ // actual 'current' window from the point of view of the extension
+ // API caller. Use the top window for now. bb2255140
+ scoped_ptr<WindowApiResult> result(CreateApiResult(request_id));
+ if (result->CreateWindowValue(result->TopIeWindow(), false))
+ result->PostResult();
+}
+
+void GetLastFocusedWindow::Execute(const ListValue& args, int request_id) {
+ scoped_ptr<WindowApiResult> result(CreateApiResult(request_id));
+ if (result->CreateWindowValue(result->TopIeWindow(), false))
+ result->PostResult();
+}
+
+bool CreateWindowFunc::EventHandler(const std::string& input_args,
+ std::string* converted_args,
+ ApiDispatcher* dispatcher) {
+ DCHECK(converted_args);
+ // We don't need to modify anything in the arguments, we just want to delay.
+ *converted_args = input_args;
+
+ scoped_ptr<ListValue> args_list;
+ int window_id = 0;
+ if (!api_module_util::GetListAndDictIntValue(
+ input_args, ext::kIdKey, &args_list, &window_id)) {
+ NOTREACHED() << "Event arguments wasn't a dictionary with an ID in it. " <<
+ input_args;
+ return false;
+ }
+
+ // The hook may call us before the window is completely created, so we
+ // must delay the execution until the first tab is completely created.
+ // TODO(mad@chromium.org): Find a way to do this without blocking
+ // all other ApiDispatching.
+ HWND window = dispatcher->GetWindowHandleFromId(window_id);
+ int waited_ms = 0;
+ while (waited_ms < kMaxDelayMs &&
+ ::IsWindow(window) &&
+ !window_utils::FindDescendentWindow(
+ window, windows::kIeTabWindowClass, false, NULL)) {
+ ::SleepEx(kDelayMs, TRUE); // TRUE = Alertable.
+ waited_ms += kDelayMs;
+ }
+ // We don't DCHECK if the window died, but we must still return false
+ // if it did, so that we don't broadcast the event back to Chrome.
+ DCHECK(waited_ms < kMaxDelayMs || !::IsWindow(window));
+ return waited_ms < kMaxDelayMs && ::IsWindow(window);
+}
+
+void CreateWindowFunc::Execute(const ListValue& args, int request_id) {
+ scoped_ptr<WindowApiResult> result(CreateApiResult(request_id));
+ scoped_ptr<DictionaryValue> input_dict;
+ // The input dictionary is optional; if not provided, the args may be
+ // either an empty list or a list with a single null value element.
+ if (args.GetSize() > 0) {
+ Value* first_arg = NULL;
+ if (!args.Get(0, &first_arg) || first_arg == NULL) {
+ NOTREACHED() << "bad request";
+ result->PostError(api_module_constants::kInvalidArgumentsError);
+ return;
+ }
+ if (first_arg->GetType() != Value::TYPE_NULL) {
+ DictionaryValue* args_dict = NULL;
+ if (!args.GetDictionary(0, &args_dict) || args_dict == NULL) {
+ NOTREACHED() << "bad request";
+ result->PostError(api_module_constants::kInvalidArgumentsError);
+ return;
+ }
+ // Remember the arguments so that we can use them later.
+ input_dict.reset(static_cast<DictionaryValue*>(args_dict->DeepCopy()));
+ }
+ }
+
+ // Look for optional url.
+ scoped_ptr<GURL> spec(new GURL(chrome::kAboutBlankURL));
+ std::string url_input;
+ if (input_dict.get() != NULL) {
+ if (input_dict->HasKey(ext::kUrlKey)) {
+ if (!input_dict->GetString(ext::kUrlKey, &url_input)) {
+ NOTREACHED() << "bad request";
+ result->PostError(api_module_constants::kInvalidArgumentsError);
+ return;
+ }
+
+ spec.reset(new GURL(url_input));
+ if (!spec->is_valid()) {
+ result->PostError(ExtensionErrorUtils::FormatErrorMessage(
+ ext::kInvalidUrlError, url_input).c_str());
+ return;
+ }
+ }
+ }
+
+ // There are many ways to create new IE windows, but they don't all behave
+ // the same depending on the IE version, OS or even settings. The most
+ // reliable way we found was to CoCreate an instance of IE, but navigating
+ // this instance doesn't behave exactly the same on all platforms.
+ //
+ // The main problem is with protected mode. If an instance of IE is launched
+ // with protected mode on, and we navigate to a URL that doesn't need
+ // protection (or vice versa), then the navigation will need to occur in
+ // another browser, not the one that was launched in the inappropriate
+ // protected mode.
+ //
+ // On IE7 if you CoCreate an instance of IE from a process running at an
+ // elevated integrity level as our Broker is, you get an unprotected mode
+ // IE, and if you navigate to a URL that needs protected mode, it will either
+ // create a new IE with the proper protected mode, or re-use an existing one
+ // if one is already opened.
+ //
+ // On IE8 the current process' integrity level is not taken into account
+ // when CoCreating a new IE, it relies on the CLSID which is the regular one
+ // (CLSID_InternetExplorer) for protected mode and a new one used for running
+ // at medium integrity level (CLSID_InternetExplorerMedium). Also, if you
+ // would then navigate to a URL that has a different protected mode than the
+ // one used for the CoCreate, then, a new window will always be created, an
+ // existing one will never be used.
+ //
+ // The other alternatives we looked at was to
+ // 1) Explicitly take the IWebBrowser2 interface of an existing IE.
+ // But this can cause the navigate to create a new tab instead
+ // of a new window (even if we specified navOpenInNewWindow) if
+ // the tab settings specify that popups should be opened in a
+ // new tab as opposed to a new window.
+ // 2) Use the IELaunchURL API (available in iepmapi.h).
+ // Except from the fact that it always creates a set of new
+ // IE processes in their own individual session this would
+ // behave exactly as we would like... But the price is too high.
+ // And it isn't available on XP...
+ // 3) Still use CoCreateInstance but don't rely on the knowledge of the
+ // current OS or IE version but rely on notifications sent to the
+ // DWebBrowser2 events thingy. Experimenting with these was not trivial
+ // and in some case lead to a non-deterministic way of identifying if
+ // a new window had been created or not.
+
+ // We need to know whether we will navigate to a URL that needs protected
+ // mode enabled or not. On earlier versions of the OS (e.g., pre-Vista),
+ // this call simply fails and thus act as if we didn't need protected mode.
+ std::wstring url = UTF8ToWide(spec->spec());
+ HRESULT protected_mode_url_hr = ::IEIsProtectedModeURL(url.c_str());
+
+ // We default to CLSID_InternetExplorer and CLSCTX_ALL but we may need to
+ // switch to CLSID_InternetExplorerMedium or add CLSCTX_ENABLE_CLOAKING later.
+ DWORD class_context = CLSCTX_ALL;
+ CLSID ie_clsid = CLSID_InternetExplorer;
+ bool lowered_integrity_level = false;
+ bool impersonating = false;
+ if (protected_mode_url_hr != S_OK) { // S_FALSE is returned for no.
+ // When we don't need protected mode, we need to explicitly
+ // request that IE8 starts the medium integrity version.
+ if (ie_util::GetIeVersion() == ie_util::IEVERSION_IE8)
+ ie_clsid = CLSID_InternetExplorerMedium;
+ } else if (ie_util::GetIeVersion() == ie_util::IEVERSION_IE7) {
+ // IE7 in protected mode needs to be started at lower integrity level
+ // than the broker process. So we must enable cloaking and temporary bring
+ // down the integrity level of this thread.
+ class_context |= CLSCTX_ENABLE_CLOAKING;
+ // We would have liked to use the CAccessToken class from ATL but it doesn't
+ // support the integrity level impersonation, just the owner, primary group
+ // and DACL. So we do it ourselves.
+ if (!::ImpersonateSelf(SecurityImpersonation)) {
+ DCHECK(false) << com::LogWe();
+ return;
+ }
+ // Remember that we successfully impersonated, so we can RevertToSelf.
+ impersonating = true;
+ // This call fails on XP, so we don't look at the error, we just log it and
+ // remember our success so we can revert it later. Specifying NULL for the
+ // thread pointer means that we want to affect the current thread.
+ HRESULT hr_lowered_integrity_level =
+ process_utils_win::SetThreadIntegrityLevel(NULL, SDDL_ML_LOW);
+ lowered_integrity_level = SUCCEEDED(hr_lowered_integrity_level);
+ DLOG_IF(WARNING, !lowered_integrity_level) <<
+ "SetThreadIntegrityLevelLow" << com::LogHr(hr_lowered_integrity_level);
+ }
+
+ // Now we can create a new web browser and be sure it will be the one that is
+ // kept (as well as its window) once we navigate.
+ CComPtr<IWebBrowser2> web_browser;
+ HRESULT hr = web_browser.CoCreateInstance(ie_clsid, NULL, class_context);
+ DCHECK(SUCCEEDED(hr)) << "Can't CoCreate IE! " << com::LogHr(hr);
+ if (FAILED(hr)) {
+ result->PostError(api_module_constants::kInternalErrorError);
+ return;
+ }
+
+ // And now we can bring back the integrity level to where it was.
+ if (lowered_integrity_level) {
+ HRESULT hr_integrity_level =
+ process_utils_win::ResetThreadIntegrityLevel(NULL);
+ DCHECK(SUCCEEDED(hr_integrity_level)) << "Failed to bring back thread " <<
+ "integrity level! " << com::LogHr(hr_integrity_level);
+ LOG_IF(WARNING, FAILED(hr)) << "ResetThreadIntegrityLevel(NULL) " <<
+ com::LogHr(hr_integrity_level);
+ }
+
+ // And stop impersonating.
+ if (impersonating) {
+ BOOL success = ::RevertToSelf();
+ DCHECK(success) << "Failed to stop impersonating! " << com::LogWe();
+ LOG_IF(WARNING, !success) << "RevertToSelf() " << com::LogWe();
+ }
+
+ // We need the HWND to create the window value to fill the info needed
+ // by the callback, and also, to potentially resize and position it.
+ HWND web_browserhwnd = NULL;
+ hr = web_browser->get_HWND(reinterpret_cast<SHANDLE_PTR*>(&web_browserhwnd));
+ DCHECK(SUCCEEDED(hr)) << "Can't get HWND!" << com::LogHr(hr);
+ if (FAILED(hr)) {
+ result->PostError(api_module_constants::kInternalErrorError);
+ return;
+ }
+
+ if (input_dict.get() == NULL) {
+ // No arguments, so no need to popupize, resize or reposition.
+ if (!result->CreateWindowValue(web_browserhwnd, false)) {
+ // CreateWindowValue will have posted the error if any.
+ return;
+ }
+ } else {
+ // Popupize if needed.
+ std::string window_type;
+ if (input_dict->GetString(ext::kWindowTypeKey, &window_type) &&
+ window_type == ext::kWindowTypeValuePopup) {
+ HRESULT hr = web_browser->put_AddressBar(VARIANT_FALSE);
+ DCHECK(SUCCEEDED(hr)) << "Failed to hide address bar. " << com::LogHr(hr);
+ hr = web_browser->put_StatusBar(VARIANT_FALSE);
+ DCHECK(SUCCEEDED(hr)) << "Failed to hide status bar. " << com::LogHr(hr);
+ hr = web_browser->put_ToolBar(FALSE);
+ DCHECK(SUCCEEDED(hr)) << "Failed put_ToolBar. " << com::LogHr(hr);
+ }
+ // Reposition and Resize if needed.
+ if (!result->UpdateWindowRect(web_browserhwnd, input_dict.get())) {
+ // UpdateWindowRect will have posted the error if any.
+ return;
+ }
+ }
+
+ // Now we can Navigate to the requested url.
+ hr = web_browser->Navigate(CComBSTR(url.c_str()),
+ &CComVariant(), // unused flags
+ &CComVariant(L"_top"), // Target frame
+ &CComVariant(), // Unused POST DATA
+ &CComVariant()); // Unused Headers
+ DCHECK(SUCCEEDED(hr)) << "Can't Navigate IE to " << url << com::LogHr(hr);
+ if (FAILED(hr)) {
+ result->PostError(api_module_constants::kInternalErrorError);
+ return;
+ }
+
+ // A CoCreated IE is not visible until we ask it to be.
+ hr = web_browser->put_Visible(VARIANT_TRUE);
+ DCHECK(SUCCEEDED(hr)) << "put_Visible: " << com::LogHr(hr);
+ if (FAILED(hr)) {
+ result->PostError(api_module_constants::kInternalErrorError);
+ return;
+ }
+ result->PostResult();
+}
+
+void UpdateWindow::Execute(const ListValue& args, int request_id) {
+ scoped_ptr<WindowApiResult> result(CreateApiResult(request_id));
+ int window_id = 0;
+ DictionaryValue* update_props;
+ if (!args.GetInteger(0, &window_id) ||
+ !args.GetDictionary(1, &update_props)) {
+ NOTREACHED() << "bad message";
+ result->PostError(api_module_constants::kInvalidArgumentsError);
+ return;
+ }
+
+ ApiDispatcher* dispatcher = GetDispatcher();
+ DCHECK(dispatcher != NULL);
+ HWND window = dispatcher->GetWindowHandleFromId(window_id);
+ if (!result->IsIeFrameClass(window)) {
+ LOG(WARNING) << "Extension trying to access non-IE or dying window: " <<
+ std::hex << window_id;
+ result->PostError(ExtensionErrorUtils::FormatErrorMessage(
+ ext::kWindowNotFoundError, base::IntToString(window_id)).c_str());
+ return;
+ }
+
+ if (result->UpdateWindowRect(window, update_props))
+ result->PostResult();
+}
+
+void RemoveWindow::Execute(const ListValue& args, int request_id) {
+ scoped_ptr<WindowApiResult> result(CreateApiResult(request_id));
+ int window_id = 0;
+ if (!args.GetInteger(0, &window_id)) {
+ NOTREACHED() << "bad message";
+ result->PostError(api_module_constants::kInvalidArgumentsError);
+ return;
+ }
+
+ ApiDispatcher* dispatcher = GetDispatcher();
+ DCHECK(dispatcher != NULL);
+ HWND window = dispatcher->GetWindowHandleFromId(window_id);
+ if (!result->IsIeFrameClass(window)) {
+ LOG(WARNING) << "Extension trying to access non-IE or dying window: " <<
+ std::hex << window_id;
+ result->PostError(ExtensionErrorUtils::FormatErrorMessage(
+ ext::kWindowNotFoundError, base::IntToString(window_id)));
+ return;
+ }
+
+ CComPtr<ICeeeWindowExecutor> executor;
+ dispatcher->GetExecutor(window, IID_ICeeeWindowExecutor,
+ reinterpret_cast<void**>(&executor));
+ if (executor == NULL) {
+ LOG(WARNING) << "Failed to get an executor to remove window.";
+ result->PostError(api_module_constants::kInternalErrorError);
+ return;
+ }
+
+ HRESULT hr = executor->RemoveWindow();
+ if (FAILED(hr)) {
+ LOG(ERROR) << "Executor failed to remove window: " << std::hex <<
+ window_id << ". " << com::LogHr(hr);
+ result->PostError("Internal Error trying to close window.");
+ return;
+ }
+
+ // S_FALSE is returned when there are no tabs to close, e.g., the initial
+ // tab has not finished loading by the time we are asked to close the window.
+ if (hr == S_FALSE) {
+ LOG(WARNING) << "Failed to get window manager to close the window, "
+ "trying WM_CLOSE instead." << com::LogHr(hr);
+ // We fall back to this to be slightly more robust, but this approach has
+ // the drawback that it shows a pop-up dialog with a message like "do you
+ // want to close N tabs" if there is more than one tab. And we may need
+ // to try a few times because we have seen cases where SendMessage didn't
+ // return 0 because the window couldn't process the message for some reason.
+ int waited_ms = 0;
+ while (waited_ms < kMaxDelayMs && ::SendMessage(window, WM_CLOSE, 0, 0)) {
+ ::SleepEx(kDelayMs, TRUE); // Alertable.
+ waited_ms += kDelayMs;
+ }
+
+ DCHECK(waited_ms < kMaxDelayMs);
+ if (waited_ms >= kMaxDelayMs) {
+ result->PostError(api_module_constants::kInternalErrorError);
+ return;
+ }
+ }
+
+ // Now we must wait for the window removal to be completely done before
+ // posting the response back to Chrome Frame.
+ // And we remember the window identifier so that we can recognize the event.
+ result->SetValue(ext::kWindowIdKey, Value::CreateIntegerValue(window_id));
+ DCHECK(dispatcher != NULL);
+ dispatcher->RegisterEphemeralEventHandler(ext_event_names::kOnWindowRemoved,
+ RemoveWindow::ContinueExecution,
+ result.release());
+}
+
+HRESULT RemoveWindow::ContinueExecution(
+ const std::string& input_args,
+ ApiDispatcher::InvocationResult* user_data,
+ ApiDispatcher* dispatcher) {
+ DCHECK(dispatcher != NULL);
+ DCHECK(user_data != NULL);
+
+ scoped_ptr<WindowApiResult> result(static_cast<WindowApiResult*>(user_data));
+
+ scoped_ptr<ListValue> args_list;
+ int window_id = 0;
+ if (!api_module_util::GetListAndIntegerValue(input_args, &args_list,
+ &window_id)) {
+ NOTREACHED() << "Event arguments are not a list with an integer in it.";
+ result->PostError(api_module_constants::kInternalErrorError);
+ return E_INVALIDARG;
+ }
+
+ const Value* saved_window_value = result->GetValue(ext::kWindowIdKey);
+ DCHECK(saved_window_value != NULL &&
+ saved_window_value->IsType(Value::TYPE_INTEGER));
+ int saved_window_id = 0;
+ bool success = saved_window_value->GetAsInteger(&saved_window_id);
+ DCHECK(success && saved_window_id != 0);
+ if (saved_window_id == window_id) {
+ // The windows.remove callback doesn't have any arguments.
+ result->set_value(NULL);
+ result->PostResult();
+ return S_OK;
+ } else {
+ // release doesn't destroy result, we need to keep it for next try.
+ result.release();
+ return S_FALSE; // S_FALSE keeps us in the queue.
+ }
+}
+
+bool RemoveWindow::EventHandler(const std::string& input_args,
+ std::string* converted_args,
+ ApiDispatcher* dispatcher) {
+ DCHECK(converted_args);
+ // We don't need to modify anything in the arguments, we just want to delay.
+ *converted_args = input_args;
+
+ scoped_ptr<Value> input_args_val(base::JSONReader::Read(input_args, true));
+ DCHECK(input_args_val.get() != NULL &&
+ input_args_val->IsType(Value::TYPE_LIST));
+ if (input_args_val.get() == NULL || !input_args_val->IsType(Value::TYPE_LIST))
+ return false;
+
+ int window_id = 0;
+ bool success = static_cast<ListValue*>(input_args_val.get())->GetInteger(
+ 0, &window_id);
+ DCHECK(success) << "Couldn't get an int window Id from input args.";
+ if (!success)
+ return false;
+ // The hook may call us before the window has completely disappeared so we
+ // must delay the execution until the window is completely gone.
+ // TODO(mad@chromium.org): Find a way to do this without blocking
+ // all other ApiDispatching.
+ HWND window = dispatcher->GetWindowHandleFromId(window_id);
+ int waited_ms = 0;
+ while (waited_ms < kMaxDelayMs && ::IsWindow(window)) {
+ ::SleepEx(kDelayMs, TRUE); // Alertable.
+ waited_ms += kDelayMs;
+ }
+ DCHECK(waited_ms < kMaxDelayMs);
+ if (waited_ms < kMaxDelayMs) {
+ return true;
+ } else {
+ return false;
+ }
+}
+
+void GetAllWindows::Execute(const ListValue& args, int request_id) {
+ scoped_ptr<IterativeWindowApiResult> result(CreateApiResult(request_id));
+ bool populate_tabs = false;
+ // The input dictionary is optional; if not provided, the args may be
+ // either an empty list or a list with a single null value element.
+ if (args.GetSize() > 0) {
+ Value* first_arg = NULL;
+ if (!args.Get(0, &first_arg) || first_arg == NULL) {
+ NOTREACHED() << "bad request";
+ result->PostError(api_module_constants::kInvalidArgumentsError);
+ } else if (first_arg->GetType() != Value::TYPE_NULL) {
+ DictionaryValue* dict = NULL;
+ if (!args.GetDictionary(0, &dict) ||
+ (dict->HasKey(ext::kPopulateKey) &&
+ !dict->GetBoolean(ext::kPopulateKey, &populate_tabs))) {
+ NOTREACHED() << "bad message";
+ result->PostError(api_module_constants::kInvalidArgumentsError);
+ }
+ }
+
+ if (result->AllFailed()) {
+ result->FlushAllPosts();
+ return;
+ }
+ }
+
+ std::set<HWND> ie_frame_windows;
+ window_utils::FindTopLevelWindows(windows::kIeFrameWindowClass,
+ &ie_frame_windows);
+ if (ie_frame_windows.empty()) {
+ result->FlushAllPosts();
+ return;
+ }
+
+ std::set<HWND>::const_iterator iter = ie_frame_windows.begin();
+ for (; iter != ie_frame_windows.end(); ++iter) {
+ if (result->CreateWindowValue(*iter, populate_tabs))
+ result->PostResult();
+ }
+
+ DCHECK(!result->IsEmpty());
+ if (result->IsEmpty()) // This is an error!
+ result->PostError(api_module_constants::kInternalErrorError);
+
+ result->FlushAllPosts();
+}
+
+} // namespace window_api
diff --git a/ceee/ie/broker/window_api_module.h b/ceee/ie/broker/window_api_module.h
new file mode 100644
index 0000000..6aed95f
--- /dev/null
+++ b/ceee/ie/broker/window_api_module.h
@@ -0,0 +1,96 @@
+// Copyright (c) 2010 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+//
+// Window API implementation.
+
+#ifndef CEEE_IE_BROKER_WINDOW_API_MODULE_H_
+#define CEEE_IE_BROKER_WINDOW_API_MODULE_H_
+
+#include <string>
+
+#include "ceee/ie/broker/api_dispatcher.h"
+#include "ceee/ie/broker/common_api_module.h"
+
+#include "toolband.h" // NOLINT
+
+namespace window_api {
+
+class WindowApiResult;
+typedef ApiResultCreator<WindowApiResult> WindowApiResultCreator;
+
+// Registers all Window API invocations with the given dispatcher.
+void RegisterInvocations(ApiDispatcher* dispatcher);
+
+class WindowApiResult : public common_api::CommonApiResult {
+ public:
+ explicit WindowApiResult(int request_id)
+ : common_api::CommonApiResult(request_id) {}
+
+ // Updates the position of the given window based on the arguments given and
+ // sets the result value appropriately. Calls PostError() if there is an
+ // error and returns false.
+ // @param window The window to update.
+ // @param window_rect The arguments for the window update, a DictionaryValue
+ // containing the left, top, width, height to update the window with.
+ virtual bool UpdateWindowRect(HWND window,
+ const DictionaryValue* window_rect);
+};
+
+typedef IterativeApiResult<WindowApiResult> IterativeWindowApiResult;
+
+class GetWindow : public WindowApiResultCreator {
+ public:
+ virtual void Execute(const ListValue& args, int request_id);
+};
+
+class GetCurrentWindow : public WindowApiResultCreator {
+ public:
+ virtual void Execute(const ListValue& args, int request_id);
+};
+
+class GetLastFocusedWindow : public WindowApiResultCreator {
+ public:
+ virtual void Execute(const ListValue& args, int request_id);
+};
+
+// Unfortunately winuser.h uses a #define for CreateWindow to use either
+// the ASCII or Wide char version, so it replaces the Constructor declaration
+// with CreateWindowW() and fails compilation if we use CreateWindow as a
+// class name. So we must have another class name and use a special case to
+// specify the name of the function we replace in Chrome in RegisterInvocations.
+class CreateWindowFunc : public WindowApiResultCreator {
+ public:
+ virtual void Execute(const ListValue& args, int request_id);
+ // public so that the RegisterInvocations can see it.
+ // Simply reacts to an OnWindowCreated event and waits for the window to
+ // be completely created before letting the ApiDispatcher broadcast the event.
+ static bool EventHandler(const std::string& input_args,
+ std::string* converted_args,
+ ApiDispatcher* dispatcher);
+};
+
+class UpdateWindow : public WindowApiResultCreator {
+ public:
+ virtual void Execute(const ListValue& args, int request_id);
+};
+
+class RemoveWindow : public WindowApiResultCreator {
+ public:
+ virtual void Execute(const ListValue& args, int request_id);
+ static HRESULT ContinueExecution(const std::string& input_args,
+ ApiDispatcher::InvocationResult* user_data,
+ ApiDispatcher* dispatcher);
+ static bool EventHandler(const std::string& input_args,
+ std::string* converted_args,
+ ApiDispatcher* dispatcher);
+};
+
+class GetAllWindows : public ApiResultCreator<IterativeWindowApiResult> {
+ public:
+ virtual void Execute(const ListValue& args, int request_id);
+};
+
+} // namespace window_api
+
+#endif // CEEE_IE_BROKER_WINDOW_API_MODULE_H_
diff --git a/ceee/ie/broker/window_api_module_unittest.cc b/ceee/ie/broker/window_api_module_unittest.cc
new file mode 100644
index 0000000..04f67ef
--- /dev/null
+++ b/ceee/ie/broker/window_api_module_unittest.cc
@@ -0,0 +1,937 @@
+// 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 API implementation unit tests.
+
+// MockWin32 can't be included after ChromeFrameHost because of an include
+// incompatibility with atlwin.h.
+#include "ceee/testing/utils/mock_win32.h" // NOLINT
+
+#include <set>
+
+#include "base/scoped_ptr.h"
+#include "ceee/common/process_utils_win.h"
+#include "ceee/ie/broker/chrome_postman.h"
+#include "ceee/ie/broker/window_api_module.h"
+#include "ceee/ie/common/ie_util.h"
+#include "ceee/ie/testing/mock_broker_and_friends.h"
+#include "ceee/testing/utils/instance_count_mixin.h"
+#include "ceee/testing/utils/mock_com.h"
+#include "ceee/testing/utils/mock_window_utils.h"
+#include "ceee/testing/utils/mock_win32.h"
+#include "ceee/testing/utils/test_utils.h"
+#include "chrome/browser/extensions/extension_event_names.h"
+#include "chrome/browser/extensions/extension_tabs_module_constants.h"
+#include "testing/gmock/include/gmock/gmock.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+namespace {
+
+using testing::_;
+using testing::AddRef;
+using testing::AnyNumber;
+using testing::AtLeast;
+using testing::CopyStringToArgument;
+using testing::DoAll;
+using testing::Gt;
+using testing::InstanceCountMixin;
+using testing::Invoke;
+using testing::MockApiDispatcher;
+using testing::MockApiInvocation;
+using testing::NotNull;
+using testing::Return;
+using testing::Sequence;
+using testing::SetArgumentPointee;
+using testing::StrEq;
+using testing::StrictMock;
+
+using window_api::WindowApiResult;
+using window_api::IterativeWindowApiResult;
+
+namespace keys = extension_tabs_module_constants;
+namespace ext_event_names = extension_event_names;
+
+const wchar_t kClassName[] = L"IEFrame";
+const int kRequestId = 12;
+const int kWindowId = 23;
+const HWND kWindowHwnd = (HWND)34;
+const int kRandomWindowId = 45;
+const HWND kRandomWindowHwnd = (HWND)56;
+
+TEST(WindowApi, RegisterInvocations) {
+ StrictMock<MockApiDispatcher> disp;
+ EXPECT_CALL(disp, RegisterInvocation(NotNull(), NotNull())).Times(AtLeast(7));
+ window_api::RegisterInvocations(&disp);
+}
+
+TEST(WindowApi, IsIeFrameClassWithNull) {
+ testing::LogDisabler no_dchecks;
+ WindowApiResult invocation_result(WindowApiResult::kNoRequestId);
+ EXPECT_FALSE(invocation_result.IsIeFrameClass(NULL));
+}
+
+TEST(WindowApi, IsIeFrameClassWithNonWindow) {
+ testing::LogDisabler no_dchecks;
+ StrictMock<testing::MockUser32> user32;
+ EXPECT_CALL(user32, IsWindow(NotNull())).WillRepeatedly(Return(FALSE));
+ WindowApiResult invocation_result(WindowApiResult::kNoRequestId);
+ EXPECT_FALSE(invocation_result.IsIeFrameClass(reinterpret_cast<HWND>(1)));
+}
+
+TEST(WindowApi, IsIeFrameClassWrongClassName) {
+ const wchar_t kClassName[] = L"IEFrames"; // note 's', making it invalid.
+ testing::LogDisabler no_dchecks;
+ StrictMock<testing::MockUser32> user32;
+ EXPECT_CALL(user32, IsWindow(NotNull())).WillOnce(Return(TRUE));
+ EXPECT_CALL(user32, GetClassName(NotNull(), NotNull(), Gt(0))).WillOnce(
+ DoAll(CopyStringToArgument<1>(kClassName),
+ Return(arraysize(kClassName))));
+ WindowApiResult invocation_result(WindowApiResult::kNoRequestId);
+ EXPECT_FALSE(invocation_result.IsIeFrameClass(reinterpret_cast<HWND>(1)));
+}
+
+TEST(WindowApi, IsIeFrameClassStraightline) {
+ testing::LogDisabler no_dchecks;
+ StrictMock<testing::MockUser32> user32;
+ EXPECT_CALL(user32, IsWindow(NotNull())).WillOnce(Return(TRUE));
+ EXPECT_CALL(user32, GetClassName(NotNull(), NotNull(), Gt(0))).WillOnce(
+ DoAll(CopyStringToArgument<1>(kClassName),
+ Return(arraysize(kClassName))));
+ WindowApiResult invocation_result(WindowApiResult::kNoRequestId);
+ EXPECT_TRUE(invocation_result.IsIeFrameClass(reinterpret_cast<HWND>(1)));
+}
+
+TEST(WindowApi, TopIeWindowNeverFound) {
+ testing::LogDisabler no_dchecks;
+ StrictMock<testing::MockWindowUtils> window_utils;
+ EXPECT_CALL(window_utils, FindDescendentWindow(_, _, _, NotNull())).
+ WillOnce(Return(false));
+ EXPECT_EQ(NULL, WindowApiResult::TopIeWindow());
+}
+
+TEST(WindowApi, TopIeWindowStraightline) {
+ StrictMock<testing::MockWindowUtils> window_utils;
+ EXPECT_CALL(window_utils, FindDescendentWindow(_, _, _, NotNull())).
+ WillOnce(DoAll(SetArgumentPointee<3>(kRandomWindowHwnd), Return(true)));
+ EXPECT_EQ(kRandomWindowHwnd, WindowApiResult::TopIeWindow());
+}
+
+class MockWindowApiResult
+ : public WindowApiResult,
+ public InstanceCountMixin<MockWindowApiResult> {
+ public:
+ explicit MockWindowApiResult(int request_id)
+ : WindowApiResult(request_id) {
+ }
+ MOCK_METHOD1(CreateTabList, Value*(BSTR));
+ MOCK_METHOD3(SetResultFromWindowInfo, void(HWND, const CeeeWindowInfo&,
+ bool));
+ MOCK_METHOD2(UpdateWindowRect, bool(HWND, const DictionaryValue*));
+ MOCK_METHOD2(CreateWindowValue, bool(HWND, bool));
+ MOCK_METHOD0(PostResult, void());
+ MOCK_METHOD1(PostError, void(const std::string&));
+
+ virtual ApiDispatcher* GetDispatcher() {
+ return &mock_api_dispatcher_;
+ }
+
+ void CallSetResultFromWindowInfo(HWND window, const CeeeWindowInfo& info,
+ bool pop) {
+ WindowApiResult::SetResultFromWindowInfo(window, info, pop);
+ }
+ bool CallCreateWindowValue(HWND window, bool pop) {
+ return WindowApiResult::CreateWindowValue(window, pop);
+ }
+ Value* CallCreateTabList(BSTR tabs) {
+ return WindowApiResult::CreateTabList(tabs);
+ }
+ bool CallUpdateWindowRect(HWND window, const DictionaryValue* dict_value) {
+ return WindowApiResult::UpdateWindowRect(window, dict_value);
+ }
+ StrictMock<MockApiDispatcher> mock_api_dispatcher_;
+};
+
+class MockIterativeWindowApiResult
+ : public IterativeWindowApiResult,
+ public InstanceCountMixin<MockIterativeWindowApiResult> {
+ public:
+ explicit MockIterativeWindowApiResult(int request_id)
+ : IterativeWindowApiResult(request_id) {}
+
+ MOCK_METHOD0(CallRealPostResult, void());
+ MOCK_METHOD1(CallRealPostError, void(const std::string&));
+ MOCK_METHOD1(CreateTabList, Value*(BSTR));
+ MOCK_METHOD3(SetResultFromWindowInfo, void(HWND, const CeeeWindowInfo&,
+ bool));
+ MOCK_METHOD2(UpdateWindowRect, bool(HWND, const DictionaryValue*));
+ MOCK_METHOD2(CreateWindowValue, bool(HWND, bool));
+
+ virtual void PostError(const std::string& error) {
+ ++error_counter_;
+ IterativeWindowApiResult::PostError(error);
+ }
+
+ virtual void PostResult() {
+ ++success_counter_;
+ IterativeWindowApiResult::PostResult();
+ }
+
+ virtual ApiDispatcher* GetDispatcher() {
+ return &mock_api_dispatcher_;
+ }
+
+ static void ResetCounters() {
+ success_counter_ = 0;
+ error_counter_ = 0;
+ }
+
+ static int success_counter() {
+ return success_counter_;
+ }
+
+ static int error_counter() {
+ return error_counter_;
+ }
+
+ StrictMock<MockApiDispatcher> mock_api_dispatcher_;
+
+ private:
+ static int success_counter_;
+ static int error_counter_;
+};
+
+int MockIterativeWindowApiResult::success_counter_ = 0;
+int MockIterativeWindowApiResult::error_counter_ = 0;
+
+// Mock static functions defined in WindowApiResult.
+MOCK_STATIC_CLASS_BEGIN(MockWindowApiResultStatics)
+ MOCK_STATIC_INIT_BEGIN(MockWindowApiResultStatics)
+ MOCK_STATIC_INIT2(WindowApiResult::TopIeWindow,
+ TopIeWindow);
+ MOCK_STATIC_INIT_END()
+ MOCK_STATIC0(HWND, , TopIeWindow);
+MOCK_STATIC_CLASS_END(MockWindowApiResultStatics)
+
+class WindowApiTests: public testing::Test {
+ public:
+ virtual void SetUp() {
+ EXPECT_HRESULT_SUCCEEDED(testing::MockWindowExecutor::CreateInitialized(
+ &mock_window_executor_, &mock_window_executor_keeper_));
+ EXPECT_HRESULT_SUCCEEDED(testing::MockTabExecutor::CreateInitialized(
+ &mock_tab_executor_, &mock_tab_executor_keeper_));
+ }
+
+ virtual void TearDown() {
+ // Everything should have been relinquished.
+ mock_window_executor_keeper_.Release();
+ mock_tab_executor_keeper_.Release();
+ ASSERT_EQ(0, testing::InstanceCountMixinBase::all_instance_count());
+ }
+ protected:
+ void MockGetWindowExecutor(MockApiDispatcher* executor_owner) {
+ EXPECT_CALL(*executor_owner, GetExecutor(_, _, NotNull())).
+ WillRepeatedly(DoAll(
+ SetArgumentPointee<2>(mock_window_executor_keeper_.p),
+ AddRef(mock_window_executor_keeper_.p)));
+ }
+
+ void MockGetTabExecutor(MockApiDispatcher* executor_owner) {
+ EXPECT_CALL(*executor_owner, GetExecutor(_, _, NotNull())).
+ WillRepeatedly(DoAll(
+ SetArgumentPointee<2>(mock_tab_executor_keeper_.p),
+ AddRef(mock_tab_executor_keeper_.p)));
+ }
+
+ void WindowAlwaysHasThread() {
+ EXPECT_CALL(user32_, GetWindowThreadProcessId(_, _)).WillRepeatedly(
+ Return(1)); // We only need to return a non-0 value.
+ }
+
+ void MockIsIeFrameClass() {
+ // This is to mock the static call to IsIeFrameClass.
+ EXPECT_CALL(user32_, IsWindow(NotNull())).WillRepeatedly(Return(TRUE));
+ EXPECT_CALL(user32_, GetClassName(NotNull(), NotNull(), Gt(0))).
+ WillRepeatedly(DoAll(CopyStringToArgument<1>(kClassName),
+ Return(arraysize(kClassName))));
+ }
+ StrictMock<testing::MockUser32> user32_;
+ // The executor classes are already strict from their base class impl.
+ testing::MockWindowExecutor* mock_window_executor_;
+ testing::MockTabExecutor* mock_tab_executor_;
+ // To control the life span of the executors.
+ CComPtr<ICeeeWindowExecutor> mock_window_executor_keeper_;
+ CComPtr<ICeeeTabExecutor> mock_tab_executor_keeper_;
+
+ private:
+ class MockChromePostman : public ChromePostman {
+ public:
+ MOCK_METHOD2(PostMessage, void(BSTR, BSTR));
+ };
+ // We should never get to the postman, we mock all the calls getting there.
+ // So we simply instantiate it strict and it will register itself as the
+ // one and only singleton to use all the time.
+ CComObjectStackEx<StrictMock<MockChromePostman>> postman_;
+};
+
+TEST_F(WindowApiTests, CreateTabList) {
+ // We only check that we can properly handle an empty list since we can't
+ // easily mock the tab_api::GetAllTabsInWindow declared on the stack.
+ // We validate it more completely in the tabs api unittest anyway.
+ WindowApiResult invocation_result(WindowApiResult::kNoRequestId);
+ scoped_ptr<Value> returned_value(invocation_result.CreateTabList(L"[]"));
+ EXPECT_NE(static_cast<Value*>(NULL), returned_value.get());
+ EXPECT_TRUE(returned_value->IsType(Value::TYPE_LIST));
+ EXPECT_EQ(0, static_cast<ListValue*>(returned_value.get())->GetSize());
+
+ // Also test the failure path.
+ testing::LogDisabler no_dchecks;
+ returned_value.reset(invocation_result.CreateTabList(L""));
+ EXPECT_EQ(NULL, returned_value.get());
+}
+
+// Mock IeIsInPrivateBrowsing.
+MOCK_STATIC_CLASS_BEGIN(MockIeUtil)
+ MOCK_STATIC_INIT_BEGIN(MockIeUtil)
+ MOCK_STATIC_INIT2(ie_util::GetIEIsInPrivateBrowsing,
+ GetIEIsInPrivateBrowsing);
+ MOCK_STATIC_INIT_END()
+ MOCK_STATIC0(bool, , GetIEIsInPrivateBrowsing);
+MOCK_STATIC_CLASS_END(MockIeUtil)
+
+TEST_F(WindowApiTests, SetResultFromWindowInfo) {
+ testing::LogDisabler no_dchecks;
+
+ // Standard test without tabs list populate nor an internal list.
+ StrictMock<MockWindowApiResult> invocation_result(
+ WindowApiResult::kNoRequestId);
+ CeeeWindowInfo window_info;
+ window_info.rect.left = 1;
+ window_info.rect.right = 2;
+ window_info.rect.top = 3;
+ window_info.rect.bottom = 4;
+ window_info.tab_list = NULL;
+
+ EXPECT_CALL(invocation_result.mock_api_dispatcher_,
+ GetWindowIdFromHandle(kWindowHwnd)).WillRepeatedly(Return(kWindowId));
+ EXPECT_CALL(invocation_result.mock_api_dispatcher_,
+ GetWindowHandleFromId(kWindowId)).WillRepeatedly(Return(kWindowHwnd));
+
+ StrictMock<MockIeUtil> mock_ie_util;
+ EXPECT_CALL(mock_ie_util, GetIEIsInPrivateBrowsing()).WillRepeatedly(
+ Return(false));
+
+ invocation_result.CallSetResultFromWindowInfo(kWindowHwnd, window_info,
+ false);
+ const Value * result = invocation_result.value();
+ EXPECT_NE(static_cast<Value*>(NULL), result);
+ EXPECT_TRUE(result->IsType(Value::TYPE_DICTIONARY));
+ DictionaryValue dict;
+ dict.SetInteger(keys::kIdKey, kWindowId);
+ dict.SetBoolean(keys::kFocusedKey, window_info.focused != FALSE);
+
+ dict.SetInteger(keys::kLeftKey, window_info.rect.left);
+ dict.SetInteger(keys::kTopKey, window_info.rect.top);
+ dict.SetInteger(keys::kWidthKey, window_info.rect.right -
+ window_info.rect.left);
+ dict.SetInteger(keys::kHeightKey, window_info.rect.bottom -
+ window_info.rect.top);
+
+ dict.SetBoolean(keys::kIncognitoKey, false);
+
+ // TODO(mad@chromium.org): for now, always setting to "normal" since
+ // we are not handling popups or app windows in IE yet.
+ dict.SetString(keys::kWindowTypeKey, keys::kWindowTypeValueNormal);
+ EXPECT_TRUE(dict.Equals(result));
+
+ invocation_result.set_value(NULL);
+
+ // Now make sure we call CreateTablist, yet still succeed if it returns NULL.
+ int tab_list = 42;
+ EXPECT_CALL(invocation_result, CreateTabList((BSTR)tab_list)).
+ WillOnce(Return(static_cast<Value*>(NULL)));
+ window_info.tab_list = (BSTR)tab_list;
+
+ invocation_result.CallSetResultFromWindowInfo(kWindowHwnd, window_info, true);
+ EXPECT_NE(static_cast<Value*>(NULL), invocation_result.value());
+
+ invocation_result.set_value(NULL);
+
+ // And now a successful run with CreateTablist.
+ EXPECT_CALL(invocation_result, CreateTabList((BSTR)tab_list)).
+ WillOnce(Return(Value::CreateIntegerValue(tab_list)));
+
+ invocation_result.CallSetResultFromWindowInfo(kWindowHwnd, window_info, true);
+ result = invocation_result.value();
+ EXPECT_NE(static_cast<Value*>(NULL), result);
+ EXPECT_TRUE(result->IsType(Value::TYPE_DICTIONARY));
+ dict.Set(keys::kTabsKey, Value::CreateIntegerValue(tab_list));
+ EXPECT_TRUE(dict.Equals(result));
+}
+
+TEST_F(WindowApiTests, CreateWindowValue) {
+ testing::LogDisabler no_dchecks;
+ EXPECT_CALL(user32_, IsWindow(_)).WillRepeatedly(Return(FALSE));
+ EXPECT_CALL(user32_, GetWindowThreadProcessId(_, _)).
+ WillRepeatedly(Return(0));
+
+ StrictMock<MockWindowApiResult> invocation_result(
+ WindowApiResult::kNoRequestId);
+ EXPECT_CALL(invocation_result.mock_api_dispatcher_,
+ GetWindowIdFromHandle(kWindowHwnd)).WillRepeatedly(Return(kWindowId));
+ EXPECT_CALL(invocation_result.mock_api_dispatcher_,
+ GetWindowHandleFromId(kWindowId)).WillRepeatedly(Return(kWindowHwnd));
+
+ // Fail because the window is not associated to a thread.
+ EXPECT_CALL(invocation_result, PostError(_)).Times(1);
+ EXPECT_FALSE(invocation_result.CallCreateWindowValue(kWindowHwnd, false));
+ EXPECT_EQ(NULL, invocation_result.value());
+
+ // Now fail because the window is not an IE Frame.
+ WindowAlwaysHasThread();
+ invocation_result.set_value(NULL);
+ EXPECT_CALL(invocation_result, PostError(_)).Times(1);
+ EXPECT_FALSE(invocation_result.CallCreateWindowValue(kWindowHwnd, false));
+ EXPECT_EQ(NULL, invocation_result.value());
+
+ // Now fail because we can't get an executor.
+ MockIsIeFrameClass();
+ EXPECT_CALL(invocation_result.mock_api_dispatcher_,
+ GetExecutor(kWindowHwnd, _, NotNull())).
+ WillOnce(SetArgumentPointee<2>((HANDLE)NULL));
+ EXPECT_CALL(invocation_result, PostError(_)).Times(1);
+ EXPECT_FALSE(invocation_result.CallCreateWindowValue(kWindowHwnd, false));
+ EXPECT_EQ(NULL, invocation_result.value());
+
+ // Now get the executor to fail.
+ MockGetWindowExecutor(&invocation_result.mock_api_dispatcher_);
+ EXPECT_CALL(*mock_window_executor_, GetWindow(FALSE, NotNull())).
+ WillOnce(Return(E_FAIL));
+ EXPECT_CALL(invocation_result, PostError(_)).Times(1);
+ EXPECT_FALSE(invocation_result.CallCreateWindowValue(kWindowHwnd, false));
+ EXPECT_EQ(NULL, invocation_result.value());
+
+ // Now go all the way.
+ EXPECT_CALL(*mock_window_executor_, GetWindow(FALSE, NotNull())).
+ WillOnce(Return(S_OK));
+ EXPECT_CALL(invocation_result, SetResultFromWindowInfo(_, _, _)).Times(1);
+ EXPECT_TRUE(invocation_result.CallCreateWindowValue(kWindowHwnd, false));
+}
+
+TEST_F(WindowApiTests, UpdateWindowRect) {
+ testing::LogDisabler no_dchecks;
+ EXPECT_CALL(user32_, IsWindow(_)).WillRepeatedly(Return(FALSE));
+
+ StrictMock<MockWindowApiResult> invocation_result(
+ WindowApiResult::kNoRequestId);
+ EXPECT_CALL(invocation_result.mock_api_dispatcher_,
+ GetWindowIdFromHandle(kWindowHwnd)).WillRepeatedly(Return(kWindowId));
+ EXPECT_CALL(invocation_result.mock_api_dispatcher_,
+ GetWindowHandleFromId(kWindowId)).WillRepeatedly(Return(kWindowHwnd));
+ EXPECT_CALL(invocation_result.mock_api_dispatcher_,
+ GetWindowIdFromHandle(kRandomWindowHwnd)).
+ WillRepeatedly(Return(kRandomWindowId));
+ EXPECT_CALL(invocation_result.mock_api_dispatcher_,
+ GetWindowHandleFromId(kRandomWindowId)).
+ WillRepeatedly(Return(kRandomWindowHwnd));
+
+ // Window has no thread.
+ EXPECT_CALL(user32_, GetWindowThreadProcessId(_, _)).WillRepeatedly(
+ Return(0));
+ EXPECT_CALL(invocation_result, PostError(_)).Times(1);
+ EXPECT_FALSE(invocation_result.CallUpdateWindowRect(kRandomWindowHwnd, NULL));
+ EXPECT_EQ(NULL, invocation_result.value());
+
+ // Window is not an IEFrame.
+ WindowAlwaysHasThread();
+ EXPECT_CALL(invocation_result, PostError(_)).Times(1);
+ EXPECT_FALSE(invocation_result.CallUpdateWindowRect(kRandomWindowHwnd, NULL));
+ EXPECT_EQ(NULL, invocation_result.value());
+
+ // Now try an empty dictionary (which should not cause an error).
+ MockIsIeFrameClass();
+ EXPECT_CALL(invocation_result, CreateWindowValue(_, false)).
+ WillOnce(Return(true));
+ EXPECT_TRUE(invocation_result.CallUpdateWindowRect(kRandomWindowHwnd, NULL));
+
+ // Now try a dictionary with invalid dictionary values.
+ DictionaryValue bad_values;
+ bad_values.Set(keys::kLeftKey, Value::CreateNullValue());
+ EXPECT_CALL(invocation_result, PostError(_)).Times(1);
+ EXPECT_FALSE(invocation_result.CallUpdateWindowRect(kRandomWindowHwnd,
+ &bad_values));
+ EXPECT_EQ(NULL, invocation_result.value());
+
+ bad_values.Set(keys::kLeftKey, Value::CreateIntegerValue(43));
+ bad_values.Set(keys::kTopKey, Value::CreateNullValue());
+ EXPECT_CALL(invocation_result, PostError(_)).Times(1);
+ EXPECT_FALSE(invocation_result.CallUpdateWindowRect(kRandomWindowHwnd,
+ &bad_values));
+ EXPECT_EQ(NULL, invocation_result.value());
+
+ bad_values.Set(keys::kTopKey, Value::CreateIntegerValue(44));
+ bad_values.Set(keys::kWidthKey, Value::CreateNullValue());
+ EXPECT_CALL(invocation_result, PostError(_)).Times(1);
+ EXPECT_FALSE(invocation_result.CallUpdateWindowRect(kRandomWindowHwnd,
+ &bad_values));
+ EXPECT_EQ(NULL, invocation_result.value());
+
+ bad_values.Set(keys::kWidthKey, Value::CreateIntegerValue(45));
+ bad_values.Set(keys::kHeightKey, Value::CreateNullValue());
+ EXPECT_CALL(invocation_result, PostError(_)).Times(1);
+ EXPECT_FALSE(invocation_result.CallUpdateWindowRect(kRandomWindowHwnd,
+ &bad_values));
+ EXPECT_EQ(NULL, invocation_result.value());
+
+ // Now make sure the values get properly propagated.
+ // But start by failing the GetExecutor.
+ scoped_ptr<DictionaryValue> good_values(static_cast<DictionaryValue*>(
+ bad_values.DeepCopy()));
+ good_values->Set(keys::kHeightKey, Value::CreateIntegerValue(46));
+ EXPECT_CALL(invocation_result.mock_api_dispatcher_,
+ GetExecutor(kRandomWindowHwnd, _, NotNull())).
+ WillOnce(SetArgumentPointee<2>((HANDLE)NULL));
+ EXPECT_CALL(invocation_result, PostError(_)).Times(1);
+ EXPECT_FALSE(invocation_result.CallUpdateWindowRect(kRandomWindowHwnd,
+ good_values.get()));
+ EXPECT_EQ(NULL, invocation_result.value());
+
+ // Now get the executor to fail.
+ MockGetWindowExecutor(&invocation_result.mock_api_dispatcher_);
+ EXPECT_CALL(*mock_window_executor_, UpdateWindow(43, 44, 45, 46, _)).
+ WillOnce(Return(E_FAIL));
+ EXPECT_CALL(invocation_result, PostError(_)).Times(1);
+ EXPECT_FALSE(invocation_result.CallUpdateWindowRect(kRandomWindowHwnd,
+ good_values.get()));
+ EXPECT_EQ(NULL, invocation_result.value());
+
+ // Now go all the way,
+ EXPECT_CALL(invocation_result, SetResultFromWindowInfo(_, _, _)).Times(1);
+ EXPECT_CALL(*mock_window_executor_, UpdateWindow(43, 44, 45, 46, _)).
+ WillOnce(Return(S_OK));
+ EXPECT_TRUE(invocation_result.CallUpdateWindowRect(kRandomWindowHwnd,
+ good_values.get()));
+}
+
+template <class BaseClass>
+class MockWindowInvocation
+ : public MockApiInvocation<WindowApiResult, MockWindowApiResult,
+ BaseClass> {
+};
+
+template <class BaseClass>
+class MockIterativeWindowInvocation
+ : public MockApiInvocation<IterativeWindowApiResult,
+ MockIterativeWindowApiResult,
+ BaseClass> {
+};
+
+TEST_F(WindowApiTests, GetWindowErrorHandling) {
+ testing::LogDisabler no_dchecks;
+ StrictMock<MockWindowInvocation<window_api::GetWindow>> invocation;
+ invocation.AllocateNewResult(kRequestId);
+ EXPECT_CALL(user32_, IsWindow(_)).WillRepeatedly(Return(FALSE));
+
+ // Bad args failure.
+ EXPECT_CALL(*invocation.invocation_result_, PostError(_)).Times(1);
+ invocation.Execute(ListValue(), kRequestId);
+}
+
+TEST_F(WindowApiTests, GetWindowStraightline) {
+ testing::LogDisabler no_dchecks;
+ StrictMock<MockWindowInvocation<window_api::GetWindow>> invocation;
+ MockIsIeFrameClass();
+ invocation.AllocateNewResult(kRequestId);
+ EXPECT_CALL(*invocation.invocation_result_, CreateWindowValue(
+ kRandomWindowHwnd, _)).WillOnce(Return(true));
+ EXPECT_CALL(*invocation.invocation_result_, PostResult()).Times(1);
+ EXPECT_CALL(invocation.mock_api_dispatcher_,
+ GetWindowHandleFromId(kRandomWindowId)).
+ WillOnce(Return(kRandomWindowHwnd));
+ ListValue args_list;
+ args_list.Append(Value::CreateIntegerValue(kRandomWindowId));
+ invocation.Execute(args_list, kRequestId);
+}
+
+TEST_F(WindowApiTests, GetCurrentWindowStraightline) {
+ testing::LogDisabler no_dchecks;
+
+ EXPECT_CALL(user32_, IsWindow(_)).WillRepeatedly(Return(TRUE));
+
+ StrictMock<MockWindowInvocation<window_api::GetCurrentWindow>> invocation;
+ invocation.AllocateNewResult(kRequestId);
+ MockWindowApiResultStatics result_statics;
+ EXPECT_CALL(result_statics, TopIeWindow()).WillOnce(Return(
+ kRandomWindowHwnd));
+ EXPECT_CALL(*invocation.invocation_result_, CreateWindowValue(
+ kRandomWindowHwnd, _)).WillOnce(Return(true));
+ EXPECT_CALL(*invocation.invocation_result_, PostResult()).Times(1);
+ invocation.Execute(ListValue(), kRequestId);
+}
+
+TEST_F(WindowApiTests, GetLastFocusedWindowStraightline) {
+ testing::LogDisabler no_dchecks; // don't care about NULL pointers.
+
+ EXPECT_CALL(user32_, IsWindow(_)).WillRepeatedly(Return(TRUE));
+
+ StrictMock<MockWindowInvocation<window_api::GetLastFocusedWindow>> invocation;
+ invocation.AllocateNewResult(kRequestId);
+ MockWindowApiResultStatics result_statics;
+ EXPECT_CALL(result_statics, TopIeWindow()).WillOnce(Return(
+ kRandomWindowHwnd));
+ EXPECT_CALL(*invocation.invocation_result_, CreateWindowValue(
+ kRandomWindowHwnd, _)).WillOnce(Return(true));
+ EXPECT_CALL(*invocation.invocation_result_, PostResult()).Times(1);
+ invocation.Execute(ListValue(), kRequestId);
+}
+
+// Mock the CoCreateInstance call that is used to create a new IE window.
+MOCK_STATIC_CLASS_BEGIN(MockIeWindowCreation)
+ MOCK_STATIC_INIT_BEGIN(MockIeWindowCreation)
+ MOCK_STATIC_INIT(CoCreateInstance);
+ MOCK_STATIC_INIT_END()
+
+ MOCK_STATIC5(HRESULT, CALLBACK, CoCreateInstance, REFCLSID, LPUNKNOWN,
+ DWORD, REFIID, LPVOID*);
+MOCK_STATIC_CLASS_END(MockIeWindowCreation)
+
+TEST_F(WindowApiTests, CreateWindowErrorHandling) {
+ testing::LogDisabler no_dchecks;
+ StrictMock<MockWindowInvocation<window_api::CreateWindowFunc>> invocation;
+ invocation.AllocateNewResult(kRequestId);
+ EXPECT_CALL(*invocation.invocation_result_, PostError(_)).Times(1);
+ ListValue wrong_type;
+ wrong_type.Append(Value::CreateBooleanValue(false));
+ invocation.Execute(wrong_type, kRequestId);
+
+ invocation.AllocateNewResult(kRequestId);
+ ListValue args_list;
+ DictionaryValue* dict_value = new DictionaryValue;
+ args_list.Append(dict_value);
+ // Wrong data type...
+ dict_value->SetInteger(keys::kUrlKey, rand());
+ EXPECT_CALL(*invocation.invocation_result_, PostError(_)).Times(1);
+ invocation.Execute(args_list, kRequestId);
+
+ invocation.AllocateNewResult(kRequestId);
+ // InvalidUrl
+ dict_value->SetString(keys::kUrlKey, "ht@tp://www.google.com/");
+ EXPECT_CALL(*invocation.invocation_result_, PostError(_)).Times(1);
+ invocation.Execute(args_list, kRequestId);
+
+ // Valid dictionary...
+ dict_value->SetString(keys::kUrlKey, "http://ossum.the.magnificent.com/");
+ dict_value->SetInteger(keys::kLeftKey, 21); // Just to force the rect access.
+
+ StrictMock<testing::MockWindowUtils> window_utils;
+ EXPECT_CALL(window_utils, FindTopLevelWindows(_, _)).Times(AnyNumber());
+
+ invocation.AllocateNewResult(kRequestId);
+ EXPECT_CALL(*invocation.invocation_result_, PostError(_)).Times(1);
+ MockIeWindowCreation mock_ie_create;
+ EXPECT_CALL(mock_ie_create, CoCreateInstance(_, _, _, _, _)).
+ WillOnce(Return(REGDB_E_CLASSNOTREG));
+ invocation.Execute(args_list, kRequestId);
+
+ invocation.AllocateNewResult(kRequestId);
+ CComObject<StrictMock<testing::MockIWebBrowser2>>* browser;
+ CComObject<StrictMock<testing::MockIWebBrowser2>>::CreateInstance(&browser);
+ DCHECK(browser != NULL);
+ CComPtr<IWebBrowser2> browser_keeper = browser;
+ EXPECT_CALL(mock_ie_create, CoCreateInstance(_, _, _, _, _)).
+ WillRepeatedly(DoAll(SetArgumentPointee<4>(browser_keeper.p),
+ AddRef(browser_keeper.p), Return(S_OK)));
+
+ EXPECT_CALL(*browser, get_HWND(NotNull())).WillRepeatedly(DoAll(
+ SetArgumentPointee<0>(0), Return(S_OK)));
+ EXPECT_CALL(*invocation.invocation_result_, UpdateWindowRect(_, _)).
+ WillOnce(Return(false));
+ invocation.Execute(args_list, kRequestId);
+
+ invocation.AllocateNewResult(kRequestId);
+ EXPECT_CALL(*invocation.invocation_result_, UpdateWindowRect(_, _)).
+ WillOnce(Return(true));
+ EXPECT_CALL(*browser, Navigate(_, _, _, _, _)).WillOnce(Return(E_FAIL));
+ EXPECT_CALL(*invocation.invocation_result_, PostError(_)).Times(1);
+ invocation.Execute(args_list, kRequestId);
+
+ invocation.AllocateNewResult(kRequestId);
+ EXPECT_CALL(*invocation.invocation_result_, UpdateWindowRect(_, _)).
+ WillOnce(Return(true));
+ EXPECT_CALL(*browser, Navigate(_, _, _, _, _)).WillRepeatedly(Return(S_OK));
+ EXPECT_CALL(*browser, put_Visible(VARIANT_TRUE)).WillOnce(Return(E_FAIL));
+ EXPECT_CALL(*invocation.invocation_result_, PostError(_)).Times(1);
+ invocation.Execute(args_list, kRequestId);
+}
+
+TEST_F(WindowApiTests, CreateWindowStraightline) {
+ testing::LogDisabler no_dchecks;
+
+ ListValue args1;
+ DictionaryValue* dict1 = new DictionaryValue;
+ args1.Append(dict1);
+ dict1->SetString(keys::kUrlKey, "http://ossum.the.magnificent.com/");
+ dict1->SetInteger(keys::kLeftKey, 21); // Just to force the rect access.
+
+ ListValue args2;
+ DictionaryValue* dict2 = new DictionaryValue;
+ args2.Append(dict2);
+ dict2->SetString(keys::kUrlKey, "http://ossum.the.omnipotent.com/");
+ dict2->SetString(keys::kWindowTypeKey, keys::kWindowTypeValuePopup);
+
+ ListValue args3;
+ args3.Append(Value::CreateNullValue());
+
+ ListValue empty_args;
+
+ const wchar_t* about_blank = L"about:blank";
+ struct {
+ const ListValue* args;
+ const wchar_t* expected_url;
+ const bool popup;
+ } test_data[] = {
+ { &args1, L"http://ossum.the.magnificent.com/", false },
+ { &args2, L"http://ossum.the.omnipotent.com/", true },
+ { &args3, about_blank, false },
+ { &empty_args, about_blank, false },
+ };
+
+ typedef StrictMock<MockWindowInvocation<window_api::CreateWindowFunc>>
+ MockCreateWindowFunc;
+
+ CComObject<StrictMock<testing::MockIWebBrowser2>>* browser;
+ CComObject<StrictMock<testing::MockIWebBrowser2>>::CreateInstance(&browser);
+ DCHECK(browser != NULL);
+ CComPtr<IWebBrowser2> browser_keeper = browser;
+ MockIeWindowCreation mock_ie_create;
+ EXPECT_CALL(mock_ie_create, CoCreateInstance(_, _, _, _, _)).
+ WillRepeatedly(DoAll(SetArgumentPointee<4>(browser_keeper.p),
+ AddRef(browser_keeper.p), Return(S_OK)));
+ EXPECT_CALL(*browser, get_HWND(NotNull())).WillRepeatedly(DoAll(
+ SetArgumentPointee<0>(0), Return(S_OK)));
+ EXPECT_CALL(*browser, Navigate(_, _, _, _, _)).WillRepeatedly(Return(S_OK));
+ EXPECT_CALL(*browser, put_Visible(VARIANT_TRUE)).WillRepeatedly(Return(S_OK));
+
+ for (int i = 0; i < arraysize(test_data); ++i) {
+ MockCreateWindowFunc invocation;
+ invocation.AllocateNewResult(kRequestId);
+ EXPECT_CALL(*invocation.invocation_result_, PostResult()).Times(1);
+ Value* first_arg = NULL;
+ if (!test_data[i].args->Get(0, &first_arg) ||
+ first_arg->GetType() == Value::TYPE_NULL) {
+ EXPECT_CALL(*invocation.invocation_result_, CreateWindowValue(_, _)).
+ WillOnce(Return(true));
+ invocation.Execute(*test_data[i].args, kRequestId);
+ } else {
+ EXPECT_CALL(*invocation.invocation_result_, UpdateWindowRect(_, _)).
+ WillOnce(Return(true));
+ if (test_data[i].popup) {
+ EXPECT_CALL(*browser, put_AddressBar(VARIANT_FALSE)).
+ WillOnce(Return(S_OK));
+ EXPECT_CALL(*browser, put_StatusBar(VARIANT_FALSE)).
+ WillOnce(Return(S_OK));
+ EXPECT_CALL(*browser, put_ToolBar(FALSE)).WillOnce(Return(S_OK));
+ }
+ invocation.Execute(*test_data[i].args, kRequestId);
+ }
+ }
+}
+
+TEST_F(WindowApiTests, UpdateWindowErrorHandling) {
+ testing::LogDisabler no_dchecks;
+ StrictMock<MockWindowInvocation<window_api::UpdateWindow>>
+ invocation;
+ invocation.AllocateNewResult(kRequestId);
+
+ ListValue root;
+ root.Append(Value::CreateIntegerValue(kRandomWindowId));
+ // Too few values in list.
+ EXPECT_CALL(*invocation.invocation_result_, PostError(_)).Times(1);
+ invocation.Execute(root, kRequestId);
+
+ invocation.AllocateNewResult(kRequestId);
+
+ Value* wrong_type = Value::CreateStringValue(L"The Answer");
+ root.Append(wrong_type); // root takes ownership.
+ // Right number of values, wrong type of second value.
+ EXPECT_CALL(*invocation.invocation_result_, PostError(_)).Times(1);
+ invocation.Execute(root, kRequestId);
+
+ invocation.AllocateNewResult(kRequestId);
+
+ root.Remove(*wrong_type);
+ root.Append(new DictionaryValue()); // root takes ownership.
+ // Right values, but not IE Frame.
+ StrictMock<testing::MockWindowUtils> window_utils;
+ EXPECT_CALL(window_utils, IsWindowClass(_, _)).WillOnce(Return(FALSE));
+ EXPECT_CALL(*invocation.invocation_result_, PostError(_)).Times(1);
+ EXPECT_CALL(invocation.mock_api_dispatcher_,
+ GetWindowHandleFromId(kRandomWindowId)).
+ WillOnce(Return(kRandomWindowHwnd));
+ invocation.Execute(root, kRequestId);
+}
+
+TEST_F(WindowApiTests, UpdateWindowStraightline) {
+ testing::LogDisabler no_dchecks;
+
+ ListValue root;
+ root.Append(Value::CreateIntegerValue(kRandomWindowId));
+ DictionaryValue* args = new DictionaryValue(); // root takes ownership.
+ root.Append(args);
+
+ MockIsIeFrameClass();
+
+ StrictMock<MockWindowInvocation<window_api::UpdateWindow>>
+ invocation;
+ invocation.AllocateNewResult(kRequestId);
+ EXPECT_CALL(*invocation.invocation_result_, UpdateWindowRect(
+ kRandomWindowHwnd, args)).WillOnce(Return(true));
+ EXPECT_CALL(*invocation.invocation_result_, PostResult()).Times(1);
+ EXPECT_CALL(invocation.mock_api_dispatcher_,
+ GetWindowHandleFromId(kRandomWindowId)).
+ WillOnce(Return(kRandomWindowHwnd));
+ invocation.Execute(root, kRequestId);
+}
+
+class MockRemoveWindow
+ : public StrictMock<MockWindowInvocation<window_api::RemoveWindow>> {
+};
+
+TEST_F(WindowApiTests, RemoveWindowErrorHandling) {
+ testing::LogDisabler no_dchecks;
+
+ // Wrong argument type.
+ MockRemoveWindow invocation;
+ invocation.AllocateNewResult(kRequestId);
+ EXPECT_CALL(*invocation.invocation_result_, PostError(_)).Times(1);
+ invocation.Execute(ListValue(), kRequestId);
+
+ // Not a valid window.
+ EXPECT_CALL(user32_, IsWindow(_)).WillRepeatedly(Return(FALSE));
+ ListValue good_args;
+ good_args.Append(Value::CreateIntegerValue(kRandomWindowId));
+ invocation.AllocateNewResult(kRequestId);
+ EXPECT_CALL(*invocation.invocation_result_, PostError(_)).Times(1);
+ EXPECT_CALL(invocation.mock_api_dispatcher_,
+ GetWindowHandleFromId(kRandomWindowId)).
+ WillOnce(Return(kRandomWindowHwnd));
+ invocation.Execute(good_args, kRequestId);
+
+ // No executor.
+ MockIsIeFrameClass();
+ EXPECT_CALL(invocation.mock_api_dispatcher_,
+ GetExecutor(kRandomWindowHwnd, _, NotNull())).
+ WillOnce(SetArgumentPointee<2>((HANDLE)NULL));
+ invocation.AllocateNewResult(kRequestId);
+ EXPECT_CALL(*invocation.invocation_result_, PostError(_)).Times(1);
+ EXPECT_CALL(invocation.mock_api_dispatcher_,
+ GetWindowHandleFromId(kRandomWindowId)).
+ WillOnce(Return(kRandomWindowHwnd));
+ invocation.Execute(good_args, kRequestId);
+
+ // Executor fails.
+ MockGetWindowExecutor(&invocation.mock_api_dispatcher_);
+ EXPECT_CALL(*mock_window_executor_, RemoveWindow()).
+ WillOnce(Return(E_FAIL));
+ invocation.AllocateNewResult(kRequestId);
+ EXPECT_CALL(*invocation.invocation_result_, PostError(_)).Times(1);
+ EXPECT_CALL(invocation.mock_api_dispatcher_,
+ GetWindowHandleFromId(kRandomWindowId)).
+ WillOnce(Return(kRandomWindowHwnd));
+ invocation.Execute(good_args, kRequestId);
+}
+
+TEST_F(WindowApiTests, RemoveWindowStraightline) {
+ testing::LogDisabler no_dchecks;
+
+ MockIsIeFrameClass();
+ MockRemoveWindow invocation;
+ ListValue good_args;
+ good_args.Append(Value::CreateIntegerValue(kRandomWindowId));
+ MockGetWindowExecutor(&invocation.mock_api_dispatcher_);
+ EXPECT_CALL(*mock_window_executor_, RemoveWindow()).
+ WillOnce(Return(S_OK));
+ invocation.AllocateNewResult(kRequestId);
+ EXPECT_CALL(*invocation.invocation_result_, PostResult()).Times(1);
+ EXPECT_CALL(invocation.mock_api_dispatcher_, RegisterEphemeralEventHandler(
+ StrEq(ext_event_names::kOnWindowRemoved),
+ window_api::RemoveWindow::ContinueExecution,
+ invocation.invocation_result_.get())).Times(1);
+ EXPECT_CALL(invocation.mock_api_dispatcher_,
+ GetWindowHandleFromId(kRandomWindowId)).
+ WillOnce(Return(kRandomWindowHwnd));
+ ApiDispatcher::InvocationResult* result = invocation.invocation_result_.get();
+ invocation.Execute(good_args, kRequestId);
+
+ std::ostringstream args;
+ args << "[" << kRandomWindowId << "]";
+ invocation.ContinueExecution(args.str(), result, invocation.GetDispatcher());
+}
+
+TEST_F(WindowApiTests, GetAllWindowsErrorHandling) {
+ testing::LogDisabler no_dchecks;
+ ListValue bad_args;
+ DictionaryValue* bad_dict = new DictionaryValue();
+ bad_dict->SetInteger(keys::kPopulateKey, 42);
+ bad_args.Append(bad_dict);
+ StrictMock<MockIterativeWindowInvocation<window_api::GetAllWindows> >
+ invocation;
+ invocation.AllocateNewResult(kRequestId);
+ // Using a strict mock ensures that FindTopLevelWindows won't get called.
+ MockIterativeWindowApiResult::ResetCounters();
+ StrictMock<testing::MockWindowUtils> window_utils;
+ EXPECT_CALL(*invocation.invocation_result_, CallRealPostError(_)).Times(1);
+ invocation.Execute(bad_args, kRequestId);
+ EXPECT_EQ(MockIterativeWindowApiResult::error_counter(), 1);
+}
+
+TEST_F(WindowApiTests, GetAllWindowsStraightline) {
+ testing::LogDisabler no_dchecks;
+
+ ListValue empty_args;
+ ListValue null_args;
+ null_args.Append(Value::CreateNullValue());
+ ListValue true_args;
+ DictionaryValue* dict_value = new DictionaryValue;
+ // The list will take ownership
+ ASSERT_TRUE(true_args.Set(0, dict_value));
+ dict_value->SetBoolean(keys::kPopulateKey, true);
+ struct {
+ const ListValue* value;
+ bool populate;
+ } values[] = {
+ { &empty_args, false },
+ { &null_args, false },
+ { &true_args, true }
+ };
+
+ std::set<HWND> ie_hwnds;
+ static const HWND kWindow1 = reinterpret_cast<HWND>(1);
+ ie_hwnds.insert(kWindow1);
+ static const HWND kWindow2 = reinterpret_cast<HWND>(2);
+ ie_hwnds.insert(kWindow2);
+
+ StrictMock<testing::MockWindowUtils> window_utils;
+ EXPECT_CALL(window_utils, FindTopLevelWindows(_, NotNull())).WillRepeatedly(
+ SetArgumentPointee<1>(ie_hwnds));
+
+ EXPECT_CALL(user32_, GetWindowThreadProcessId(_, _)).
+ WillRepeatedly(Return(1));
+
+ for (int i = 0; i < arraysize(values); ++i) {
+ StrictMock<MockIterativeWindowInvocation<window_api::GetAllWindows> >
+ invocation;
+ invocation.AllocateNewResult(kRequestId);
+ EXPECT_CALL(*invocation.invocation_result_, CreateWindowValue(
+ kWindow1, values[i].populate)).WillOnce(Return(true));
+ EXPECT_CALL(*invocation.invocation_result_, CreateWindowValue(
+ kWindow2, values[i].populate)).WillOnce(Return(true));
+ EXPECT_CALL(*invocation.invocation_result_, CallRealPostResult()).Times(1);
+ MockIterativeWindowApiResult::ResetCounters();
+ invocation.Execute(*values[i].value, kRequestId);
+ EXPECT_EQ(MockIterativeWindowApiResult::error_counter(), 0);
+ EXPECT_EQ(MockIterativeWindowApiResult::success_counter(), ie_hwnds.size());
+ }
+}
+
+} // namespace
diff --git a/ceee/ie/broker/window_events_funnel.cc b/ceee/ie/broker/window_events_funnel.cc
new file mode 100644
index 0000000..4300dcc
--- /dev/null
+++ b/ceee/ie/broker/window_events_funnel.cc
@@ -0,0 +1,154 @@
+// 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 to be sent to Chrome.
+
+#include "ceee/ie/broker/window_events_funnel.h"
+
+#include "base/json/json_writer.h"
+#include "base/logging.h"
+#include "base/scoped_ptr.h"
+#include "base/values.h"
+#include "ceee/common/com_utils.h"
+#include "ceee/common/windows_constants.h"
+#include "ceee/common/window_utils.h"
+#include "ceee/ie/broker/chrome_postman.h"
+#include "ceee/ie/common/ie_util.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;
+
+namespace {
+// The Shell Hook uses a registered message to communicate with registered
+// windows.
+const UINT kShellHookMessage = ::RegisterWindowMessage(L"SHELLHOOK");
+
+// The HWND, Class and WindowProc for the Registered Shell Hook Window.
+HWND shell_hook_window = NULL;
+const wchar_t kShellHookWindowClassName[] = L"CeeeShellHookWindow";
+LRESULT CALLBACK WindowEventsFunnelWindowProc(HWND hwnd, UINT message,
+ WPARAM wparam, LPARAM lparam) {
+ if (message == kShellHookMessage) {
+ switch (wparam) {
+ case HSHELL_WINDOWCREATED: {
+ if (window_utils::IsWindowClass(reinterpret_cast<HWND>(lparam),
+ windows::kIeFrameWindowClass)) {
+ WindowEventsFunnel window_events_funnel;
+ window_events_funnel.OnCreated(lparam);
+ }
+ break;
+ }
+ case HSHELL_WINDOWDESTROYED: {
+ if (window_utils::IsWindowClass(reinterpret_cast<HWND>(lparam),
+ windows::kIeFrameWindowClass)) {
+ WindowEventsFunnel window_events_funnel;
+ window_events_funnel.OnRemoved(lparam);
+ }
+ break;
+ }
+ case HSHELL_WINDOWACTIVATED: {
+ if (window_utils::IsWindowClass(reinterpret_cast<HWND>(lparam),
+ windows::kIeFrameWindowClass)) {
+ WindowEventsFunnel window_events_funnel;
+ window_events_funnel.OnFocusChanged(lparam);
+ }
+ break;
+ }
+ }
+ }
+ return ::DefWindowProc(hwnd, message, wparam, lparam);
+}
+}
+
+void WindowEventsFunnel::Initialize() {
+ if (shell_hook_window)
+ return;
+
+ WNDCLASSEX shell_window_hook_class = {0};
+ shell_window_hook_class.cbSize = sizeof(WNDCLASSEX);
+ shell_window_hook_class.lpfnWndProc = WindowEventsFunnelWindowProc;
+ shell_window_hook_class.hInstance = GetModuleHandle(NULL);
+ shell_window_hook_class.lpszClassName = kShellHookWindowClassName;
+
+ ATOM class_registration = ::RegisterClassEx(&shell_window_hook_class);
+ DCHECK(class_registration != NULL) <<
+ "Couldn't register Shell Hook Window class!" << com::LogWe();
+ if (!class_registration)
+ return;
+
+ shell_hook_window = ::CreateWindow(
+ reinterpret_cast<wchar_t*>(class_registration),
+ L"", 0, 0, 0, 0, 0, HWND_MESSAGE, NULL, NULL, NULL);
+ DCHECK(shell_hook_window != NULL) << "Couldn't Create Shell Hook Window!" <<
+ com::LogWe();
+ if (shell_hook_window) {
+ BOOL success = ::RegisterShellHookWindow(shell_hook_window);
+ DCHECK(success) << "Couldn't register shell hook window!" << com::LogWe();
+ }
+}
+
+void WindowEventsFunnel::Terminate() {
+ if (shell_hook_window) {
+ BOOL success = ::DeregisterShellHookWindow(shell_hook_window);
+ DCHECK(success) << "Couldn't unregister shell hook window!" << com::LogWe();
+ success = ::DestroyWindow(shell_hook_window);
+ shell_hook_window = NULL;
+ DCHECK(success) << "Couldn't destroy shell hook window!" << com::LogWe();
+ }
+}
+
+HRESULT WindowEventsFunnel::SendEvent(const char* event_name,
+ const Value& event_args) {
+ // Event arguments are always 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);
+ }
+
+ DCHECK(ChromePostman::GetInstance() != NULL);
+ ChromePostman::GetInstance()->FireEvent(CComBSTR(event_name),
+ CComBSTR(event_args_str.c_str()));
+ return S_OK;
+}
+
+HRESULT WindowEventsFunnel::OnCreated(int window_id) {
+ HWND window = reinterpret_cast<HWND>(window_id);
+ RECT window_rect;
+ if (!::GetWindowRect(window, &window_rect)) {
+ DWORD we = ::GetLastError();
+ DCHECK(false) << "GetWindowRect failed " << com::LogWe(we);
+ return HRESULT_FROM_WIN32(we);
+ }
+
+ scoped_ptr<DictionaryValue> dict(new DictionaryValue());
+ dict->SetInteger(keys::kIdKey, window_id);
+ dict->SetBoolean(keys::kFocusedKey,
+ (window == window_utils::GetTopLevelParent(::GetForegroundWindow())));
+ dict->SetInteger(keys::kLeftKey, window_rect.left);
+ dict->SetInteger(keys::kTopKey, window_rect.top);
+ dict->SetInteger(keys::kWidthKey, window_rect.right - window_rect.left);
+ dict->SetInteger(keys::kHeightKey, window_rect.bottom - window_rect.top);
+ dict->SetBoolean(keys::kIncognitoKey, ie_util::GetIEIsInPrivateBrowsing());
+ // TODO(mad@chromium.org): for now, always setting to "normal" since
+ // we don't yet have a way to tell if the window is a popup or not.
+ dict->SetString(keys::kWindowTypeKey, keys::kWindowTypeValueNormal);
+
+ return SendEvent(ext_event_names::kOnWindowCreated, *dict.get());
+}
+
+HRESULT WindowEventsFunnel::OnFocusChanged(int window_id) {
+ scoped_ptr<Value> args(Value::CreateIntegerValue(window_id));
+ return SendEvent(ext_event_names::kOnWindowFocusedChanged, *args.get());
+}
+
+HRESULT WindowEventsFunnel::OnRemoved(int window_id) {
+ scoped_ptr<Value> args(Value::CreateIntegerValue(window_id));
+ return SendEvent(ext_event_names::kOnWindowRemoved, *args.get());
+}
diff --git a/ceee/ie/broker/window_events_funnel.h b/ceee/ie/broker/window_events_funnel.h
new file mode 100644
index 0000000..0278959
--- /dev/null
+++ b/ceee/ie/broker/window_events_funnel.h
@@ -0,0 +1,52 @@
+// 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 Window Events.
+
+#ifndef CEEE_IE_BROKER_WINDOW_EVENTS_FUNNEL_H_
+#define CEEE_IE_BROKER_WINDOW_EVENTS_FUNNEL_H_
+
+#include <wtypes.h> // For HRESULT and others...
+
+#include "base/basictypes.h"
+
+
+// Fwd.
+class Value;
+
+
+// Implements a set of methods to send window related events to the Chrome.
+class WindowEventsFunnel {
+ public:
+ WindowEventsFunnel() {}
+ virtual ~WindowEventsFunnel() {}
+
+ // Un/Register our window to receive Shell Hook Notification Messages.
+ static void Initialize();
+ static void Terminate();
+
+ // Sends the windows.onCreated event to Chrome.
+ // @param window_id The identifier of the window that was created.
+ virtual HRESULT OnCreated(int window_id);
+
+ // Sends the windows.onFocusChanged event to Chrome.
+ // @param window_id The identifier of the window that received the focus.
+ virtual HRESULT OnFocusChanged(int window_id);
+
+ // Sends the windows.onRemoved event to Chrome.
+ // @param window_id The identifier of the window that was removed.
+ virtual HRESULT OnRemoved(int window_id);
+
+ protected:
+ // Send the given event to Chrome.
+ // @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:
+ DISALLOW_COPY_AND_ASSIGN(WindowEventsFunnel);
+};
+
+#endif // CEEE_IE_BROKER_WINDOW_EVENTS_FUNNEL_H_
diff --git a/ceee/ie/broker/window_events_funnel_unittest.cc b/ceee/ie/broker/window_events_funnel_unittest.cc
new file mode 100644
index 0000000..d116bbd
--- /dev/null
+++ b/ceee/ie/broker/window_events_funnel_unittest.cc
@@ -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.
+//
+// Unit tests for WindowEventsFunnel.
+
+// MockWin32 can't be included after ChromeFrameHost because of an include
+// incompatibility with atlwin.h.
+#include "ceee/testing/utils/mock_win32.h" // NOLINT
+
+#include "base/json/json_writer.h"
+#include "base/scoped_ptr.h"
+#include "base/values.h"
+#include "ceee/ie/broker/chrome_postman.h"
+#include "ceee/ie/broker/window_events_funnel.h"
+#include "ceee/ie/common/ie_util.h"
+#include "ceee/ie/testing/mock_broker_and_friends.h"
+#include "ceee/testing/utils/mock_window_utils.h"
+#include "ceee/testing/utils/test_utils.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::_;
+using testing::NotNull;
+using testing::Return;
+using testing::SetArgumentPointee;
+using testing::StrEq;
+using testing::StrictMock;
+
+MATCHER_P(ValuesEqual, value, "") {
+ return arg.Equals(value);
+}
+
+class TestWindowEventsFunnel : public WindowEventsFunnel {
+ public:
+ MOCK_METHOD2(SendEvent, HRESULT(const char*, const Value&));
+ HRESULT CallSendEvent(const char* name, const Value& value) {
+ return WindowEventsFunnel::SendEvent(name, value);
+ }
+};
+
+TEST(WindowEventsFunnelTest, SendEvent) {
+ TestWindowEventsFunnel events_funnel;
+
+ 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);
+
+ class MockChromePostman : public ChromePostman {
+ public:
+ MOCK_METHOD2(FireEvent, void(BSTR, BSTR));
+ };
+ // Simply instantiating the postman will register it as the
+ // one and only singleton to use all the time.
+ CComObjectStackEx<testing::StrictMock<MockChromePostman>> postman;
+ EXPECT_CALL(postman, FireEvent(
+ StrEq(CComBSTR(kEventName).m_str),
+ StrEq(CComBSTR(event_args_str.c_str()).m_str))).Times(1);
+ EXPECT_HRESULT_SUCCEEDED(events_funnel.CallSendEvent(kEventName, event_args));
+}
+
+// Mock IeIsInPrivateBrowsing.
+MOCK_STATIC_CLASS_BEGIN(MockIeUtil)
+ MOCK_STATIC_INIT_BEGIN(MockIeUtil)
+ MOCK_STATIC_INIT2(ie_util::GetIEIsInPrivateBrowsing,
+ GetIEIsInPrivateBrowsing);
+ MOCK_STATIC_INIT_END()
+ MOCK_STATIC0(bool, , GetIEIsInPrivateBrowsing);
+MOCK_STATIC_CLASS_END(MockIeUtil)
+
+TEST(WindowEventsFunnelTest, OnWindowCreated) {
+ testing::LogDisabler no_dchecks;
+ TestWindowEventsFunnel window_events_funnel;
+
+ int window_id = 42;
+ HWND window = reinterpret_cast<HWND>(window_id);
+
+ testing::MockUser32 user32;
+ EXPECT_CALL(user32, GetWindowRect(window, NotNull()))
+ .WillOnce(Return(FALSE));
+ EXPECT_CALL(window_events_funnel, SendEvent(_, _)).Times(0);
+ // We return the HRESULT conversion of GetLastError.
+ ::SetLastError(ERROR_INVALID_ACCESS);
+ EXPECT_HRESULT_FAILED(window_events_funnel.OnCreated(window_id));
+ ::SetLastError(ERROR_SUCCESS);
+
+ RECT window_rect = {1, 2, 3, 4};
+ EXPECT_CALL(user32, GetWindowRect(window, NotNull()))
+ .WillOnce(DoAll(SetArgumentPointee<1>(window_rect), Return(TRUE)));
+ EXPECT_CALL(user32, GetForegroundWindow()).WillOnce(Return((HWND)NULL));
+ testing::MockWindowUtils mock_windows_utils;
+ EXPECT_CALL(mock_windows_utils, GetTopLevelParent((HWND)NULL))
+ .WillOnce(Return(window));
+
+ StrictMock<MockIeUtil> mock_ie_util;
+ EXPECT_CALL(mock_ie_util, GetIEIsInPrivateBrowsing()).WillRepeatedly(
+ Return(false));
+
+ scoped_ptr<DictionaryValue> dict(new DictionaryValue());
+ dict->SetInteger(keys::kIdKey, window_id);
+ dict->SetBoolean(keys::kFocusedKey, true);
+ dict->SetInteger(keys::kLeftKey, window_rect.left);
+ dict->SetInteger(keys::kTopKey, window_rect.top);
+ dict->SetInteger(keys::kWidthKey, window_rect.right - window_rect.left);
+ dict->SetInteger(keys::kHeightKey, window_rect.bottom - window_rect.top);
+ dict->SetBoolean(keys::kIncognitoKey, false);
+ // TODO(mad@chromium.org): for now, always setting to "normal" since
+ // we are not handling popups or app windows in IE yet.
+ dict->SetString(keys::kWindowTypeKey, keys::kWindowTypeValueNormal);
+
+ EXPECT_CALL(window_events_funnel, SendEvent(StrEq(
+ ext_event_names::kOnWindowCreated), ValuesEqual(dict.get()))).
+ WillOnce(Return(S_OK));
+ EXPECT_HRESULT_SUCCEEDED(window_events_funnel.OnCreated(window_id));
+}
+
+TEST(WindowEventsFunnelTest, OnFocusChanged) {
+ TestWindowEventsFunnel window_events_funnel;
+ int window_id = 42;
+
+ scoped_ptr<Value> args(Value::CreateIntegerValue(window_id));
+ EXPECT_CALL(window_events_funnel, SendEvent(StrEq(
+ ext_event_names::kOnWindowFocusedChanged), ValuesEqual(args.get()))).
+ WillOnce(Return(S_OK));
+ EXPECT_HRESULT_SUCCEEDED(window_events_funnel.OnFocusChanged(window_id));
+}
+
+TEST(WindowEventsFunnelTest, OnWindowRemoved) {
+ TestWindowEventsFunnel window_events_funnel;
+ int window_id = 42;
+
+ scoped_ptr<Value> args(Value::CreateIntegerValue(window_id));
+ EXPECT_CALL(window_events_funnel, SendEvent(
+ StrEq(ext_event_names::kOnWindowRemoved), ValuesEqual(args.get()))).
+ WillOnce(Return(S_OK));
+ EXPECT_HRESULT_SUCCEEDED(window_events_funnel.OnRemoved(window_id));
+}
+
+} // namespace
diff --git a/ceee/ie/common/api_registration.h b/ceee/ie/common/api_registration.h
new file mode 100644
index 0000000..26dad37
--- /dev/null
+++ b/ceee/ie/common/api_registration.h
@@ -0,0 +1,96 @@
+// Copyright (c) 2010 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+//
+// Lists the APIs to be registered, and in which modules to register them.
+// This is to give us a single place to specify the list of APIs to handle
+// (in the broker) and to redirect (in each Chrome Frame host - which is not
+// necessarily in the broker and thus wouldn't have a ProductionApiDispatcher
+// object available to query for registered functions).
+
+#ifndef CEEE_IE_COMMON_API_REGISTRATION_H_
+#define CEEE_IE_COMMON_API_REGISTRATION_H_
+
+#include "chrome/browser/extensions/execute_code_in_tab_function.h"
+#include "chrome/browser/extensions/extension_cookies_api.h"
+#include "chrome/browser/extensions/extension_infobar_module.h"
+#include "chrome/browser/extensions/extension_tabs_module.h"
+
+#ifdef REGISTER_API_FUNCTION
+#error Must change the name of the REGISTER_API_FUNCTION macro in this file
+#endif // REGISTER_API_FUNCTION
+
+// You must define a REGISTER_API_FUNCTION macro in the compilation unit where
+// you include this file, before invoking any of the REGISTER_XYZ_API_FUNCTIONS
+// macros.
+//
+// In the compilation unit that registers function groups with the production
+// API dispatcher, define PRODUCTION_API_DISPATCHER and your own definition of
+// each of these macros. This may seem a bit roundabout, it's just done this
+// way to ensure that if you add function groups to this file and forget to
+// update the production API dispatcher you'll get a compilation error.
+
+#ifndef PRODUCTION_API_DISPATCHER
+
+// Registers the chrome.tab.* functions we handle.
+#define REGISTER_TAB_API_FUNCTIONS() \
+ REGISTER_API_FUNCTION(GetTab); \
+ REGISTER_API_FUNCTION(GetSelectedTab); \
+ REGISTER_API_FUNCTION(GetAllTabsInWindow); \
+ REGISTER_API_FUNCTION(CreateTab); \
+ REGISTER_API_FUNCTION(UpdateTab); \
+ REGISTER_API_FUNCTION(MoveTab); \
+ REGISTER_API_FUNCTION(TabsExecuteScript); \
+ REGISTER_API_FUNCTION(TabsInsertCSS); \
+ REGISTER_API_FUNCTION(RemoveTab)
+
+// Registers the chrome.window.* functions we handle.
+// We don't have CreateWindow in here because of a compilation limitation.
+// See the comment in the windows_api::CreateWindowFunc class for more details.
+#define REGISTER_WINDOW_API_FUNCTIONS() \
+ REGISTER_API_FUNCTION(GetWindow); \
+ REGISTER_API_FUNCTION(GetCurrentWindow); \
+ REGISTER_API_FUNCTION(GetLastFocusedWindow); \
+ REGISTER_API_FUNCTION(UpdateWindow); \
+ REGISTER_API_FUNCTION(RemoveWindow); \
+ REGISTER_API_FUNCTION(GetAllWindows)
+
+// Registers the chrome.cookies.* functions we handle.
+#define REGISTER_COOKIE_API_FUNCTIONS() \
+ REGISTER_API_FUNCTION(GetCookie); \
+ REGISTER_API_FUNCTION(GetAllCookies); \
+ REGISTER_API_FUNCTION(SetCookie); \
+ REGISTER_API_FUNCTION(RemoveCookie); \
+ REGISTER_API_FUNCTION(GetAllCookieStores)
+
+// Registers the chrome.experimental.infobars.* functions we handle.
+#define REGISTER_INFOBAR_API_FUNCTIONS() \
+ REGISTER_API_FUNCTION(ShowInfoBar)
+
+// Although we don't need to handle any chrome.experimental.webNavigation.*
+// functions, we use this macro to register permanent event handlers when
+// PRODUCTION_API_DISPATCHER is defined.
+#define REGISTER_WEBNAVIGATION_API_FUNCTIONS()
+
+// Although we don't need to handle any chrome.experimental.webRequest.*
+// functions, we use this macro to register permanent event handlers when
+// PRODUCTION_API_DISPATCHER is defined.
+#define REGISTER_WEBREQUEST_API_FUNCTIONS()
+
+// Add new tab function groups before this line.
+#endif // PRODUCTION_API_DISPATCHER
+
+// Call this to register all functions. If you don't define
+// PRODUCTION_API_DISPATCHER it will simply cause REGISTER_FUNCTION to be called
+// for all functions. Otherwise it will cause your custom implementation of
+// REGISTER_XYZ_API_FUNCTIONS to be called for each of the function groups
+// above.
+#define REGISTER_ALL_API_FUNCTIONS() \
+ REGISTER_TAB_API_FUNCTIONS(); \
+ REGISTER_WINDOW_API_FUNCTIONS(); \
+ REGISTER_COOKIE_API_FUNCTIONS(); \
+ REGISTER_INFOBAR_API_FUNCTIONS(); \
+ REGISTER_WEBNAVIGATION_API_FUNCTIONS(); \
+ REGISTER_WEBREQUEST_API_FUNCTIONS()
+
+#endif // CEEE_IE_COMMON_API_REGISTRATION_H_
diff --git a/ceee/ie/common/ceee_module_util.cc b/ceee/ie/common/ceee_module_util.cc
new file mode 100644
index 0000000..0b9335e
--- /dev/null
+++ b/ceee/ie/common/ceee_module_util.cc
@@ -0,0 +1,279 @@
+// 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.
+//
+// CEEE module-wide utilities.
+
+#include "ceee/ie/common/ceee_module_util.h"
+
+#include "base/file_path.h"
+#include "base/file_util.h"
+#include "base/logging.h"
+#include "base/path_service.h"
+#include "base/win/registry.h"
+#include "ceee/common/process_utils_win.h"
+#include "chrome/installer/util/google_update_constants.h"
+
+namespace {
+
+const wchar_t* kRegistryPath = L"SOFTWARE\\Google\\CEEE";
+const wchar_t* kRegistryValueToolbandIsHidden = L"toolband_is_hidden";
+const wchar_t* kRegistryValueToolbandPlaced = L"toolband_placed";
+const wchar_t* kRegistryValueCrxInstalledPath = L"crx_installed_path";
+const wchar_t* kRegistryValueCrxInstalledTime = L"crx_installed_time";
+
+// Global state needed by the BHO and the
+// toolband, to indicate whether ShowDW calls should affect
+// registry tracking of the user's visibility preference. A
+// non-zero value indicates that the calls should be ignored.
+LONG g_ignore_show_dw_changes = 0;
+
+bool GetCeeeRegistryBoolean(const wchar_t* key, bool default_value) {
+ base::win::RegKey hkcu(HKEY_CURRENT_USER, kRegistryPath, KEY_READ);
+ LOG_IF(ERROR, !hkcu.Valid()) << "Could not open reg key: " << kRegistryPath;
+
+ DWORD dword_value_representation = 0;
+ DWORD size = sizeof(dword_value_representation);
+ DWORD type = REG_DWORD;
+
+ if (!hkcu.Valid() ||
+ !hkcu.ReadValue(key, &dword_value_representation, &size, &type)) {
+ return default_value;
+ }
+
+ return dword_value_representation != 0;
+}
+
+void SetCeeeRegistryBoolean(const wchar_t* key, bool assign_value) {
+ base::win::RegKey hkcu(HKEY_CURRENT_USER, kRegistryPath, KEY_WRITE);
+ LOG_IF(ERROR, !hkcu.Valid()) << "Could not open reg key: " << kRegistryPath;
+
+ DWORD dword_value_representation = assign_value ? 1 : 0;
+ bool write_result = hkcu.WriteValue(key, &dword_value_representation,
+ sizeof(dword_value_representation),
+ REG_DWORD);
+
+ LOG_IF(ERROR, !write_result) << "Failed to write a registry key: " << key;
+}
+
+} // anonymous namespace
+
+namespace ceee_module_util {
+
+
+// The name of the profile we want ChromeFrame to use (for Internet Explorer).
+const wchar_t kChromeProfileName[] = L"iexplore";
+
+// The name of the profile we want ChromeFrame to use (for Internet Explorer)
+// in case when explorer is 'Run as Administrator'.
+const wchar_t kChromeProfileNameForAdmin[] = L"iexplore_admin";
+
+const wchar_t kChromeProfileNameForFirefox[] = L"ceee_ff";
+const wchar_t kInternetExplorerModuleName[] = L"iexplore.exe";
+const wchar_t kCeeeBrokerModuleName[] = L"ceee_broker.exe";
+
+std::wstring GetExtensionPath() {
+ const wchar_t* kRegistryValue = L"crx_path";
+
+ // We first check HKEY_CURRENT_USER, then HKEY_LOCAL_MACHINE. This is to
+ // let individual users override the machine-wide setting. The machine-wide
+ // setting is needed as our installer runs as system.
+ //
+ // On 64-bit Windows, there are separate versions of the registry for 32-bit
+ // applications and 64-bit applications. When you manually set things in the
+ // registry, you may have set them using the 32-bit regedit program
+ // (generally at c:\windows\SysWOW64\regedit.exe) or the 64-bit version,
+ // confusingly named regedt32.exe (at c:\windows\system32\regedt32.exe).
+ //
+ // You need to make sure you set the version of the registry corresponding to
+ // the way this executable is compiled.
+ //
+ // If the registry values are not set, then attempt to locate the extension
+ // by convention: that is, in a folder called
+ // "[32-bit program files]\Google\CEEE\Extensions", look for the
+ // first valid directory and use that. Eventually, when we support more
+ // than one extension, we can load/install all directories and/or crx files
+ // found here.
+ std::wstring crx_path;
+ base::win::RegKey hkcu(HKEY_CURRENT_USER, kRegistryPath, KEY_READ);
+ base::win::RegKey hklm(HKEY_LOCAL_MACHINE, kRegistryPath, KEY_READ);
+
+ base::win::RegKey* keys[] = { &hkcu, &hklm };
+ for (int i = 0; i < arraysize(keys); ++i) {
+ if (keys[i]->Valid() && keys[i]->ReadValue(kRegistryValue, &crx_path))
+ break;
+ }
+
+ if (crx_path.size() == 0u) {
+ FilePath file_path;
+ PathService::Get(base::DIR_PROGRAM_FILES, &file_path);
+
+ file_path = file_path.Append(L"Google").Append(L"CEEE").
+ Append(L"Extensions");
+ if (!file_path.empty()) {
+ // First check for a .crx file (we prefer the .crx)
+ file_util::FileEnumerator e(
+ file_path, false, file_util::FileEnumerator::FILES);
+ for (FilePath ext = e.Next(); !ext.empty(); ext = e.Next()) {
+ if (ext.Extension() == L".crx") {
+ crx_path = ext.value();
+ break;
+ }
+ }
+
+ if (crx_path.size() == 0u) {
+ // Use the first directory instead, in the hope that it is an
+ // exploded extension directory.
+ file_util::FileEnumerator e(
+ file_path, false, file_util::FileEnumerator::DIRECTORIES);
+ FilePath directory = e.Next();
+ if (!directory.empty()) {
+ crx_path = directory.value();
+ }
+ }
+ }
+ }
+
+ // Don't DCHECK here - it's an expected case that no .crx file is provided
+ // for installation along with CEEE.
+ LOG_IF(WARNING, crx_path.empty()) << "No CRX found to install.";
+ return crx_path;
+}
+
+FilePath GetInstalledExtensionPath() {
+ std::wstring crx_path;
+
+ base::win::RegKey hkcu(HKEY_CURRENT_USER, kRegistryPath, KEY_READ);
+ // Default value is good enough for us.
+ hkcu.ReadValue(kRegistryValueCrxInstalledPath, &crx_path);
+
+ return FilePath(crx_path);
+}
+
+base::Time GetInstalledExtensionTime() {
+ int64 crx_time = 0;
+ DWORD size = sizeof(crx_time);
+
+ base::win::RegKey hkcu(HKEY_CURRENT_USER, kRegistryPath, KEY_READ);
+ // Default value is good enough for us.
+ hkcu.ReadValue(kRegistryValueCrxInstalledTime, &crx_time, &size, NULL);
+
+ return base::Time::FromInternalValue(crx_time);
+}
+
+bool NeedToInstallExtension() {
+ std::wstring path = GetExtensionPath();
+ if (path.empty()) {
+ // No extension to install, so no need to install the extension.
+ return false;
+ }
+
+ const FilePath crx_path(path);
+ if (IsCrxOrEmpty(crx_path.value())) {
+ if (crx_path == GetInstalledExtensionPath()) {
+ base::Time installed_time;
+ base::PlatformFileInfo extension_info;
+ const bool success = file_util::GetFileInfo(crx_path, &extension_info);
+ // If the call above didn't succeed, assume we need to install.
+ return !success ||
+ extension_info.last_modified > GetInstalledExtensionTime();
+ }
+
+ return true;
+ }
+
+ return false;
+}
+
+void SetInstalledExtensionPath(const FilePath& path) {
+ base::PlatformFileInfo extension_info;
+ const bool success = file_util::GetFileInfo(path, &extension_info);
+ const int64 crx_time = success ?
+ extension_info.last_modified.ToInternalValue() :
+ base::Time::Now().ToInternalValue();
+
+ base::win::RegKey key(HKEY_CURRENT_USER, kRegistryPath, KEY_WRITE);
+ bool write_result = key.WriteValue(kRegistryValueCrxInstalledTime,
+ &crx_time,
+ sizeof(crx_time),
+ REG_QWORD);
+ DCHECK(write_result);
+ write_result = key.WriteValue(kRegistryValueCrxInstalledPath,
+ path.value().c_str());
+ DCHECK(write_result);
+}
+
+bool IsCrxOrEmpty(const std::wstring& path) {
+ return (path.empty() ||
+ (path.substr(std::max(path.size() - 4, 0u)) == L".crx"));
+}
+
+void SetOptionToolbandIsHidden(bool is_hidden) {
+ SetCeeeRegistryBoolean(kRegistryValueToolbandIsHidden, is_hidden);
+}
+
+bool GetOptionToolbandIsHidden() {
+ return GetCeeeRegistryBoolean(kRegistryValueToolbandIsHidden, false);
+}
+
+void SetOptionToolbandForceReposition(bool reposition_next_time) {
+ SetCeeeRegistryBoolean(kRegistryValueToolbandPlaced, !reposition_next_time);
+}
+
+bool GetOptionToolbandForceReposition() {
+ return !GetCeeeRegistryBoolean(kRegistryValueToolbandPlaced, false);
+}
+
+void SetIgnoreShowDWChanges(bool ignore) {
+ if (ignore) {
+ ::InterlockedIncrement(&g_ignore_show_dw_changes);
+ } else {
+ ::InterlockedDecrement(&g_ignore_show_dw_changes);
+ }
+}
+
+bool GetIgnoreShowDWChanges() {
+ return ::InterlockedExchangeAdd(&g_ignore_show_dw_changes, 0) != 0;
+}
+
+const wchar_t* GetBrokerProfileNameForIe() {
+ bool running_as_admin = false;
+ HRESULT hr =
+ process_utils_win::IsCurrentProcessUacElevated(&running_as_admin);
+ DCHECK(SUCCEEDED(hr));
+
+ // Profile name for 'runas' mode has to be different because this mode spawns
+ // its own copy of ceee_broker. To avoid scrambling user data, we run it in
+ // different profile. In the unlikely event of even failing to retrieve info,
+ // run as normal user.
+ return (FAILED(hr) || !running_as_admin) ? kChromeProfileName
+ : kChromeProfileNameForAdmin;
+}
+
+// TODO(vitalybuka@google.com) : remove this code and use BrowserDistribution.
+// BrowserDistribution requires modification to know about CEEE (bb3136374).
+std::wstring GetCromeFrameClientStateKey() {
+ static const wchar_t kChromeFrameGuid[] =
+ L"{8BA986DA-5100-405E-AA35-86F34A02ACBF}";
+ std::wstring key(google_update::kRegPathClientState);
+ key.append(L"\\");
+ key.append(kChromeFrameGuid);
+ return key;
+}
+
+// TODO(vitalybuka@google.com) : remove this code and use
+// GoogleUpdateSettings::GetCollectStatsConsent() code.
+// BrowserDistribution requires modification to know about CEEE (bb3136374).
+bool GetCollectStatsConsent() {
+ std::wstring reg_path = GetCromeFrameClientStateKey();
+ base::win::RegKey key(HKEY_CURRENT_USER, reg_path.c_str(), KEY_READ);
+ DWORD value;
+ if (!key.ReadValueDW(google_update::kRegUsageStatsField, &value)) {
+ base::win::RegKey hklm_key(HKEY_LOCAL_MACHINE, reg_path.c_str(), KEY_READ);
+ if (!hklm_key.ReadValueDW(google_update::kRegUsageStatsField, &value))
+ return false;
+ }
+ return (1 == value);
+}
+
+} // namespace ceee_module_util
diff --git a/ceee/ie/common/ceee_module_util.h b/ceee/ie/common/ceee_module_util.h
new file mode 100644
index 0000000..e7b2f30
--- /dev/null
+++ b/ceee/ie/common/ceee_module_util.h
@@ -0,0 +1,101 @@
+// 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.
+//
+// CEEE module-wide utilities.
+
+#ifndef CEEE_IE_COMMON_TOOLBAND_MODULE_UTIL_H__
+#define CEEE_IE_COMMON_TOOLBAND_MODULE_UTIL_H__
+
+#include <wtypes.h>
+#include <string>
+
+#include "base/file_path.h"
+#include "base/time.h"
+
+namespace ceee_module_util {
+
+void AddRefModuleWorkerThread();
+void ReleaseModuleWorkerThread();
+
+// 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);
+
+void Lock();
+void Unlock();
+
+LONG LockModule();
+LONG UnlockModule();
+
+class AutoLock {
+ public:
+ AutoLock() {
+ Lock();
+ }
+ ~AutoLock() {
+ Unlock();
+ }
+};
+
+// Gets the path of a .crx that should be installed at the same time as
+// CEEE is installed. May be empty, meaning no .crx files need to be
+// installed.
+//
+// If the returned path does not have a .crx extension, it should be
+// assumed to be an exploded extension directory rather than a .crx
+std::wstring GetExtensionPath();
+
+// Return the path and time of the last installation that occurred.
+FilePath GetInstalledExtensionPath();
+base::Time GetInstalledExtensionTime();
+
+// Return true if the extension is outdated compared to the registry
+bool NeedToInstallExtension();
+
+// Install an extension and sets the right registry values
+void SetInstalledExtensionPath(const FilePath& path);
+
+// Returns true if the given path ends with .crx or is empty (as an
+// unset "what to load/install" preference means we should expect
+// an extension to already be installed).
+bool IsCrxOrEmpty(const std::wstring& path);
+
+// Stores/reads a registry entry that tracks whether the user has made the
+// toolband visible or hidden.
+void SetOptionToolbandIsHidden(bool isHidden);
+bool GetOptionToolbandIsHidden();
+
+// Stores/reads a registry entry that tracks whether intial (after setup)
+// positioning of the toolband has been completed.
+void SetOptionToolbandForceReposition(bool reposition_next_time);
+bool GetOptionToolbandForceReposition();
+
+// Indicates whether ShowDW calls should affect registry tracking of the
+// user's visibility preference.
+void SetIgnoreShowDWChanges(bool ignore);
+bool GetIgnoreShowDWChanges();
+
+// Chooses between kChromeProfileName and kChromeProfileNameForAdmin depending
+// on process properties (run as admin or not).
+const wchar_t* GetBrokerProfileNameForIe();
+
+// Returns true if Chrome Frame is allowed to send usage stats.
+bool GetCollectStatsConsent();
+
+// Returns Google Update ClientState registry key for ChromeFrame.
+std::wstring GetCromeFrameClientStateKey();
+
+// The name of the profile that is used by the Firefox CEEE.
+extern const wchar_t kChromeProfileNameForFirefox[];
+
+// The name of the Internet Explorer executable.
+extern const wchar_t kInternetExplorerModuleName[];
+
+// The name of the CEEE Broker executable.
+extern const wchar_t kCeeeBrokerModuleName[];
+
+} // namespace
+
+#endif // CEEE_IE_COMMON_TOOLBAND_MODULE_UTIL_H__
diff --git a/ceee/ie/common/ceee_module_util_unittest.cc b/ceee/ie/common/ceee_module_util_unittest.cc
new file mode 100644
index 0000000..67c0dd1
--- /dev/null
+++ b/ceee/ie/common/ceee_module_util_unittest.cc
@@ -0,0 +1,287 @@
+// 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 the CEEE module-wide utilities.
+
+#include "ceee/ie/common/ceee_module_util.h"
+
+#include <wtypes.h>
+#include <string>
+
+#include "base/file_path.h"
+#include "base/file_util.h"
+#include "base/logging.h"
+#include "base/path_service.h"
+#include "base/win/registry.h"
+#include "base/string_util.h"
+#include "ceee/common/process_utils_win.h"
+#include "ceee/ie/testing/mock_broker_and_friends.h"
+#include "ceee/testing/utils/mock_com.h"
+#include "ceee/testing/utils/mock_window_utils.h"
+#include "ceee/testing/utils/mock_win32.h"
+#include "ceee/testing/utils/test_utils.h"
+#include "chrome/installer/util/google_update_constants.h"
+#include "testing/gmock/include/gmock/gmock.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+namespace {
+
+using testing::_;
+using testing::DoAll;
+using testing::NotNull;
+using testing::SetArgumentPointee;
+using testing::StrictMock;
+using testing::Return;
+
+const wchar_t* kReplacementRoot =
+ L"Software\\Google\\InstallUtilUnittest";
+const wchar_t* kHKCUReplacement =
+ L"Software\\Google\\InstallUtilUnittest\\HKCU";
+const wchar_t* kHKLMReplacement =
+ L"Software\\Google\\InstallUtilUnittest\\HKLM";
+
+MOCK_STATIC_CLASS_BEGIN(MockProcessWinUtils)
+ MOCK_STATIC_INIT_BEGIN(MockProcessWinUtils)
+ MOCK_STATIC_INIT2(process_utils_win::IsCurrentProcessUacElevated,
+ IsCurrentProcessUacElevated);
+ MOCK_STATIC_INIT_END()
+
+ MOCK_STATIC1(HRESULT, , IsCurrentProcessUacElevated, bool*);
+MOCK_STATIC_CLASS_END(MockProcessWinUtils)
+
+
+class CeeeModuleUtilTest : public testing::Test {
+ protected:
+ static const DWORD kFalse = 0;
+ static const DWORD kTrue = 1;
+ static const DWORD kInvalid = 5;
+
+ virtual void SetUp() {
+ // Wipe the keys we redirect to.
+ // This gives us a stable run, even in the presence of previous
+ // crashes or failures.
+ LSTATUS err = SHDeleteKey(HKEY_CURRENT_USER, kReplacementRoot);
+ EXPECT_TRUE(err == ERROR_SUCCESS || err == ERROR_FILE_NOT_FOUND);
+
+ // Create the keys we're redirecting HKCU and HKLM to.
+ ASSERT_TRUE(hkcu_.Create(HKEY_CURRENT_USER, kHKCUReplacement, KEY_READ));
+ ASSERT_TRUE(hklm_.Create(HKEY_CURRENT_USER, kHKLMReplacement, KEY_READ));
+
+ // And do the switcharoo.
+ ASSERT_EQ(ERROR_SUCCESS,
+ ::RegOverridePredefKey(HKEY_CURRENT_USER, hkcu_.Handle()));
+ ASSERT_EQ(ERROR_SUCCESS,
+ ::RegOverridePredefKey(HKEY_LOCAL_MACHINE, hklm_.Handle()));
+ }
+
+ virtual void TearDown() {
+ // Undo the redirection.
+ EXPECT_EQ(ERROR_SUCCESS, ::RegOverridePredefKey(HKEY_CURRENT_USER, NULL));
+ EXPECT_EQ(ERROR_SUCCESS, ::RegOverridePredefKey(HKEY_LOCAL_MACHINE, NULL));
+
+ // Close our handles and delete the temp keys we redirected to.
+ hkcu_.Close();
+ hklm_.Close();
+ EXPECT_EQ(ERROR_SUCCESS, SHDeleteKey(HKEY_CURRENT_USER, kReplacementRoot));
+ }
+
+ base::win::RegKey hkcu_;
+ base::win::RegKey hklm_;
+};
+
+// Mock PathService::Get that is used to look up files.
+MOCK_STATIC_CLASS_BEGIN(MockPathService)
+ MOCK_STATIC_INIT_BEGIN(MockPathService)
+ bool (*func_ptr)(int, FilePath*) = PathService::Get;
+ MOCK_STATIC_INIT2(func_ptr, Get);
+ MOCK_STATIC_INIT_END()
+
+ MOCK_STATIC2(bool, , Get, int, FilePath*);
+MOCK_STATIC_CLASS_END(MockPathService);
+
+// Empty registry case (without crx)
+TEST_F(CeeeModuleUtilTest, ExtensionPathTestNoRegistry) {
+ namespace cmu = ceee_module_util;
+
+ StrictMock<MockPathService> mock_path;
+ FilePath temp_path;
+ ASSERT_TRUE(file_util::CreateNewTempDirectory(L"CeeeModuleUtilTest",
+ &temp_path));
+
+ EXPECT_CALL(mock_path, Get(base::DIR_PROGRAM_FILES, NotNull())).
+ WillOnce(DoAll(
+ SetArgumentPointee<1>(temp_path),
+ Return(true)));
+ EXPECT_EQ(std::wstring(), cmu::GetExtensionPath());
+
+ FilePath full_path = temp_path.Append(L"Google").
+ Append(L"CEEE").Append(L"Extensions");
+ FilePath crx_path = full_path.Append(L"testing.crx");
+ ASSERT_TRUE(file_util::CreateDirectory(full_path));
+ FilePath temp_file_path;
+ ASSERT_TRUE(file_util::CreateTemporaryFileInDir(full_path, &temp_file_path));
+ ASSERT_TRUE(file_util::Move(temp_file_path, crx_path));
+ EXPECT_CALL(mock_path, Get(base::DIR_PROGRAM_FILES, NotNull())).
+ WillOnce(DoAll(SetArgumentPointee<1>(temp_path), Return(true)));
+ EXPECT_EQ(crx_path.value(), cmu::GetExtensionPath());
+
+ // Clean up.
+ file_util::Delete(temp_path, true);
+}
+
+TEST_F(CeeeModuleUtilTest, ExtensionPathTest) {
+ namespace cmu = ceee_module_util;
+
+ // The FilePath::Get method shouldn't be called if we take the value
+ // from the registry.
+ StrictMock<MockPathService> mock_path;
+
+ // Creates a registry key
+ base::win::RegKey hkcu(HKEY_CURRENT_USER, L"SOFTWARE\\Google\\CEEE",
+ KEY_WRITE);
+ base::win::RegKey hklm(HKEY_LOCAL_MACHINE, L"SOFTWARE\\Google\\CEEE",
+ KEY_WRITE);
+
+ // Create a random string so we can compare the result with it.
+ // Ideally, this would be a really random string, not some arbitrary constant.
+ std::wstring path = L"asdfqwerasdfqwer";
+ const wchar_t* kRegistryValueName = L"crx_path";
+
+ hklm.WriteValue(kRegistryValueName, path.c_str());
+ hklm.Close();
+ EXPECT_EQ(path, cmu::GetExtensionPath());
+
+ // Change the random string and verify we get the new one if we set it
+ // to HKCU
+ path += L"_HKCU";
+ hkcu.WriteValue(kRegistryValueName, path.c_str());
+ hkcu.Close();
+ EXPECT_EQ(path, cmu::GetExtensionPath());
+}
+
+TEST_F(CeeeModuleUtilTest, NeedToInstallExtension) {
+ namespace cmu = ceee_module_util;
+
+ // Create our registry key and a temporary file
+ base::win::RegKey hkcu(HKEY_CURRENT_USER, L"SOFTWARE\\Google\\CEEE",
+ KEY_WRITE);
+ FilePath temp_path;
+ ASSERT_TRUE(file_util::CreateNewTempDirectory(L"CeeeModuleUtilTest",
+ &temp_path));
+ FilePath crx_path = temp_path.Append(L"temp.crx");
+ FilePath temp_file_path;
+ ASSERT_TRUE(file_util::CreateTemporaryFileInDir(temp_path, &temp_file_path));
+ ASSERT_TRUE(file_util::Move(temp_file_path, crx_path));
+
+ const wchar_t* kRegistryCrxPathValueName = L"crx_path";
+ const wchar_t* kRegistryValueCrxInstalledPath = L"crx_installed_path";
+ const wchar_t* kRegistryValueCrxInstalledTime = L"crx_installed_time";
+
+ hkcu.WriteValue(kRegistryCrxPathValueName, crx_path.value().c_str());
+
+ // Those should return empty values
+ // We use EXPECT_TRUE instead of EXPECT_EQ because there's no operator<<
+ // declared for FilePath and base::Time so we cannot output the
+ // expected/received values.
+ EXPECT_TRUE(FilePath() == cmu::GetInstalledExtensionPath());
+ EXPECT_TRUE(base::Time() == cmu::GetInstalledExtensionTime());
+
+ // Now we should need to install, since the keys aren't there yet.
+ EXPECT_TRUE(cmu::NeedToInstallExtension());
+
+ // And if we update the install info, we shouldn't need to anymore.
+ cmu::SetInstalledExtensionPath(crx_path);
+ EXPECT_FALSE(cmu::NeedToInstallExtension());
+
+ // We get the installed path and time and verify them against our values.
+ base::PlatformFileInfo crx_info;
+ ASSERT_TRUE(file_util::GetFileInfo(crx_path, &crx_info));
+ EXPECT_TRUE(crx_path == cmu::GetInstalledExtensionPath());
+ EXPECT_TRUE(crx_info.last_modified == cmu::GetInstalledExtensionTime());
+
+ // Finally, if we update the file time, we should be able to install again.
+ // But we must make sure to cross the 10ms boundary which is the granularity
+ // of the FILETIME, so sleep for 20ms to be on the safe side.
+ ::Sleep(20);
+ ASSERT_TRUE(file_util::SetLastModifiedTime(crx_path, base::Time::Now()));
+ EXPECT_TRUE(cmu::NeedToInstallExtension());
+}
+
+TEST(CeeeModuleUtil, GetBrokerProfileNameForIe) {
+ MockProcessWinUtils mock_query_admin;
+
+ EXPECT_CALL(mock_query_admin, IsCurrentProcessUacElevated(_)).
+ WillOnce(DoAll(SetArgumentPointee<0>(false), Return(S_OK)));
+ const wchar_t* profile_user =
+ ceee_module_util::GetBrokerProfileNameForIe();
+
+ EXPECT_CALL(mock_query_admin, IsCurrentProcessUacElevated(_)).
+ WillOnce(DoAll(SetArgumentPointee<0>(true), Return(S_OK)));
+ const wchar_t* profile_admin =
+ ceee_module_util::GetBrokerProfileNameForIe();
+
+ ASSERT_STRNE(profile_user, profile_admin);
+}
+
+TEST(CeeeModuleUtil, GetCromeFrameClientStateKey) {
+ EXPECT_EQ(L"Software\\Google\\Update\\ClientState\\"
+ L"{8BA986DA-5100-405E-AA35-86F34A02ACBF}",
+ ceee_module_util::GetCromeFrameClientStateKey());
+}
+
+TEST_F(CeeeModuleUtilTest, GetCollectStatsConsentFromHkcu) {
+ EXPECT_FALSE(ceee_module_util::GetCollectStatsConsent());
+ base::win::RegKey hkcu(HKEY_CURRENT_USER,
+ ceee_module_util::GetCromeFrameClientStateKey().c_str(), KEY_WRITE);
+
+ ASSERT_TRUE(hkcu.WriteValue(google_update::kRegUsageStatsField, kTrue));
+ EXPECT_TRUE(ceee_module_util::GetCollectStatsConsent());
+
+ ASSERT_TRUE(hkcu.WriteValue(google_update::kRegUsageStatsField, kFalse));
+ EXPECT_FALSE(ceee_module_util::GetCollectStatsConsent());
+
+ ASSERT_TRUE(hkcu.WriteValue(google_update::kRegUsageStatsField, kInvalid));
+ EXPECT_FALSE(ceee_module_util::GetCollectStatsConsent());
+
+ ASSERT_TRUE(hkcu.DeleteValue(google_update::kRegUsageStatsField));
+ EXPECT_FALSE(ceee_module_util::GetCollectStatsConsent());
+}
+
+TEST_F(CeeeModuleUtilTest, GetCollectStatsConsentFromHklm) {
+ EXPECT_FALSE(ceee_module_util::GetCollectStatsConsent());
+ base::win::RegKey hklm(HKEY_LOCAL_MACHINE,
+ ceee_module_util::GetCromeFrameClientStateKey().c_str(), KEY_WRITE);
+
+ ASSERT_TRUE(hklm.WriteValue(google_update::kRegUsageStatsField, kTrue));
+ EXPECT_TRUE(ceee_module_util::GetCollectStatsConsent());
+
+ ASSERT_TRUE(hklm.WriteValue(google_update::kRegUsageStatsField, kFalse));
+ EXPECT_FALSE(ceee_module_util::GetCollectStatsConsent());
+
+ ASSERT_TRUE(hklm.WriteValue(google_update::kRegUsageStatsField, kInvalid));
+ EXPECT_FALSE(ceee_module_util::GetCollectStatsConsent());
+
+ ASSERT_TRUE(hklm.DeleteValue(google_update::kRegUsageStatsField));
+ EXPECT_FALSE(ceee_module_util::GetCollectStatsConsent());
+}
+
+TEST_F(CeeeModuleUtilTest, GetCollectStatsConsentHkcuBeforeHklm) {
+ EXPECT_FALSE(ceee_module_util::GetCollectStatsConsent());
+ base::win::RegKey hkcu(HKEY_CURRENT_USER,
+ ceee_module_util::GetCromeFrameClientStateKey().c_str(), KEY_WRITE);
+
+ base::win::RegKey hklm(HKEY_LOCAL_MACHINE,
+ ceee_module_util::GetCromeFrameClientStateKey().c_str(), KEY_WRITE);
+
+ ASSERT_TRUE(hklm.WriteValue(google_update::kRegUsageStatsField, kTrue));
+ ASSERT_TRUE(ceee_module_util::GetCollectStatsConsent());
+
+ ASSERT_TRUE(hkcu.WriteValue(google_update::kRegUsageStatsField, kFalse));
+ EXPECT_FALSE(ceee_module_util::GetCollectStatsConsent());
+
+ ASSERT_TRUE(hkcu.DeleteValue(google_update::kRegUsageStatsField));
+ ASSERT_TRUE(hklm.DeleteValue(google_update::kRegUsageStatsField));
+}
+
+} // namespace
diff --git a/ceee/ie/common/chrome_frame_host.cc b/ceee/ie/common/chrome_frame_host.cc
new file mode 100644
index 0000000..907b1f9
--- /dev/null
+++ b/ceee/ie/common/chrome_frame_host.cc
@@ -0,0 +1,373 @@
+// 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.
+//
+// ChromeFrameHost implementation.
+#include "ceee/ie/common/chrome_frame_host.h"
+
+#include <algorithm>
+#include <vector>
+
+#include "base/logging.h"
+#include "ceee/common/com_utils.h"
+#include "ceee/common/process_utils_win.h"
+#include "ceee/ie/common/ceee_module_util.h"
+#include "chrome/browser/automation/extension_automation_constants.h"
+#include "chrome/common/chrome_switches.h"
+
+#include "toolband.h" // NOLINT
+
+namespace ext = extension_automation_constants;
+
+
+_ATL_FUNC_INFO ChromeFrameHost::handler_type_idispatch_ =
+ { CC_STDCALL, VT_EMPTY, 1, { VT_DISPATCH } };
+_ATL_FUNC_INFO ChromeFrameHost::handler_type_long_ =
+ { CC_STDCALL, VT_EMPTY, 1, { VT_I4 } };
+_ATL_FUNC_INFO ChromeFrameHost::handler_type_idispatch_bstr_ =
+ { CC_STDCALL, VT_EMPTY, 2, { VT_DISPATCH, VT_BSTR } };
+_ATL_FUNC_INFO ChromeFrameHost::handler_type_idispatch_variantptr_ =
+ { CC_STDCALL, VT_EMPTY, 2, { VT_DISPATCH, VT_BYREF | VT_VARIANT } };
+_ATL_FUNC_INFO ChromeFrameHost::handler_type_bstr_i4_=
+ { CC_STDCALL, VT_EMPTY, 2, { VT_BSTR, VT_I4 } };
+_ATL_FUNC_INFO ChromeFrameHost::handler_type_bstrarray_=
+ { CC_STDCALL, VT_EMPTY, 1, { VT_ARRAY | VT_BSTR } };
+_ATL_FUNC_INFO ChromeFrameHost::handler_type_void_=
+ { CC_STDCALL, VT_EMPTY, 0, { } };
+
+// {AFA3E2CF-2C8E-4546-8CD0-A2D93759A4DE}
+extern const GUID IID_IChromeFrameHost =
+ { 0xafa3e2cf, 0x2c8e, 0x4546,
+ { 0x8c, 0xd0, 0xa2, 0xd9, 0x37, 0x59, 0xa4, 0xde } };
+
+ChromeFrameHost::ChromeFrameHost()
+ : document_loaded_(false), origin_(ext::kAutomationOrigin) {
+ LOG(INFO) << "Create ChromeFrameHost(" << this << ")";
+}
+
+ChromeFrameHost::~ChromeFrameHost() {
+ LOG(INFO) << "Destroy ChromeFrameHost(" << this << ")";
+}
+
+HRESULT ChromeFrameHost::FinalConstruct() {
+ return S_OK;
+}
+
+void ChromeFrameHost::FinalRelease() {
+}
+
+STDMETHODIMP ChromeFrameHost::GetWantsPrivileged(boolean* wants_privileged) {
+ *wants_privileged = true;
+ return S_OK;
+}
+
+STDMETHODIMP ChromeFrameHost::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 ChromeFrameHost::GetChromeProfileName(BSTR* profile_name) {
+ return chrome_profile_name_.CopyTo(profile_name);
+}
+
+STDMETHODIMP ChromeFrameHost::GetExtensionApisToAutomate(
+ BSTR* functions_enabled) {
+ DCHECK(functions_enabled != NULL);
+ HRESULT hr = S_FALSE;
+ if (event_sink_ != NULL) {
+ hr = event_sink_->OnCfGetExtensionApisToAutomate(functions_enabled);
+#ifndef NDEBUG
+ if (*functions_enabled != NULL) {
+ // Only one chrome frame host is allowed to return a list of functions
+ // to enable automation on, so make sure we are the one and only.
+ std::wstring event_name(L"google-ceee-apiautomation!");
+
+ DCHECK(chrome_profile_name_ != NULL);
+ if (chrome_profile_name_ != NULL)
+ event_name += chrome_profile_name_;
+
+ std::replace(event_name.begin(), event_name.end(), '\\', '!');
+ std::transform(
+ event_name.begin(), event_name.end(), event_name.begin(), tolower);
+ automating_extension_api_.Set(
+ ::CreateEvent(NULL, TRUE, TRUE, event_name.c_str()));
+ DWORD we = ::GetLastError();
+ DCHECK(automating_extension_api_ != NULL &&
+ we != ERROR_ALREADY_EXISTS &&
+ we != ERROR_ACCESS_DENIED);
+ }
+#endif // NDEBUG
+ }
+ return hr;
+}
+
+HRESULT ChromeFrameHost::Initialize() {
+ return S_OK;
+}
+
+STDMETHODIMP ChromeFrameHost::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_);
+
+ chrome_frame_.Release();
+#ifndef NDEBUG
+ automating_extension_api_.Close();
+#endif
+ return S_OK;
+}
+
+STDMETHODIMP_(void) ChromeFrameHost::SetEventSink(
+ IChromeFrameHostEvents* event_sink) {
+ event_sink_ = event_sink;
+}
+
+HRESULT ChromeFrameHost::InstallExtension(BSTR crx_path) {
+ if (chrome_frame_) {
+ return chrome_frame_->installExtension(crx_path);
+ } else {
+ NOTREACHED();
+ return E_UNEXPECTED;
+ }
+}
+
+HRESULT ChromeFrameHost::LoadExtension(BSTR extension_dir) {
+ if (chrome_frame_) {
+ return chrome_frame_->installExtension(extension_dir);
+ } else {
+ NOTREACHED();
+ return E_UNEXPECTED;
+ }
+}
+
+HRESULT ChromeFrameHost::GetEnabledExtensions() {
+ if (chrome_frame_) {
+ return chrome_frame_->getEnabledExtensions();
+ } else {
+ NOTREACHED();
+ return E_UNEXPECTED;
+ }
+}
+
+HRESULT ChromeFrameHost::GetSessionId(int *session_id) {
+ if (chrome_frame_) {
+ CComQIPtr<IChromeFrameInternal> chrome_frame_internal_(chrome_frame_);
+ if (chrome_frame_internal_)
+ return chrome_frame_internal_->getSessionId(session_id);
+ else
+ return kInvalidChromeSessionId;
+ }
+ NOTREACHED();
+ return E_UNEXPECTED;
+}
+
+void ChromeFrameHost::OnFinalMessage(HWND window) {
+ GetUnknown()->Release();
+}
+
+HRESULT ChromeFrameHost::SetChildSite(IUnknown* child) {
+ if (child == NULL)
+ return E_POINTER;
+
+ HRESULT hr = S_OK;
+ CComPtr<IObjectWithSite> child_site;
+ hr = child->QueryInterface(&child_site);
+ if (SUCCEEDED(hr))
+ hr = child_site->SetSite(GetUnknown());
+
+ return hr;
+}
+
+LRESULT ChromeFrameHost::OnCreate(LPCREATESTRUCT lpCreateStruct) {
+ // Grab a self-reference.
+ GetUnknown()->AddRef();
+
+ return 0;
+}
+
+HRESULT ChromeFrameHost::SetUrl(BSTR url) {
+ HRESULT hr = chrome_frame_->put_src(url);
+ DCHECK(SUCCEEDED(hr)) << "Failed to navigate Chrome Frame: " <<
+ com::LogHr(hr);
+ return hr;
+}
+
+STDMETHODIMP ChromeFrameHost::StartChromeFrame() {
+ DCHECK(!IsWindow());
+
+ // Create a message window to host our control.
+ if (NULL == Create(HWND_MESSAGE))
+ return E_FAIL;
+
+ // Create a host window instance.
+ CComPtr<IAxWinHostWindow> host;
+ HRESULT hr = CreateActiveXHost(&host);
+ if (FAILED(hr)) {
+ LOG(ERROR) << "Failed to create ActiveX host window: " << com::LogHr(hr);
+ return hr;
+ }
+
+ // 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 = CreateChromeFrame(&chrome_frame_);
+ if (FAILED(hr)) {
+ LOG(ERROR) << "Failed create Chrome Frame: " << com::LogHr(hr);
+ return hr;
+ }
+
+ // And attach it to our window. This causes the host to subclass
+ // our window and attach itself to it.
+ hr = host->AttachControl(chrome_frame_, m_hWnd);
+ if (FAILED(hr)) {
+ LOG(ERROR) << "Failed to attach Chrome Frame to the host" << com::LogHr(hr);
+ return hr;
+ }
+
+ // 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 hr;
+ }
+
+ return hr;
+}
+
+HRESULT ChromeFrameHost::CreateActiveXHost(IAxWinHostWindow** host) {
+ return CAxHostWindow::CreateInstance(host);
+}
+
+HRESULT ChromeFrameHost::CreateChromeFrame(IChromeFrame** chrome_frame) {
+ CComPtr<IChromeFrame> new_cf;
+ HRESULT hr = new_cf.CoCreateInstance(L"ChromeTab.ChromeFrame");
+ if (SUCCEEDED(hr))
+ hr = new_cf.CopyTo(chrome_frame);
+
+ return hr;
+}
+
+STDMETHODIMP ChromeFrameHost::PostMessage(BSTR message, BSTR target) {
+ if (!document_loaded_) {
+ PostedMessage posted_message = { message, target };
+ posted_messages_.push_back(posted_message);
+ return S_FALSE;
+ }
+
+ HRESULT hr = chrome_frame_->postPrivateMessage(message, origin_, target);
+
+ return hr;
+}
+
+STDMETHODIMP_(void) ChromeFrameHost::OnCfLoad(IDispatch* event) {
+ DLOG(INFO) << "OnCfLoad";
+ if (document_loaded_) {
+ // If we were already loaded, our list should be empty.
+ DCHECK(posted_messages_.empty());
+ return;
+ }
+ document_loaded_ = true;
+
+ // Flush all posted messages.
+ PostedMessageList::iterator it(posted_messages_.begin());
+ for (; it != posted_messages_.end(); ++it) {
+ HRESULT hr = chrome_frame_->postPrivateMessage(it->message, origin_,
+ it->target);
+ DCHECK(SUCCEEDED(hr)) << "postPrivateMessage failed with: " <<
+ com::LogHr(hr);
+ }
+ posted_messages_.clear();
+}
+
+STDMETHODIMP_(void) ChromeFrameHost::OnCfLoadError(IDispatch* event) {
+ DLOG(ERROR) << "OnCfLoadError";
+ DCHECK(false) << "OnCfLoadError";
+}
+
+STDMETHODIMP_(void) ChromeFrameHost::OnCfExtensionReady(BSTR path,
+ int response) {
+ DLOG(INFO) << "OnCfExtensionReady: " << path << ", " << response;
+ // Early exit if there's no sink.
+ if (event_sink_ == NULL)
+ return;
+ event_sink_->OnCfExtensionReady(path, response);
+}
+
+STDMETHODIMP_(void) ChromeFrameHost::OnCfGetEnabledExtensionsComplete(
+ SAFEARRAY* extension_directories) {
+ DLOG(INFO) << "OnCfGetEnabledExtensionsComplete";
+ if (event_sink_)
+ event_sink_->OnCfGetEnabledExtensionsComplete(extension_directories);
+}
+
+STDMETHODIMP_(void) ChromeFrameHost::OnCfChannelError() {
+ DCHECK(false) << "OnCfChannelError means that Chrome has Crashed!";
+ if (event_sink_)
+ event_sink_->OnCfChannelError();
+}
+
+STDMETHODIMP_(void) ChromeFrameHost::OnCfMessage(IDispatch* event) {
+ DLOG(INFO) << "OnCfMessage";
+}
+
+STDMETHODIMP_(void) ChromeFrameHost::OnCfReadyStateChanged(LONG state) {
+ DLOG(INFO) << "OnCfReadyStateChanged(" << state << ")";
+ if (event_sink_)
+ event_sink_->OnCfReadyStateChanged(state);
+}
+
+STDMETHODIMP_(void) ChromeFrameHost::OnCfPrivateMessage(IDispatch* event,
+ BSTR target) {
+ // Early exit if there's no sink.
+ if (event_sink_ == NULL)
+ return;
+
+ // Make sure that the message has a "data" member and get it. This should
+ // be a JSON-encoded command to execute.
+ CComDispatchDriver event_dispatch(event);
+
+ CComVariant origin;
+ HRESULT hr = event_dispatch.GetPropertyByName(L"origin", &origin);
+ DCHECK(SUCCEEDED(hr) && origin.vt == VT_BSTR);
+ if (FAILED(hr) || origin.vt != VT_BSTR) {
+ NOTREACHED() << "No origin on event";
+ return;
+ }
+
+ CComVariant data;
+ hr = event_dispatch.GetPropertyByName(L"data", &data);
+ DCHECK(SUCCEEDED(hr) && data.vt == VT_BSTR);
+ if (FAILED(hr) || data.vt != VT_BSTR) {
+ NOTREACHED() << "No data on event";
+ return;
+ }
+
+ // Forward to the sink.
+ event_sink_->OnCfPrivateMessage(V_BSTR(&data), V_BSTR(&origin), target);
+}
+
+STDMETHODIMP_(void) ChromeFrameHost::SetChromeProfileName(
+ const wchar_t* chrome_profile_name) {
+ chrome_profile_name_ = chrome_profile_name;
+ DLOG(INFO) << "Assigned profile name " << chrome_profile_name_;
+}
diff --git a/ceee/ie/common/chrome_frame_host.h b/ceee/ie/common/chrome_frame_host.h
new file mode 100644
index 0000000..ef43499
--- /dev/null
+++ b/ceee/ie/common/chrome_frame_host.h
@@ -0,0 +1,237 @@
+// 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 ChromeFrameHost implementation.
+#ifndef CEEE_IE_COMMON_CHROME_FRAME_HOST_H_
+#define CEEE_IE_COMMON_CHROME_FRAME_HOST_H_
+
+#include <atlbase.h>
+#include <atlwin.h>
+#include <atlcrack.h>
+#include <list>
+
+#include "base/basictypes.h"
+#include "base/scoped_handle.h"
+#include "ceee/common/initializing_coclass.h"
+#include "chrome_tab.h" // NOLINT
+
+// fwd.
+struct IAxWinHostWindow;
+
+class IChromeFrameHostEvents: public IUnknown {
+ public:
+ virtual HRESULT OnCfReadyStateChanged(LONG state) = 0;
+ virtual HRESULT OnCfPrivateMessage(BSTR msg, BSTR origin, BSTR target) = 0;
+ virtual HRESULT OnCfExtensionReady(BSTR path, int response) = 0;
+ virtual HRESULT OnCfGetEnabledExtensionsComplete(SAFEARRAY* base_dirs) = 0;
+ virtual HRESULT OnCfGetExtensionApisToAutomate(BSTR* functions_enabled) = 0;
+ virtual HRESULT OnCfChannelError() = 0;
+};
+
+// This is the interface the chrome frame host presents to its consumers.
+extern const GUID IID_IChromeFrameHost;
+class IChromeFrameHost: public IUnknown {
+ public:
+ // Set the name of the profile we want Chrome Frame to use.
+ // @param chrome_profile_name The name of the profile to use.
+ STDMETHOD_(void, SetChromeProfileName)(
+ const wchar_t* chrome_profile_name) = 0;
+
+ // Set the URL we want to navigate Chrome Frame to once it is ready.
+ // @param url The URL to navigate to.
+ STDMETHOD(SetUrl)(BSTR url) = 0;
+
+ // Creates and initializes our Chrome Frame instance.
+ STDMETHOD(StartChromeFrame)() = 0;
+
+ // Posts a message through Chrome Frame, or enqueues it if
+ // Chrome Frame is not yet ready and @p queueable is true.
+ // @param message The message to post to Chrome Frame.
+ // @param target Where we want the message to be posted within Chrome.
+ STDMETHOD(PostMessage)(BSTR message, BSTR target) = 0;
+
+ // Tears down an initialized ChromeFrameHost.
+ STDMETHOD(TearDown)() = 0;
+
+ // Sets the event sink for this ChromeFrameHost.
+ STDMETHOD_(void, SetEventSink)(IChromeFrameHostEvents* event_sink) = 0;
+
+ // Installs the given extension. Results come back via
+ // IChromeFrameHostEvents::OnCfExtensionReady.
+ STDMETHOD(InstallExtension)(BSTR crx_path) = 0;
+
+ // Loads the given exploded extension directory. Results come back via
+ // IChromeFrameHostEvents::OnCfExtensionReady.
+ STDMETHOD(LoadExtension)(BSTR extension_dir) = 0;
+
+ // Initiates a request for installed extensions. Results come back via
+ // IChromeFrameHostEvents::OnCfGetEnabledExtensionsComplete.
+ STDMETHOD(GetEnabledExtensions)() = 0;
+
+ // Retrieves the session_id used by Chrome for the CF tab. Will return S_FALSE
+ // if the session id is not yet available.
+ // The session_id is the id used for the Tab javascript object.
+ STDMETHOD(GetSessionId)(int* session_id) = 0;
+};
+
+class ATL_NO_VTABLE ChromeFrameHost
+ : public CComObjectRootEx<CComSingleThreadModel>,
+ public InitializingCoClass<ChromeFrameHost>,
+ public IServiceProviderImpl<ChromeFrameHost>,
+ public IChromeFrameHost,
+ public IChromeFramePrivileged,
+ public IDispEventSimpleImpl<0,
+ ChromeFrameHost,
+ &DIID_DIChromeFrameEvents>,
+ public CWindowImpl<ChromeFrameHost> {
+ public:
+ typedef IDispEventSimpleImpl<0,
+ ChromeFrameHost,
+ &DIID_DIChromeFrameEvents> ChromeFrameEvents;
+ ChromeFrameHost();
+ ~ChromeFrameHost();
+
+ BEGIN_COM_MAP(ChromeFrameHost)
+ COM_INTERFACE_ENTRY(IServiceProvider)
+ COM_INTERFACE_ENTRY_IID(IID_IChromeFrameHost, IChromeFrameHost)
+ COM_INTERFACE_ENTRY(IChromeFramePrivileged)
+ END_COM_MAP()
+
+ BEGIN_SERVICE_MAP(ChromeFrameHost)
+ SERVICE_ENTRY(SID_ChromeFramePrivileged)
+ END_SERVICE_MAP()
+
+ BEGIN_SINK_MAP(ChromeFrameHost)
+ SINK_ENTRY_INFO(0, DIID_DIChromeFrameEvents, CF_EVENT_DISPID_ONLOAD,
+ OnCfLoad, &handler_type_idispatch_)
+ SINK_ENTRY_INFO(0, DIID_DIChromeFrameEvents, CF_EVENT_DISPID_ONLOADERROR,
+ OnCfLoadError, &handler_type_idispatch_)
+ SINK_ENTRY_INFO(0, DIID_DIChromeFrameEvents, CF_EVENT_DISPID_ONMESSAGE,
+ OnCfMessage, &handler_type_idispatch_)
+ SINK_ENTRY_INFO(0, DIID_DIChromeFrameEvents,
+ CF_EVENT_DISPID_ONREADYSTATECHANGED,
+ OnCfReadyStateChanged, &handler_type_long_)
+ SINK_ENTRY_INFO(0, DIID_DIChromeFrameEvents,
+ CF_EVENT_DISPID_ONPRIVATEMESSAGE,
+ OnCfPrivateMessage, &handler_type_idispatch_bstr_)
+ 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_ONCHANNELERROR,
+ OnCfChannelError, &handler_type_void_)
+ END_SINK_MAP()
+
+ DECLARE_PROTECT_FINAL_CONSTRUCT()
+
+ HRESULT Initialize();
+
+ HRESULT FinalConstruct();
+ void FinalRelease();
+
+ BEGIN_MSG_MAP(ChromeFrameHost)
+ MSG_WM_CREATE(OnCreate)
+ END_MSG_MAP()
+
+ // @name IChromeFramePrivileged implementation.
+ // @{
+ STDMETHOD(GetWantsPrivileged)(boolean* wants_privileged);
+ STDMETHOD(GetChromeExtraArguments)(BSTR* args);
+ STDMETHOD(GetChromeProfileName)(BSTR* args);
+ STDMETHOD(GetExtensionApisToAutomate)(BSTR* functions_enabled);
+ // @}
+
+ // @name ChromeFrame event handlers
+ // @{
+ STDMETHOD_(void, OnCfLoad)(IDispatch* event);
+ STDMETHOD_(void, OnCfLoadError)(IDispatch* event);
+ STDMETHOD_(void, OnCfMessage)(IDispatch* event);
+ STDMETHOD_(void, OnCfReadyStateChanged)(LONG state);
+ STDMETHOD_(void, OnCfPrivateMessage)(IDispatch *event, BSTR target);
+ STDMETHOD_(void, OnCfExtensionReady)(BSTR path, int response);
+ STDMETHOD_(void, OnCfGetEnabledExtensionsComplete)(
+ SAFEARRAY* extension_directories);
+ STDMETHOD_(void, OnCfChannelError)(void);
+ // @}
+
+ // @name IChromeFrameHost implementation.
+ // @{
+ STDMETHOD_(void, SetChromeProfileName)(const wchar_t* chrome_profile_name);
+ STDMETHOD(SetUrl)(BSTR url);
+ STDMETHOD(StartChromeFrame)();
+ STDMETHOD(PostMessage)(BSTR message, BSTR target);
+ STDMETHOD(TearDown)();
+ STDMETHOD_(void, SetEventSink)(IChromeFrameHostEvents* event_sink);
+ STDMETHOD(InstallExtension)(BSTR crx_path);
+ STDMETHOD(LoadExtension)(BSTR extension_dir);
+ STDMETHOD(GetEnabledExtensions)();
+ STDMETHOD(GetSessionId)(int* session_id);
+ // @}
+
+ protected:
+ virtual HRESULT CreateActiveXHost(IAxWinHostWindow** host);
+ virtual HRESULT CreateChromeFrame(IChromeFrame** chrome_frame);
+
+ // 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);
+
+ private:
+ struct PostedMessage {
+ CComBSTR message;
+ CComBSTR target;
+ };
+ typedef std::list<PostedMessage> PostedMessageList;
+
+ // Set us as site for child.
+ HRESULT SetChildSite(IUnknown* child);
+
+ // Our Chrome Frame instance.
+ CComPtr<IChromeFrame> chrome_frame_;
+
+ // The Chrome profile we ask to connect with.
+ CComBSTR chrome_profile_name_;
+
+ // Messages posted before Chrome Frame has loaded have to be enqueued,
+ // to ensure that the toolband extension has propertly loaded and
+ // initialized before we attempt to post messages at it.
+ // This list stores such messages until Chrome Frame reports the
+ // document loaded.
+ PostedMessageList posted_messages_;
+
+ // True iff Chrome Frame has reported a document loaded event.
+ // TODO(mad@chromium.org): Use a three states variable to take the
+ // error case into account.
+ bool document_loaded_;
+
+#ifndef NDEBUG
+ // We use a cross process event to make sure there is only one chrome frame
+ // host that returns ExtensionApisToAutomate... But only needed for a DCHECK.
+ ScopedHandle automating_extension_api_;
+#endif
+
+ // A cached BSTR for the posted messages origin (which is kAutomationOrigin).
+ CComBSTR origin_;
+
+ // Our event sink.
+ CComPtr<IChromeFrameHostEvents> event_sink_;
+
+ // 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_idispatch_variantptr_;
+ static _ATL_FUNC_INFO handler_type_bstr_i4_;
+ static _ATL_FUNC_INFO handler_type_bstrarray_;
+ static _ATL_FUNC_INFO handler_type_void_;
+
+ DISALLOW_COPY_AND_ASSIGN(ChromeFrameHost);
+};
+
+#endif // CEEE_IE_COMMON_CHROME_FRAME_HOST_H_
diff --git a/ceee/ie/common/chrome_frame_host_unittest.cc b/ceee/ie/common/chrome_frame_host_unittest.cc
new file mode 100644
index 0000000..f7cf592
--- /dev/null
+++ b/ceee/ie/common/chrome_frame_host_unittest.cc
@@ -0,0 +1,607 @@
+// 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 chrome frame host.
+#include "ceee/ie/common/chrome_frame_host.h"
+
+#include "base/string_util.h"
+#include "ceee/ie/common/mock_ceee_module_util.h"
+#include "ceee/testing/utils/test_utils.h"
+#include "ceee/testing/utils/mock_com.h"
+#include "ceee/testing/utils/mock_static.h"
+#include "ceee/testing/utils/dispex_mocks.h"
+#include "chrome/common/url_constants.h"
+#include "gmock/gmock.h"
+#include "gtest/gtest.h"
+
+namespace {
+
+using testing::FireEvent;
+
+using testing::_;
+using testing::CopyInterfaceToArgument;
+using testing::DoAll;
+using testing::Eq;
+using testing::Field;
+using testing::Invoke;
+using testing::NotNull;
+using testing::Return;
+using testing::SetArgumentPointee;
+using testing::StrEq;
+using testing::StrictMock;
+using testing::MockDispatchEx;
+
+class MockChromeFrameHost : public ChromeFrameHost {
+ public:
+ // Mock the creator functions to control them.
+ MOCK_METHOD1(CreateChromeFrame, HRESULT(IChromeFrame** chrome_frame));
+ // And the event handlers to test event subscription.
+ MOCK_METHOD1_WITH_CALLTYPE(__stdcall, OnCfLoad, void(IDispatch* event));
+ MOCK_METHOD1_WITH_CALLTYPE(__stdcall, OnCfLoadError, void(IDispatch* event));
+ MOCK_METHOD1_WITH_CALLTYPE(__stdcall, OnCfMessage, void(IDispatch* event));
+ MOCK_METHOD1_WITH_CALLTYPE(__stdcall, OnCfReadyStateChanged, void(LONG));
+ MOCK_METHOD2_WITH_CALLTYPE(__stdcall,
+ OnCfPrivateMessage,
+ void(IDispatch* event, BSTR origin));
+ MOCK_METHOD2_WITH_CALLTYPE(__stdcall,
+ OnCfExtensionReady,
+ void(BSTR path, int response));
+ MOCK_METHOD1_WITH_CALLTYPE(__stdcall, OnCfGetEnabledExtensionsComplete,
+ void(SAFEARRAY* extensions));
+ MOCK_METHOD0_WITH_CALLTYPE(__stdcall, OnCfChannelError, void(void));
+};
+
+class TestChromeFrameHost
+ : public StrictMock<MockChromeFrameHost>,
+ public InitializingCoClass<TestChromeFrameHost> {
+ public:
+ using InitializingCoClass<TestChromeFrameHost>::CreateInitialized;
+
+ TestChromeFrameHost() : active_x_host_creation_error_(S_OK) {
+ ++instance_count_;
+ }
+ ~TestChromeFrameHost() {
+ --instance_count_;
+ }
+
+ void set_active_x_host_creation_error(HRESULT hr) {
+ active_x_host_creation_error_ = hr;
+ }
+
+ HRESULT Initialize(TestChromeFrameHost** self) {
+ *self = this;
+ return S_OK;
+ }
+
+
+ HRESULT CreateActiveXHost(IAxWinHostWindow** host) {
+ if (FAILED(active_x_host_creation_error_))
+ return active_x_host_creation_error_;
+
+ return ChromeFrameHost::CreateActiveXHost(host);
+ }
+
+ // For some of the mocks, we want to test the original behavior.
+ void CallOnCfLoad(IDispatch* event) {
+ ChromeFrameHost::OnCfLoad(event);
+ }
+ void CallOnCfReadyStateChanged(LONG state) {
+ ChromeFrameHost::OnCfReadyStateChanged(state);
+ }
+ void CallOnCfPrivateMessage(IDispatch* event, BSTR origin) {
+ ChromeFrameHost::OnCfPrivateMessage(event, origin);
+ }
+ void CallOnCfExtensionReady(BSTR path, int response) {
+ ChromeFrameHost::OnCfExtensionReady(path, response);
+ }
+ void CallOnCfGetEnabledExtensionsComplete(SAFEARRAY* enabled_extensions) {
+ ChromeFrameHost::OnCfGetEnabledExtensionsComplete(enabled_extensions);
+ }
+ void CallOnCfChannelError() {
+ ChromeFrameHost::OnCfChannelError();
+ }
+
+ public:
+ HRESULT active_x_host_creation_error_;
+
+ static size_t instance_count_;
+};
+
+size_t TestChromeFrameHost::instance_count_ = 0;
+
+class IChromeFrameImpl : public IDispatchImpl<IChromeFrame,
+ &IID_IChromeFrame,
+ &LIBID_ChromeTabLib> {
+ public:
+ // @name IChromeFrame implementation
+ // @{
+ MOCK_METHOD1_WITH_CALLTYPE(__stdcall, get_src, HRESULT(BSTR *src));
+ MOCK_METHOD1_WITH_CALLTYPE(__stdcall, put_src, HRESULT(BSTR src));
+ MOCK_METHOD2_WITH_CALLTYPE(__stdcall, postMessage, HRESULT(
+ BSTR message, VARIANT target));
+ MOCK_METHOD1_WITH_CALLTYPE(__stdcall, get_onload, HRESULT(
+ VARIANT *onload_handler));
+ MOCK_METHOD1_WITH_CALLTYPE(__stdcall, put_onload, HRESULT(
+ VARIANT onload_handler));
+ MOCK_METHOD1_WITH_CALLTYPE(__stdcall, get_onloaderror, HRESULT(
+ VARIANT *onerror_handler));
+ MOCK_METHOD1_WITH_CALLTYPE(__stdcall, put_onloaderror, HRESULT(
+ VARIANT onerror_handler));
+ MOCK_METHOD1_WITH_CALLTYPE(__stdcall, get_onmessage, HRESULT(
+ VARIANT *onmessage_handler));
+ MOCK_METHOD1_WITH_CALLTYPE(__stdcall, put_onmessage, HRESULT(
+ VARIANT onmessage_handler));
+ MOCK_METHOD1_WITH_CALLTYPE(__stdcall, get_readyState, HRESULT(
+ LONG *ready_state));
+ MOCK_METHOD3_WITH_CALLTYPE(__stdcall, addEventListener, HRESULT(
+ BSTR event_type, IDispatch *listener, VARIANT use_capture));
+ MOCK_METHOD3_WITH_CALLTYPE(__stdcall, removeEventListener, HRESULT(
+ BSTR event_type, IDispatch *listener, VARIANT use_capture));
+ MOCK_METHOD1_WITH_CALLTYPE(__stdcall, get_version, HRESULT(BSTR *version));
+ MOCK_METHOD3_WITH_CALLTYPE(__stdcall, postPrivateMessage, HRESULT(
+ BSTR message, BSTR origin, BSTR target));
+ MOCK_METHOD1_WITH_CALLTYPE(__stdcall, get_useChromeNetwork, HRESULT(
+ VARIANT_BOOL *pVal));
+ MOCK_METHOD1_WITH_CALLTYPE(__stdcall, put_useChromeNetwork, HRESULT(
+ VARIANT_BOOL newVal));
+ MOCK_METHOD1_WITH_CALLTYPE(__stdcall, installExtension, HRESULT(
+ BSTR crx_path));
+ MOCK_METHOD1_WITH_CALLTYPE(__stdcall, loadExtension, HRESULT(
+ BSTR path));
+ MOCK_METHOD0_WITH_CALLTYPE(__stdcall, getEnabledExtensions, HRESULT());
+ MOCK_METHOD0_WITH_CALLTYPE(__stdcall, registerBhoIfNeeded, HRESULT());
+ MOCK_METHOD1_WITH_CALLTYPE(__stdcall, getSessionId, HRESULT(int*));
+ // @}
+};
+
+class MockChromeFrame
+ : public CComObjectRootEx<CComSingleThreadModel>,
+ public InitializingCoClass< StrictMock<MockChromeFrame> >,
+ public IObjectWithSiteImpl<MockChromeFrame>,
+ public StrictMock<IChromeFrameImpl>,
+ public IConnectionPointContainerImpl<MockChromeFrame>,
+ public IConnectionPointImpl<MockChromeFrame, &DIID_DIChromeFrameEvents> {
+ public:
+ BEGIN_COM_MAP(MockChromeFrame)
+ COM_INTERFACE_ENTRY(IObjectWithSite)
+ COM_INTERFACE_ENTRY(IDispatch)
+ COM_INTERFACE_ENTRY(IChromeFrame)
+ COM_INTERFACE_ENTRY(IConnectionPointContainer)
+ END_COM_MAP()
+
+ BEGIN_CONNECTION_POINT_MAP(MockChromeFrame)
+ CONNECTION_POINT_ENTRY(DIID_DIChromeFrameEvents)
+ END_CONNECTION_POINT_MAP()
+
+ MockChromeFrame() : no_events_(false) {
+ ++instance_count_;
+ }
+ ~MockChromeFrame() {
+ --instance_count_;
+ }
+
+ void set_no_events(bool no_events) {
+ no_events_ = no_events;
+ }
+
+ HRESULT Initialize(MockChromeFrame** self) {
+ *self = this;
+ return S_OK;
+ }
+
+ // Override from IConnectionPointContainerImpl
+ STDMETHOD(FindConnectionPoint)(REFIID iid, IConnectionPoint** cp) {
+ typedef IConnectionPointContainerImpl<MockChromeFrame> CPC;
+
+ if (iid == DIID_DIChromeFrameEvents && no_events_)
+ return CONNECT_E_NOCONNECTION;
+
+ return CPC::FindConnectionPoint(iid, cp);
+ }
+
+ typedef IConnectionPointImpl<MockChromeFrame, &DIID_DIChromeFrameEvents> CP;
+ void FireEvent1(DISPID id, IDispatch* event) {
+ return FireEvent(static_cast<CP*>(this), id, 1, &CComVariant(event));
+ }
+
+ void FireCfLoad(IDispatch* event) {
+ FireEvent1(CF_EVENT_DISPID_ONLOAD, event);
+ }
+
+ void FireCfLoadError(IDispatch* event) {
+ FireEvent1(CF_EVENT_DISPID_ONLOADERROR, event);
+ }
+
+ void FireCfMessage(IDispatch* event) {
+ FireEvent1(CF_EVENT_DISPID_ONMESSAGE, event);
+ }
+
+ void FireCfReadyStateChanged(LONG ready_state) {
+ return FireEvent(static_cast<CP*>(this),
+ CF_EVENT_DISPID_ONREADYSTATECHANGED,
+ 1,
+ &CComVariant(ready_state));
+ }
+
+ void FireCfPrivateMessage(IDispatch* event, BSTR origin) {
+ CComVariant args[] = { origin, event };
+ return FireEvent(static_cast<CP*>(this),
+ CF_EVENT_DISPID_ONPRIVATEMESSAGE,
+ arraysize(args),
+ args);
+ }
+
+ void FireCfExtensionReady(BSTR path, int response) {
+ CComVariant args[] = { response , path};
+ return FireEvent(static_cast<CP*>(this),
+ CF_EVENT_DISPID_ONEXTENSIONREADY,
+ arraysize(args),
+ args);
+ }
+
+ void FireCfGetEnabledExtensionsComplete(SAFEARRAY* array) {
+ VARIANT args[] = { { VT_ARRAY | VT_BSTR } };
+ return FireEvent(static_cast<CP*>(this),
+ CF_EVENT_DISPID_ONGETENABLEDEXTENSIONSCOMPLETE,
+ arraysize(args),
+ args);
+ }
+
+ void FireCfChannelError() {
+ return FireEvent(static_cast<CP*>(this),
+ CF_EVENT_DISPID_ONCHANNELERROR,
+ 0,
+ NULL);
+ }
+
+ public:
+ // Quench our event sink.
+ bool no_events_;
+
+ static size_t instance_count_;
+};
+
+size_t MockChromeFrame::instance_count_ = 0;
+
+class IChromeFrameHostEventsMockImpl : public IChromeFrameHostEvents {
+ public:
+ MOCK_METHOD3(OnCfPrivateMessage, HRESULT(BSTR, BSTR, BSTR));
+ MOCK_METHOD2(OnCfExtensionReady, HRESULT(BSTR, int));
+ MOCK_METHOD1(OnCfGetEnabledExtensionsComplete,
+ HRESULT(SAFEARRAY* extensions));
+ MOCK_METHOD1(OnCfGetExtensionApisToAutomate,
+ HRESULT(BSTR* enabled_functions));
+ MOCK_METHOD1(OnCfReadyStateChanged, HRESULT(LONG));
+ MOCK_METHOD0(OnCfChannelError, HRESULT(void));
+};
+
+class MockChromeFrameHostEvents
+ : public CComObjectRootEx<CComSingleThreadModel>,
+ public InitializingCoClass<MockChromeFrameHostEvents>,
+ public StrictMock<IChromeFrameHostEventsMockImpl> {
+ public:
+ BEGIN_COM_MAP(MockChromeFrameHostEvents)
+ COM_INTERFACE_ENTRY(IUnknown)
+ END_COM_MAP()
+
+ HRESULT Initialize(MockChromeFrameHostEvents** self) {
+ *self = this;
+ return S_OK;
+ }
+};
+
+class ChromeFrameHostTest: public testing::Test {
+ public:
+ virtual void SetUp() {
+ ASSERT_HRESULT_SUCCEEDED(
+ TestChromeFrameHost::CreateInitialized(&host_, &host_keeper_));
+ }
+
+ virtual void TearDown() {
+ if (host_)
+ host_->TearDown();
+ host_ = NULL;
+ host_keeper_.Release();
+
+ chrome_frame_ = NULL;
+ chrome_frame_keeper_ = NULL;
+
+ ASSERT_EQ(0, TestChromeFrameHost::instance_count_);
+ ASSERT_EQ(0, MockChromeFrame::instance_count_);
+ }
+
+ void ExpectCreateChromeFrame(HRESULT hr) {
+ if (SUCCEEDED(hr)) {
+ ASSERT_HRESULT_SUCCEEDED(
+ MockChromeFrame::CreateInitialized(&chrome_frame_,
+ &chrome_frame_keeper_));
+ EXPECT_CALL(*host_, CreateChromeFrame(_)).
+ WillRepeatedly(
+ DoAll(
+ CopyInterfaceToArgument<0>(chrome_frame_keeper_),
+ Return(S_OK)));
+ } else {
+ EXPECT_CALL(*host_, CreateChromeFrame(_)).
+ WillRepeatedly(Return(hr));
+ }
+ }
+
+ public:
+ TestChromeFrameHost* host_;
+ CComPtr<IUnknown> host_keeper_;
+
+ MockChromeFrame* chrome_frame_;
+ CComPtr<IChromeFrame> chrome_frame_keeper_;
+
+ // Quench logging for all tests.
+ testing::LogDisabler no_dchecks_;
+};
+
+TEST_F(ChromeFrameHostTest, StartChromeFrameSuccess) {
+ ExpectCreateChromeFrame(S_OK);
+
+ ASSERT_HRESULT_SUCCEEDED(host_->StartChromeFrame());
+}
+
+TEST_F(ChromeFrameHostTest, StartChromeFrameFailsOnActiveXHostCreationFailure) {
+ ExpectCreateChromeFrame(S_OK);
+
+ host_->set_active_x_host_creation_error(E_OUTOFMEMORY);
+ ASSERT_EQ(E_OUTOFMEMORY, host_->StartChromeFrame());
+}
+
+TEST_F(ChromeFrameHostTest, StartChromeFrameFailsOnAdviseFailure) {
+ ExpectCreateChromeFrame(S_OK);
+
+ chrome_frame_->set_no_events(true);
+ ASSERT_HRESULT_FAILED(host_->StartChromeFrame());
+}
+
+TEST_F(ChromeFrameHostTest, StartChromeFrameFailsOnCreationFailure) {
+ ExpectCreateChromeFrame(E_OUTOFMEMORY);
+
+ ASSERT_HRESULT_FAILED(host_->StartChromeFrame());
+}
+
+TEST_F(ChromeFrameHostTest, ChromeFramePrivilegedInServiceProviderChain) {
+ ExpectCreateChromeFrame(S_OK);
+
+ ASSERT_HRESULT_SUCCEEDED(host_->StartChromeFrame());
+
+ // Get the service provider on our mock Chrome frame.
+ CComPtr<IServiceProvider> sp;
+ ASSERT_HRESULT_SUCCEEDED(
+ chrome_frame_->GetSite(IID_IServiceProvider,
+ reinterpret_cast<void**>(&sp)));
+
+ CComPtr<IChromeFramePrivileged> priv;
+ ASSERT_HRESULT_SUCCEEDED(sp->QueryService(SID_ChromeFramePrivileged,
+ IID_IChromeFramePrivileged,
+ reinterpret_cast<void**>(&priv)));
+ ASSERT_TRUE(priv != NULL);
+
+ boolean wants_priv = FALSE;
+ ASSERT_HRESULT_SUCCEEDED(priv->GetWantsPrivileged(&wants_priv));
+ ASSERT_TRUE(wants_priv);
+
+ CComBSTR profile_name;
+ ASSERT_HRESULT_SUCCEEDED(priv->GetChromeProfileName(&profile_name));
+ ASSERT_TRUE(profile_name == NULL);
+
+ static const wchar_t* kProfileName = L"iexplore";
+ host_->SetChromeProfileName(kProfileName);
+ ASSERT_HRESULT_SUCCEEDED(priv->GetChromeProfileName(&profile_name));
+ ASSERT_STREQ(kProfileName, profile_name);
+}
+
+TEST_F(ChromeFrameHostTest, PostMessage) {
+ ExpectCreateChromeFrame(S_OK);
+ ASSERT_HRESULT_SUCCEEDED(host_->StartChromeFrame());
+
+ // Make sure we properly queue before the document gets loaded.
+ CComBSTR queue_it_1("queue_it_1");
+ CComBSTR queue_it_2("queue_it_2");
+ CComBSTR target_1("target_1");
+ CComBSTR target_2("target_2");
+
+ EXPECT_CALL(*chrome_frame_, postPrivateMessage(_, _, _)).Times(0);
+ EXPECT_HRESULT_SUCCEEDED(host_->PostMessage(queue_it_1, target_1));
+ EXPECT_HRESULT_SUCCEEDED(host_->PostMessage(queue_it_2, target_2));
+
+ // Only the queued messages should be posted.
+ EXPECT_CALL(*chrome_frame_, postPrivateMessage(StrEq(queue_it_1.m_str),
+ _, StrEq(target_1.m_str))).WillOnce(Return(S_OK));
+ EXPECT_CALL(*chrome_frame_, postPrivateMessage(StrEq(queue_it_2.m_str),
+ _, StrEq(target_2.m_str))).WillOnce(Return(S_OK));
+
+ MockDispatchEx* event;
+ CComDispatchDriver event_keeper;
+ ASSERT_HRESULT_SUCCEEDED(
+ InitializingCoClass<MockDispatchEx>::CreateInitialized(&event,
+ &event_keeper));
+ host_->CallOnCfLoad(event);
+
+ // Nothing left to be posted once we have loaded.
+ EXPECT_CALL(*chrome_frame_, postPrivateMessage(_, _, _)).Times(0);
+ host_->CallOnCfLoad(event);
+
+ // Messages should go directly to Chrome Frame now, whether they are
+ // queueable or not.
+ EXPECT_CALL(*chrome_frame_, postPrivateMessage(StrEq(queue_it_1.m_str),
+ _, StrEq(target_1.m_str))).WillOnce(Return(S_OK));
+ EXPECT_CALL(*chrome_frame_, postPrivateMessage(StrEq(queue_it_2.m_str),
+ _, StrEq(target_2.m_str))).WillOnce(Return(S_OK));
+ EXPECT_HRESULT_SUCCEEDED(host_->PostMessage(queue_it_1, target_1));
+ EXPECT_HRESULT_SUCCEEDED(host_->PostMessage(queue_it_2, target_2));
+
+ // Nothing left to be posted once we have loaded.
+ EXPECT_CALL(*chrome_frame_, postPrivateMessage(_, _, _)).Times(0);
+ host_->CallOnCfLoad(event);
+}
+
+TEST_F(ChromeFrameHostTest, OnCfReadyStateChanged) {
+ ExpectCreateChromeFrame(S_OK);
+ ASSERT_HRESULT_SUCCEEDED(host_->StartChromeFrame());
+
+ // Should call event sink on all states, but only once
+ // event sink is set.
+ host_->CallOnCfReadyStateChanged(READYSTATE_UNINITIALIZED);
+ host_->CallOnCfReadyStateChanged(READYSTATE_LOADING);
+ host_->CallOnCfReadyStateChanged(READYSTATE_LOADED);
+ host_->CallOnCfReadyStateChanged(READYSTATE_INTERACTIVE);
+ host_->CallOnCfReadyStateChanged(READYSTATE_COMPLETE);
+
+ MockChromeFrameHostEvents* event_sink;
+ CComPtr<IUnknown> event_sink_sp;
+ ASSERT_HRESULT_SUCCEEDED(
+ InitializingCoClass<MockChromeFrameHostEvents>::CreateInitialized(
+ &event_sink, &event_sink_sp));
+
+ host_->SetEventSink(event_sink);
+ EXPECT_CALL(*event_sink, OnCfReadyStateChanged(_)).Times(5);
+ host_->CallOnCfReadyStateChanged(READYSTATE_UNINITIALIZED);
+ host_->CallOnCfReadyStateChanged(READYSTATE_LOADING);
+ host_->CallOnCfReadyStateChanged(READYSTATE_LOADED);
+ host_->CallOnCfReadyStateChanged(READYSTATE_INTERACTIVE);
+ host_->CallOnCfReadyStateChanged(READYSTATE_COMPLETE);
+}
+
+TEST_F(ChromeFrameHostTest, ChromeFrameEventsCaptured) {
+ ExpectCreateChromeFrame(S_OK);
+ ASSERT_HRESULT_SUCCEEDED(host_->StartChromeFrame());
+
+ // Create a handy-dandy dispatch mock object.
+ MockDispatchEx* event;
+ CComDispatchDriver event_keeper;
+ ASSERT_HRESULT_SUCCEEDED(
+ InitializingCoClass<MockDispatchEx>::CreateInitialized(&event,
+ &event_keeper));
+
+ EXPECT_CALL(*host_, OnCfLoad(event)).Times(1);
+ chrome_frame_->FireCfLoad(event);
+
+ EXPECT_CALL(*host_, OnCfLoadError(event)).Times(1);
+ chrome_frame_->FireCfLoadError(event);
+
+ EXPECT_CALL(*host_, OnCfMessage(event)).Times(1);
+ chrome_frame_->FireCfMessage(event);
+
+ EXPECT_CALL(*host_, OnCfReadyStateChanged(READYSTATE_LOADING)).Times(1);
+ chrome_frame_->FireCfReadyStateChanged(READYSTATE_LOADING);
+
+ EXPECT_CALL(*host_, OnCfReadyStateChanged(READYSTATE_COMPLETE)).Times(1);
+ chrome_frame_->FireCfReadyStateChanged(READYSTATE_COMPLETE);
+
+ static const wchar_t* kOrigin = L"From Russia with Love";
+ EXPECT_CALL(*host_, OnCfPrivateMessage(event, StrEq(kOrigin))).Times(1);
+ chrome_frame_->FireCfPrivateMessage(event, CComBSTR(kOrigin));
+
+ static const wchar_t* kPath = L"they all lead to Rome";
+ EXPECT_CALL(*host_, OnCfExtensionReady(StrEq(kPath), 42)).Times(1);
+ chrome_frame_->FireCfExtensionReady(CComBSTR(kPath), 42);
+
+ EXPECT_CALL(*host_, OnCfGetEnabledExtensionsComplete(_)).Times(1);
+ chrome_frame_->FireCfGetEnabledExtensionsComplete(NULL);
+
+ EXPECT_CALL(*host_, OnCfChannelError()).Times(1);
+ chrome_frame_->FireCfChannelError();
+}
+
+// Used to compare two arrays of strings, as when GetIDsOfNames is called
+MATCHER_P(SingleEntryLPOLESTRArraysEqual, single_entry, "") {
+ return std::wstring(single_entry) == arg[0];
+}
+
+TEST_F(ChromeFrameHostTest, EventSync) {
+ // Make sure all calls are safe without an event sink.
+ BSTR functions_enabled = NULL;
+ CComQIPtr<IChromeFramePrivileged> host_privileged(host_);
+ EXPECT_HRESULT_SUCCEEDED(host_privileged->GetExtensionApisToAutomate(
+ &functions_enabled));
+ EXPECT_EQ(NULL, functions_enabled);
+
+ host_->CallOnCfExtensionReady(L"", 0);
+ host_->CallOnCfGetEnabledExtensionsComplete(NULL);
+ host_->CallOnCfPrivateMessage(NULL, NULL);
+ host_->CallOnCfPrivateMessage(NULL, NULL);
+ host_->CallOnCfChannelError();
+
+ // Now make sure the calls are properly propagated to the event sync.
+ MockChromeFrameHostEvents* event_sink;
+ CComPtr<IUnknown> event_sink_keeper;
+ ASSERT_HRESULT_SUCCEEDED(
+ InitializingCoClass<MockChromeFrameHostEvents>::CreateInitialized(
+ &event_sink, &event_sink_keeper));
+ // We cheat a bit here because there is no IID to QI for
+ // IChromeFrameHostEvents.
+ host_->SetEventSink(event_sink);
+
+ static const BSTR kCheatCode = L"Cheat code";
+ EXPECT_CALL(*event_sink, OnCfGetExtensionApisToAutomate(NotNull())).WillOnce(
+ DoAll(SetArgumentPointee<0>(kCheatCode), Return(S_OK)));
+ EXPECT_HRESULT_SUCCEEDED(host_privileged->GetExtensionApisToAutomate(
+ &functions_enabled));
+ DCHECK_EQ(kCheatCode, functions_enabled);
+
+ // Other calls should be OK, as long as the string isn't set to a non-NULL
+ // value.
+ EXPECT_CALL(*event_sink, OnCfGetExtensionApisToAutomate(NotNull())).WillOnce(
+ DoAll(SetArgumentPointee<0>(static_cast<LPOLESTR>(0)), Return(S_FALSE)));
+ EXPECT_HRESULT_SUCCEEDED(host_privileged->GetExtensionApisToAutomate(
+ &functions_enabled));
+
+ EXPECT_CALL(*event_sink, OnCfExtensionReady(StrEq(L""), 0)).Times(1);
+ host_->CallOnCfExtensionReady(L"", 0);
+
+ EXPECT_CALL(*event_sink, OnCfGetEnabledExtensionsComplete(NULL)).Times(1);
+ host_->CallOnCfGetEnabledExtensionsComplete(NULL);
+
+ EXPECT_CALL(*event_sink, OnCfChannelError()).Times(1);
+ host_->CallOnCfChannelError();
+
+ MockDispatchEx* mock_dispatch = NULL;
+ CComDispatchDriver dispatch_keeper;
+ ASSERT_HRESULT_SUCCEEDED(
+ InitializingCoClass<MockDispatchEx>::CreateInitialized(&mock_dispatch,
+ &dispatch_keeper));
+ static LPCOLESTR kOrigin = L"Origin";
+ VARIANT origin;
+ origin.vt = VT_BSTR;
+ // This will be freed by the calling API. If we free it here, we're going to
+ // end up with a double-free.
+ origin.bstrVal = SysAllocString(kOrigin);
+ DISPID origin_dispid = 42;
+ EXPECT_CALL(*mock_dispatch, GetIDsOfNames(_,
+ SingleEntryLPOLESTRArraysEqual(L"origin"), 1, _, NotNull())).
+ WillOnce(DoAll(SetArgumentPointee<4>(origin_dispid), Return(S_OK)));
+ EXPECT_CALL(*mock_dispatch, Invoke(origin_dispid, _, _, DISPATCH_PROPERTYGET,
+ Field(&DISPPARAMS::cArgs, Eq(0)), _, _, _)).WillOnce(DoAll(
+ SetArgumentPointee<5>(origin), Return(S_OK)));
+
+ static LPCOLESTR kData = L"Data";
+ VARIANT data;
+ data.vt = VT_BSTR;
+ // This will be freed by the calling API. If we free it here, we're going to
+ // end up with a double-free.
+ data.bstrVal = SysAllocString(kData);
+ DISPID data_dispid = 24;
+ EXPECT_CALL(*mock_dispatch, GetIDsOfNames(_,
+ SingleEntryLPOLESTRArraysEqual(L"data"), 1, _, NotNull())).
+ WillOnce(DoAll(SetArgumentPointee<4>(data_dispid), Return(S_OK)));
+ EXPECT_CALL(*mock_dispatch, Invoke(data_dispid, _, _, DISPATCH_PROPERTYGET,
+ Field(&DISPPARAMS::cArgs, Eq(0)), _, _, _)).WillOnce(DoAll(
+ SetArgumentPointee<5>(data), Return(S_OK)));
+
+ static LPOLESTR kTarget = L"Target";
+ EXPECT_CALL(*event_sink, OnCfPrivateMessage(
+ StrEq(kData), StrEq(kOrigin), StrEq(kTarget))).Times(1);
+
+ host_->CallOnCfPrivateMessage(dispatch_keeper, kTarget);
+
+ // Clean the VARIANTs.
+ ZeroMemory(&origin, sizeof(origin));
+ ZeroMemory(&data, sizeof(data));
+}
+
+} // namespace
diff --git a/ceee/ie/common/common.gyp b/ceee/ie/common/common.gyp
new file mode 100644
index 0000000..759c901
--- /dev/null
+++ b/ceee/ie/common/common.gyp
@@ -0,0 +1,124 @@
+# 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': 'ie_common_settings',
+ 'type': 'none',
+ 'direct_dependent_settings': {
+ 'defines': [
+ # TODO(joi@chromium.org) Put into an include somewhere.
+ '_WIN32_WINDOWS=0x0410',
+ '_WIN32_IE=0x0600',
+ '_ATL_CSTRING_EXPLICIT_CONSTRUCTORS',
+ '_ATL_STATIC_REGISTRY',
+ '_WTL_NO_CSTRING',
+ ],
+ 'include_dirs': [
+ '../../../third_party/wtl/include',
+ ],
+ },
+ },
+ {
+ 'target_name': 'ie_guids',
+ 'type': 'static_library',
+ 'dependencies': [
+ 'ie_common_settings',
+ '../plugin/toolband/toolband.gyp:toolband_idl',
+ '../plugin/toolband/toolband.gyp:chrome_tab_idl',
+ ],
+ 'sources': [
+ 'ie_guids.cc',
+ ],
+ 'include_dirs': [
+ '../../..',
+ ],
+ },
+ {
+ 'target_name': 'ie_common',
+ 'type': 'static_library',
+ 'dependencies': [
+ 'ie_common_settings',
+ '../../../base/base.gyp:base',
+ '../../../breakpad/breakpad.gyp:breakpad_handler',
+ '../../../build/temp_gyp/googleurl.gyp:googleurl',
+ '../../../net/net.gyp:net_base',
+ '../../../ceee/common/common.gyp:initializing_coclass',
+ '../../../ceee/common/common.gyp:ceee_common',
+ '../../../ceee/testing/utils/test_utils.gyp:test_utils',
+ '../plugin/toolband/toolband.gyp:chrome_tab_idl',
+ '../plugin/toolband/toolband.gyp:toolband_idl',
+ ],
+ 'sources': [
+ 'api_registration.h',
+ 'chrome_frame_host.cc',
+ 'chrome_frame_host.h',
+ 'constants.cc',
+ 'constants.h',
+ 'crash_reporter.cc',
+ 'crash_reporter.h',
+ 'extension_manifest.cc',
+ 'extension_manifest.h',
+ 'ie_tab_interfaces.cc',
+ 'ie_tab_interfaces.h',
+ 'ie_util.cc',
+ 'ie_util.h',
+ 'mock_ie_tab_interfaces.h',
+ 'precompile.cc',
+ 'precompile.h',
+ 'ceee_module_util.cc',
+ 'ceee_module_util.h',
+
+ # TODO(joi@chromium.org) Refactor to use chrome/common library.
+ '../../../chrome/browser/automation/extension_automation_constants.cc',
+ '../../../chrome/browser/extensions/'
+ 'extension_bookmarks_module_constants.cc',
+ '../../../chrome/browser/extensions/extension_event_names.cc',
+ '../../../chrome/browser/extensions/'
+ 'extension_page_actions_module_constants.cc',
+ '../../../chrome/browser/extensions/extension_cookies_api_constants.cc',
+ '../../../chrome/browser/extensions/'
+ 'extension_infobar_module_constants.cc',
+ '../../../chrome/browser/extensions/extension_tabs_module_constants.cc',
+ '../../../chrome/browser/extensions/'
+ 'extension_webnavigation_api_constants.cc',
+ '../../../chrome/browser/extensions/'
+ 'extension_webrequest_api_constants.cc',
+ '../../../chrome/common/chrome_switches.cc',
+ '../../../chrome/common/chrome_switches.h',
+ '../../../chrome/common/url_constants.cc',
+ '../../../chrome/common/url_constants.h',
+ '../../../chrome/common/extensions/extension_constants.cc',
+ '../../../chrome/common/extensions/extension_constants.h',
+ '../../../chrome/common/extensions/extension_error_utils.cc',
+ '../../../chrome/common/extensions/extension_error_utils.h',
+ '../../../chrome/common/extensions/url_pattern.cc',
+ '../../../chrome/common/extensions/url_pattern.h',
+ '../../../chrome/common/extensions/user_script.cc',
+ '../../../chrome/common/extensions/user_script.h',
+ ],
+ 'direct_dependent_settings': {
+ 'include_dirs': [
+ # Because we use some of the chrome files above directly, we need
+ # to specify thess include paths which they depend on.
+ '../../../skia/config/win',
+ '../../../third_party/skia/include/config',
+ ],
+ },
+ 'configurations': {
+ 'Debug': {
+ 'msvs_precompiled_source': 'precompile.cc',
+ 'msvs_precompiled_header': 'precompile.h',
+ },
+ },
+ },
+ ]
+}
diff --git a/ceee/ie/common/constants.cc b/ceee/ie/common/constants.cc
new file mode 100644
index 0000000..64a1d52
--- /dev/null
+++ b/ceee/ie/common/constants.cc
@@ -0,0 +1,11 @@
+// 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.
+//
+// Shared constants between the different modules of CEEE.
+
+#include "ceee/ie/common/constants.h"
+
+namespace ceee_event_names {
+ const char kCeeeOnTabUnmapped[] = "ceee.OnTabUnmapped";
+}
diff --git a/ceee/ie/common/constants.h b/ceee/ie/common/constants.h
new file mode 100644
index 0000000..600a589
--- /dev/null
+++ b/ceee/ie/common/constants.h
@@ -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.
+//
+#ifndef CEEE_IE_COMMON_CONSTANTS_H_
+#define CEEE_IE_COMMON_CONSTANTS_H_
+
+namespace ceee_event_names {
+ // Private messages used by the ApiDispatcher and the funnels.
+ extern const char kCeeeOnTabUnmapped[];
+}
+
+#endif // CEEE_IE_COMMON_CONSTANTS_H_
diff --git a/ceee/ie/common/crash_reporter.cc b/ceee/ie/common/crash_reporter.cc
new file mode 100644
index 0000000..4ed9b5c
--- /dev/null
+++ b/ceee/ie/common/crash_reporter.cc
@@ -0,0 +1,75 @@
+// 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.
+//
+// Definition of IE crash reporter.
+
+#include "ceee/ie/common/crash_reporter.h"
+
+#include "base/logging.h"
+#include "ceee/ie/common/ceee_module_util.h"
+
+const wchar_t kGoogleUpdatePipeName[] =
+ L"\\\\.\\pipe\\GoogleCrashServices\\S-1-5-18";
+
+CrashReporter::CrashReporter(const wchar_t* component_name)
+ : exception_handler_(NULL) {
+ // Initialize the custom data that will be used to identify the client
+ // when reporting a crash.
+ // TODO(jeffbailey@google.com): Inherit Chrome's version number.
+ // (bb3143594).
+ google_breakpad::CustomInfoEntry ver_entry(L"ver", L"Ver.Goes.Here");
+ google_breakpad::CustomInfoEntry prod_entry(L"prod", L"CEEE_IE");
+ google_breakpad::CustomInfoEntry plat_entry(L"plat", L"Win32");
+ google_breakpad::CustomInfoEntry type_entry(L"ptype", component_name);
+ google_breakpad::CustomInfoEntry entries[] = {
+ ver_entry, prod_entry, plat_entry, type_entry };
+
+ const int num_entries = arraysize(entries);
+ client_info_entries_.reset(
+ new google_breakpad::CustomInfoEntry[num_entries]);
+
+ for (int i = 0; i < num_entries; ++i) {
+ client_info_entries_[i] = entries[i];
+ }
+
+ client_info_.entries = client_info_entries_.get();
+ client_info_.count = num_entries;
+}
+
+CrashReporter::~CrashReporter() {
+ DCHECK(exception_handler_ == NULL);
+}
+
+void CrashReporter::InitializeCrashReporting(bool full_dump) {
+ DCHECK(exception_handler_ == NULL);
+
+ if (!ceee_module_util::GetCollectStatsConsent())
+ return;
+
+ wchar_t temp_path[MAX_PATH];
+ DWORD len = ::GetTempPath(arraysize(temp_path), temp_path);
+ if (len == 0) {
+ LOG(ERROR) << "Failed to instantiate Breakpad exception handler. " <<
+ "Could not get a temp path.";
+ return;
+ }
+
+ // Install an exception handler instance here, which should be the lowest
+ // level in the process. We give it the appropriate pipe name so it can
+ // talk to the reporting service (e.g. Omaha) to do dump generation and
+ // send information back to the server.
+ MINIDUMP_TYPE dump_type = full_dump ? MiniDumpWithFullMemory : MiniDumpNormal;
+ exception_handler_ = new google_breakpad::ExceptionHandler(
+ temp_path, NULL, NULL, NULL,
+ google_breakpad::ExceptionHandler::HANDLER_ALL, dump_type,
+ kGoogleUpdatePipeName, &client_info_);
+
+ LOG_IF(ERROR, exception_handler_ == NULL) <<
+ "Failed to instantiate Breakpad exception handler.";
+}
+
+void CrashReporter::ShutdownCrashReporting() {
+ delete exception_handler_;
+ exception_handler_ = NULL;
+}
diff --git a/ceee/ie/common/crash_reporter.h b/ceee/ie/common/crash_reporter.h
new file mode 100644
index 0000000..1f34370
--- /dev/null
+++ b/ceee/ie/common/crash_reporter.h
@@ -0,0 +1,41 @@
+// 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 common IE crash reporter.
+
+#ifndef CEEE_IE_COMMON_CRASH_REPORTER_H_
+#define CEEE_IE_COMMON_CRASH_REPORTER_H_
+
+#include "base/scoped_ptr.h"
+#include "client/windows/handler/exception_handler.h"
+
+// A wrapper around Breakpad's ExceptionHandler class for crash reporting using
+// Omaha's crash reporting service.
+class CrashReporter {
+ public:
+ explicit CrashReporter(const wchar_t* component_name);
+ virtual ~CrashReporter();
+
+ // Initialize the ExceptionHandler to begin catching and reporting unhandled
+ // exceptions.
+ //
+ // @param full_dump Whether or not to do a full memory dump.
+ void InitializeCrashReporting(bool full_dump);
+
+ // Halt crash reporting, stopping the ExceptionHandler from catching any
+ // further unhandled exceptions.
+ void ShutdownCrashReporting();
+
+ // TODO(stevet@google.com): Provide a way to trigger a dump send,
+ // which can be used for debugging.
+ private:
+ google_breakpad::CustomClientInfo client_info_;
+ scoped_array<google_breakpad::CustomInfoEntry> client_info_entries_;
+
+ // Valid after a call to InitializeCrashReporting and invalidated after a call
+ // to ShutdownCrashReporting.
+ google_breakpad::ExceptionHandler* exception_handler_;
+};
+
+#endif // CEEE_IE_COMMON_CRASH_REPORTER_H_
diff --git a/ceee/ie/common/crash_reporter_unittest.cc b/ceee/ie/common/crash_reporter_unittest.cc
new file mode 100644
index 0000000..541f581
--- /dev/null
+++ b/ceee/ie/common/crash_reporter_unittest.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.
+//
+// Unit tests for the Crash Reporter.
+
+#include "ceee/ie/common/crash_reporter.h"
+
+#include "base/logging.h"
+#include "base/win/pe_image.h"
+#include "ceee/ie/common/ceee_module_util.h"
+
+#include "ceee/testing/utils/mock_window_utils.h"
+#include "ceee/testing/utils/test_utils.h"
+#include "testing/gmock/include/gmock/gmock.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+namespace {
+
+using testing::_;
+using testing::Return;
+using testing::StrictMock;
+
+// A helper for just extracting the default exception filter.
+static LPTOP_LEVEL_EXCEPTION_FILTER GetUnhandledExceptionFilter() {
+ LPTOP_LEVEL_EXCEPTION_FILTER old_exh = SetUnhandledExceptionFilter(NULL);
+ EXPECT_EQ(NULL, SetUnhandledExceptionFilter(old_exh));
+ return old_exh;
+}
+
+// A helper for just extracting the default invalid parameter handler.
+static _invalid_parameter_handler GetInvalidParameterHandler() {
+ _invalid_parameter_handler old_iph = _set_invalid_parameter_handler(NULL);
+ EXPECT_EQ(NULL, _set_invalid_parameter_handler(old_iph));
+ return old_iph;
+}
+
+// A helper for just extracting the default purecall handler.
+static _purecall_handler GetPureCallHandler() {
+ _purecall_handler old_pch = _set_purecall_handler(NULL);
+ EXPECT_EQ(NULL, _set_purecall_handler(old_pch));
+ return old_pch;
+}
+
+MOCK_STATIC_CLASS_BEGIN(MockCeeeModuleUtil)
+ MOCK_STATIC_INIT_BEGIN(MockCeeeModuleUtil)
+ MOCK_STATIC_INIT2(ceee_module_util::GetCollectStatsConsent,
+ GetCollectStatsConsent);
+ MOCK_STATIC_INIT_END()
+ MOCK_STATIC0(bool, , GetCollectStatsConsent);
+MOCK_STATIC_CLASS_END(MockCeeeModuleUtil)
+
+// Test that basic exception handling from Breakpad works, by ensuring that
+// calling CrashReporter functions correctly replaces default handlers.
+TEST(CrashReporterTest, ExceptionHandling) {
+ base::win::PEImage my_image(reinterpret_cast<HMODULE>(&__ImageBase));
+
+ // Take the default handlers out and do a quick sanity check. It is needed
+ // later to ensure it gets replaced by breakpad.
+ LPTOP_LEVEL_EXCEPTION_FILTER orig_exh = GetUnhandledExceptionFilter();
+ EXPECT_TRUE(orig_exh != NULL);
+
+ _invalid_parameter_handler orig_iph = GetInvalidParameterHandler();
+ EXPECT_EQ(NULL, orig_iph);
+
+ _purecall_handler orig_pch = GetPureCallHandler();
+ EXPECT_EQ(NULL, orig_pch);
+
+ // Initialize and ensure that a new handler has replaced the original
+ // handler, and that the new handler is within this image/from breakpad.
+ CrashReporter crash_reporter(L"unittest");
+
+ StrictMock<MockCeeeModuleUtil> mock;
+ EXPECT_CALL(mock, GetCollectStatsConsent())
+ .Times(1)
+ .WillOnce(Return(false));
+
+ crash_reporter.InitializeCrashReporting(false);
+ EXPECT_EQ(orig_exh, GetUnhandledExceptionFilter());
+ crash_reporter.ShutdownCrashReporting();
+
+ EXPECT_CALL(mock, GetCollectStatsConsent())
+ .Times(1)
+ .WillOnce(Return(true));
+ crash_reporter.InitializeCrashReporting(false);
+
+ LPTOP_LEVEL_EXCEPTION_FILTER new_exh = GetUnhandledExceptionFilter();
+ EXPECT_TRUE(my_image.GetImageSectionFromAddr((PVOID)new_exh) != NULL);
+ EXPECT_TRUE(orig_exh != new_exh);
+
+ _invalid_parameter_handler new_iph = GetInvalidParameterHandler();
+ EXPECT_TRUE(my_image.GetImageSectionFromAddr((PVOID)new_iph) != NULL);
+ EXPECT_TRUE(orig_iph != new_iph);
+
+ _purecall_handler new_pch = GetPureCallHandler();
+ EXPECT_TRUE(my_image.GetImageSectionFromAddr((PVOID)new_pch) != NULL);
+ EXPECT_TRUE(orig_pch != new_pch);
+
+ // Shut down, and ensure that the original exception handler is replaced
+ // by breakpad.
+ crash_reporter.ShutdownCrashReporting();
+
+ LPTOP_LEVEL_EXCEPTION_FILTER final_exh = GetUnhandledExceptionFilter();
+ EXPECT_EQ(orig_exh, final_exh);
+
+ _invalid_parameter_handler final_iph = GetInvalidParameterHandler();
+ EXPECT_EQ(orig_iph, final_iph);
+
+ _purecall_handler final_pch = GetPureCallHandler();
+ EXPECT_EQ(orig_pch, final_pch);
+}
+
+} // namespace
diff --git a/ceee/ie/common/extension_manifest.cc b/ceee/ie/common/extension_manifest.cc
new file mode 100644
index 0000000..22d5f4a
--- /dev/null
+++ b/ceee/ie/common/extension_manifest.cc
@@ -0,0 +1,306 @@
+// 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 access Chrome Extension manifest data.
+
+#include "ceee/ie/common/extension_manifest.h"
+
+#include "base/base64.h"
+#include "base/file_path.h"
+#include "base/file_util.h"
+#include "base/json/json_reader.h"
+#include "base/logging.h"
+#include "base/string_number_conversions.h"
+#include "base/string_util.h"
+#include "base/third_party/nss/blapi.h"
+#include "base/third_party/nss/sha256.h"
+#include "base/utf_string_conversions.h"
+#include "base/values.h"
+#include "chrome/common/extensions/extension_constants.h"
+#include "chrome/common/url_constants.h"
+#include "net/base/net_util.h"
+
+namespace ext_keys = extension_manifest_keys;
+namespace ext_values = extension_manifest_values;
+
+const char ExtensionManifest::kManifestFilename[] = "manifest.json";
+
+// First 16 bytes of SHA256 hashed public key.
+const size_t ExtensionManifest::kIdSize = 16;
+
+HRESULT ExtensionManifest::ReadManifestFile(const FilePath& extension_path,
+ bool require_key) {
+ // TODO(mad@chromium.org): Unbranch (taken from constructor of
+ // Extension class).
+ DCHECK(extension_path.IsAbsolute());
+#if defined(OS_WIN)
+ // Normalize any drive letter to upper-case. We do this for consistency with
+ // net_utils::FilePathToFileURL(), which does the same thing, to make string
+ // comparisons simpler.
+ std::wstring path_str = extension_path.value();
+ if (path_str.size() >= 2 && path_str[0] >= L'a' && path_str[0] <= L'z' &&
+ path_str[1] == ':')
+ path_str[0] += ('A' - 'a');
+
+ path_ = FilePath(path_str);
+#else
+ path_ = path;
+#endif
+
+ // This piece comes from ExtensionsServiceBackend::LoadExtension()
+ FilePath manifest_path =
+ extension_path.AppendASCII(ExtensionManifest::kManifestFilename);
+ std::string json_string;
+ if (!file_util::ReadFileToString(manifest_path, &json_string)) {
+ LOG(ERROR) << "Invalid extension path or manifest file: " <<
+ extension_path.value();
+ return E_FAIL;
+ }
+
+ scoped_ptr<Value> value(base::JSONReader::Read(json_string, true));
+ if (!value.get() || !value->IsType(Value::TYPE_DICTIONARY)) {
+ LOG(ERROR) << "Invalid manifest file";
+ return E_FAIL;
+ }
+
+ DictionaryValue* dict = static_cast<DictionaryValue*>(value.get());
+
+ // And the rest is highly inspired from Extension::InitFromValue()
+
+ // Initialize public key and id.
+ if (dict->HasKey(ext_keys::kPublicKey)) {
+ if (!dict->GetString(ext_keys::kPublicKey, &public_key_) ||
+ FAILED(CalculateIdFromPublicKey())) {
+ public_key_.clear();
+ extension_id_.clear();
+ LOG(ERROR) << "Invalid public key format";
+ return E_FAIL;
+ }
+ } else if (require_key) {
+ LOG(ERROR) << "Required key not found in manifest file";
+ return E_FAIL;
+ }
+
+ // Initialize the URL.
+ extension_url_ = GURL(std::string(chrome::kExtensionScheme) +
+ chrome::kStandardSchemeSeparator + extension_id_ + "/");
+
+ // Initialize content scripts (optional).
+ // MUST BE DONE AFTER setting up extension_id_, extension_url_ and path_
+ if (dict->HasKey(ext_keys::kContentScripts)) {
+ ListValue* list_value;
+ if (!dict->GetList(ext_keys::kContentScripts, &list_value)) {
+ LOG(ERROR) << "Invalid content script JSON value";
+ return E_FAIL;
+ }
+
+ for (size_t i = 0; i < list_value->GetSize(); ++i) {
+ DictionaryValue* content_script;
+ if (!list_value->GetDictionary(i, &content_script)) {
+ LOG(ERROR) << "Invalid content script JSON value";
+ return E_FAIL;
+ }
+
+ UserScript script;
+ HRESULT hr = LoadUserScriptHelper(content_script, &script);
+ if (FAILED(hr))
+ return hr;
+ script.set_extension_id(extension_id_);
+ content_scripts_.push_back(script);
+ }
+ }
+
+ // Initialize toolstrips (optional).
+ toolstrip_file_names_.clear();
+ if (dict->HasKey(ext_keys::kToolstrips)) {
+ ListValue* list_value;
+ if (!dict->GetList(ext_keys::kToolstrips, &list_value)) {
+ LOG(ERROR) << "Invalid toolstrip JSON value";
+ return E_FAIL;
+ }
+ for (size_t i = 0; i < list_value->GetSize(); ++i) {
+ std::string toolstrip_file_name;
+ if (!list_value->GetString(i, &toolstrip_file_name)) {
+ LOG(ERROR) << "Invalid toolstrip JSON value";
+ return E_FAIL;
+ }
+ toolstrip_file_names_.push_back(toolstrip_file_name);
+ }
+ }
+ // Add code here to read other manifest properties.
+ return S_OK;
+}
+
+// TODO(mad@chromium.org): Unbranch (taken from Extension class).
+
+// static
+GURL ExtensionManifest::GetResourceUrl(const GURL& extension_url,
+ const std::string& relative_path) {
+ DCHECK(extension_url.SchemeIs(chrome::kExtensionScheme));
+ DCHECK_EQ("/", extension_url.path());
+
+ GURL ret_val = GURL(extension_url.spec() + relative_path);
+ DCHECK(StartsWithASCII(ret_val.spec(), extension_url.spec(), false));
+
+ return ret_val;
+}
+
+// TODO(mad@chromium.org): Reuse code in common\extensions\extension.cc
+HRESULT ExtensionManifest::CalculateIdFromPublicKey() {
+ std::string public_key_bytes;
+ if (!base::Base64Decode(public_key_, &public_key_bytes))
+ return E_FAIL;
+
+ if (public_key_bytes.length() == 0)
+ return E_FAIL;
+
+ // SHA256 needs to work with an array of bytes, which we get from a string.
+ const uint8* ubuf =
+ reinterpret_cast<const unsigned char*>(public_key_bytes.data());
+ SHA256Context ctx;
+ SHA256_Begin(&ctx);
+ SHA256_Update(&ctx, ubuf, public_key_bytes.length());
+ // We must hash this value to a fixed size array.
+ uint8 hash[kIdSize];
+ SHA256_End(&ctx, hash, NULL, sizeof(hash));
+ // To stay in sync with the code in Chrome, we start by converting the bytes
+ // to a string representing the concatenation of the Hex values of all bytes.
+ extension_id_ = base::HexEncode(hash, sizeof(hash));
+ for (size_t i = 0; i < extension_id_.size(); ++i) {
+ // And then, for each nibble represented by a single Hex digit, we use
+ // the value to offset from the letter 'a' to construct the key in the
+ // limited alphabet used for Chrome extension Ids ['a', 'q'].
+ int val = -1;
+ if (base::HexStringToInt(extension_id_.substr(i, 1), &val))
+ extension_id_[i] = val + 'a';
+ else
+ extension_id_[i] = 'a';
+ }
+ return S_OK;
+}
+
+
+// TODO(mad@chromium.org): Unbranch (taken from the Extension class).
+HRESULT ExtensionManifest::LoadUserScriptHelper(
+ const DictionaryValue* content_script, UserScript* result) {
+ // run_at
+ if (content_script->HasKey(ext_keys::kRunAt)) {
+ std::string run_location;
+ if (!content_script->GetString(ext_keys::kRunAt, &run_location)) {
+ LOG(ERROR) << "Invalid toolstrip JSON value";
+ return E_FAIL;
+ }
+
+ if (run_location == ext_values::kRunAtDocumentStart) {
+ result->set_run_location(UserScript::DOCUMENT_START);
+ } else if (run_location == ext_values::kRunAtDocumentEnd) {
+ result->set_run_location(UserScript::DOCUMENT_END);
+ } else if (run_location == ext_values::kRunAtDocumentIdle) {
+ result->set_run_location(UserScript::DOCUMENT_IDLE);
+ } else {
+ LOG(ERROR) << "Invalid toolstrip JSON value";
+ return E_FAIL;
+ }
+ }
+
+ // all frames
+ if (content_script->HasKey(ext_keys::kAllFrames)) {
+ bool all_frames = false;
+ if (!content_script->GetBoolean(ext_keys::kAllFrames, &all_frames)) {
+ LOG(ERROR) << "Invalid toolstrip JSON value";
+ return E_FAIL;
+ }
+ result->set_match_all_frames(all_frames);
+ }
+
+ // matches
+ ListValue* matches = NULL;
+ if (!content_script->GetList(ext_keys::kMatches, &matches)) {
+ LOG(ERROR) << "Invalid manifest without a matches value";
+ return E_FAIL;
+ }
+
+ if (matches->GetSize() == 0) {
+ LOG(ERROR) << "Invalid manifest without a matches value";
+ return E_FAIL;
+ }
+ for (size_t i = 0; i < matches->GetSize(); ++i) {
+ std::string match_str;
+ if (!matches->GetString(i, &match_str)) {
+ LOG(ERROR) << "Invalid matches JSON value";
+ return E_FAIL;
+ }
+
+ URLPattern pattern(UserScript::kValidUserScriptSchemes);
+ if (pattern.Parse(match_str) != URLPattern::PARSE_SUCCESS) {
+ LOG(ERROR) << "Invalid matches value";
+ return E_FAIL;
+ }
+
+ result->add_url_pattern(pattern);
+ }
+
+ // js and css keys
+ ListValue* js = NULL;
+ if (content_script->HasKey(ext_keys::kJs) &&
+ !content_script->GetList(ext_keys::kJs, &js)) {
+ LOG(ERROR) << "Invalid content script JS JSON value";
+ return E_FAIL;
+ }
+
+ ListValue* css = NULL;
+ if (content_script->HasKey(ext_keys::kCss) &&
+ !content_script->GetList(ext_keys::kCss, &css)) {
+ LOG(ERROR) << "Invalid content script CSS JSON value";
+ return E_FAIL;
+ }
+
+ // The manifest needs to have at least one js or css user script definition.
+ if (((js ? js->GetSize() : 0) + (css ? css->GetSize() : 0)) == 0) {
+ LOG(ERROR) << "Invalid manifest without any content script";
+ return E_FAIL;
+ }
+
+ if (js) {
+ for (size_t script_index = 0; script_index < js->GetSize();
+ ++script_index) {
+ Value* value;
+ std::wstring relative;
+ if (!js->Get(script_index, &value) || !value->GetAsString(&relative)) {
+ LOG(ERROR) << "Invalid content script JS JSON value";
+ return E_FAIL;
+ }
+ // TODO(mad@chromium.org): Make GetResourceUrl accept wstring
+ // too and check with georged@chromium.org who has the same todo
+ // in chrome\common\extensions\extension.cc
+ GURL url = GetResourceUrl(WideToUTF8(relative));
+ result->js_scripts().push_back(
+ UserScript::File(path(), FilePath(relative), url));
+ // TODO(mad@chromium.org): Verify that the path refers to an
+ // existing file.
+ }
+ }
+
+ if (css) {
+ for (size_t script_index = 0; script_index < css->GetSize();
+ ++script_index) {
+ Value* value;
+ std::wstring relative;
+ if (!css->Get(script_index, &value) || !value->GetAsString(&relative)) {
+ LOG(ERROR) << "Invalid content script CSS JSON value";
+ return E_FAIL;
+ }
+ // TODO(mad@chromium.org): Make GetResourceUrl accept wstring
+ // too and check with georged@chromium.org who has the same todo
+ // in chrome\common\extensions\extension.cc
+ GURL url = GetResourceUrl(WideToUTF8(relative));
+ result->css_scripts().push_back(
+ UserScript::File(path(), FilePath(relative), url));
+ // TODO(mad@chromium.org): Verify that the path refers to an
+ // existing file.
+ }
+ }
+
+ return S_OK;
+}
diff --git a/ceee/ie/common/extension_manifest.h b/ceee/ie/common/extension_manifest.h
new file mode 100644
index 0000000..11fe165
--- /dev/null
+++ b/ceee/ie/common/extension_manifest.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.
+//
+// Utility class to access Chrome Extension manifest data.
+
+#ifndef CEEE_IE_COMMON_EXTENSION_MANIFEST_H_
+#define CEEE_IE_COMMON_EXTENSION_MANIFEST_H_
+
+#include <wtypes.h>
+
+#include <string>
+#include <vector>
+
+#include "base/basictypes.h"
+#include "base/file_path.h"
+#include "chrome/common/extensions/user_script.h"
+#include "googleurl/src/gurl.h"
+
+
+class DictionaryValue;
+
+
+// A helper class to read data from a Chrome Extension manifest file.
+// TODO(mad@chromium.org): Find a way to reuse code from chrome
+// extension classes.
+class ExtensionManifest {
+ public:
+ ExtensionManifest() {}
+ ~ExtensionManifest() {}
+
+ // The name of the manifest inside an extension.
+ static const char kManifestFilename[];
+
+ // The number of bytes in a legal id.
+ static const size_t kIdSize;
+
+ // Clears the current content and reset it with the content of the manifest
+ // file found under the provided Chrome extension folder path.
+ HRESULT ReadManifestFile(const FilePath& extension_path, bool require_id);
+
+ // Returns the extension ID as read from manifest without any transformation.
+ const std::string& extension_id() const {
+ return extension_id_;
+ }
+
+ // Returns the public key as read from manifest without any transformation.
+ const std::string& public_key() const {
+ return public_key_;
+ }
+
+ // Returns the list of toolstrip file names.
+ const std::vector<std::string>& GetToolstripFileNames() const {
+ return toolstrip_file_names_;
+ }
+
+ const FilePath& path() const { return path_; }
+ const GURL& extension_url() const { return extension_url_; }
+ const UserScriptList& content_scripts() const { return content_scripts_; }
+
+ // Returns an absolute url to a resource inside of an extension. The
+ // |extension_url| argument should be the url() from an Extension object. The
+ // |relative_path| can be untrusted user input. The returned URL will either
+ // be invalid() or a child of |extension_url|.
+ // NOTE: Static so that it can be used from multiple threads.
+ static GURL GetResourceUrl(const GURL& extension_url,
+ const std::string& relative_path);
+ GURL GetResourceUrl(const std::string& relative_path) {
+ return GetResourceUrl(extension_url(), relative_path);
+ }
+
+ private:
+ // Transforms the value public_key_ to set the value of extension_id_
+ // using the same algorithm as Chrome.
+ HRESULT CalculateIdFromPublicKey();
+
+ // Helper method that loads a UserScript object from a dictionary in the
+ // content_script list of the manifest.
+ HRESULT LoadUserScriptHelper(const DictionaryValue* content_script,
+ UserScript* result);
+
+ // The absolute path to the directory the extension is stored in.
+ FilePath path_;
+
+ // The base extension url for the extension.
+ GURL extension_url_;
+
+ // Paths to the content scripts the extension contains.
+ UserScriptList content_scripts_;
+
+ std::vector<std::string> toolstrip_file_names_;
+ std::string extension_id_;
+ std::string public_key_;
+
+ DISALLOW_COPY_AND_ASSIGN(ExtensionManifest);
+};
+
+#endif // CEEE_IE_COMMON_EXTENSION_MANIFEST_H_
diff --git a/ceee/ie/common/extension_manifest_unittest.cc b/ceee/ie/common/extension_manifest_unittest.cc
new file mode 100644
index 0000000..9a3d990
--- /dev/null
+++ b/ceee/ie/common/extension_manifest_unittest.cc
@@ -0,0 +1,319 @@
+// 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 "chrome/common/extensions/extension_constants.h"
+#include "chrome/common/extensions/user_script.h"
+#include "ceee/ie/common/extension_manifest.h"
+#include "ceee/testing/utils/test_utils.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+namespace ext_keys = extension_manifest_keys;
+namespace ext_values = extension_manifest_values;
+
+namespace {
+
+// Static constants
+static const char* kToolstripName1 = "MyToolstrip.html";
+static const char* kToolstripName2 = "YourToolstrip.html";
+static const char* kPublicKeyName = "0123456789ABCDEF0123456789ABCDEF";
+static const char* kUrlPattern1 = "http://madlymad.com/*";
+static const char* kUrlPattern2 = "https://superdave.com/*";
+static const char* kCssPath = "CssPath.css";
+static const wchar_t* kCssWPath = L"CssPath.css";
+static const char* kJsPath1 = "JsPath1.js";
+static const wchar_t* kJsWPath1 = L"JsPath1.js";
+static const char* kJsPath2 = "JsPath2.js";
+static const wchar_t* kJsWPath2 = L"JsPath2.js";
+// This Id value has been computed with a valid version of the code.
+// If the algorithm changes, we must update this value.
+static const char* kComputedId = "fppgjfcenabdcibneonnejnkdafgjcch";
+
+static const char* kExtensionUrl =
+ "chrome-extension://fppgjfcenabdcibneonnejnkdafgjcch/";
+
+// Test fixture to handle the stuff common to all tests.
+class ExtensionManifestTest : public testing::Test {
+ protected:
+ ExtensionManifestTest()
+ // We need to remember whether we created the file or not so that we
+ // can delete it in TearDown because some tests don't create a file.
+ : created_file_(false) {
+ }
+
+ // We always use the same temporary file name, so we can make it static.
+ static void SetUpTestCase() {
+ EXPECT_TRUE(file_util::GetTempDir(&file_dir_));
+ file_path_ = file_dir_.AppendASCII(ExtensionManifest::kManifestFilename);
+ }
+
+ // This is the common code to write the Json values to the manifest file.
+ void WriteJsonToFile(const Value& value) {
+ std::string json_string;
+ base::JSONWriter::Write(&value, false, &json_string);
+
+ FILE* temp_file = file_util::OpenFile(file_path_, "w");
+ EXPECT_TRUE(temp_file != NULL);
+ created_file_ = true;
+
+ fwrite(json_string.c_str(), json_string.size(), 1, temp_file);
+ file_util::CloseFile(temp_file);
+ temp_file = NULL;
+ }
+
+ // We must delete the file on each tests that created one.
+ virtual void TearDown() {
+ if (created_file_) {
+ EXPECT_TRUE(file_util::Delete(file_path_, false));
+ }
+ }
+
+ protected:
+ static FilePath file_dir_;
+ // We keep both the file path and the dir only path to avoid reconstructing it
+ // all the time.
+ static FilePath file_path_;
+
+ private:
+ bool created_file_;
+};
+
+FilePath ExtensionManifestTest::file_dir_;
+FilePath ExtensionManifestTest::file_path_;
+
+
+TEST_F(ExtensionManifestTest, InvalidFileName) {
+ testing::LogDisabler no_dchecks;
+
+ // This test assumes that there is no manifest file in the current path.
+ base::PlatformFileInfo dummy;
+ EXPECT_FALSE(file_util::GetFileInfo(FilePath(L"manifest.json"), &dummy));
+
+ ExtensionManifest manifest;
+ EXPECT_HRESULT_FAILED(manifest.ReadManifestFile(FilePath(), false));
+ EXPECT_HRESULT_FAILED(manifest.ReadManifestFile(
+ FilePath(L"LalaLandBoobooGaloo"), false));
+}
+
+TEST_F(ExtensionManifestTest, EmptyFile) {
+ testing::LogDisabler no_dchecks;
+
+ // Value's constructor is protected, so we must go dynamic.
+ scoped_ptr<Value> value(Value::CreateNullValue());
+ WriteJsonToFile(*value);
+
+ ExtensionManifest manifest;
+ EXPECT_HRESULT_FAILED(manifest.ReadManifestFile(file_dir_, false));
+}
+
+TEST_F(ExtensionManifestTest, InvalidJsonFile) {
+ testing::LogDisabler no_dchecks;
+
+ ListValue dummy_list;
+ dummy_list.Append(Value::CreateIntegerValue(42));
+
+ WriteJsonToFile(dummy_list);
+
+ ExtensionManifest manifest;
+ EXPECT_HRESULT_FAILED(manifest.ReadManifestFile(file_dir_, false));
+}
+
+TEST_F(ExtensionManifestTest, InvalidPublicKey) {
+ testing::LogDisabler no_dchecks;
+
+ DictionaryValue values;
+ values.Set(ext_keys::kPublicKey,
+ Value::CreateStringValue("Babebibobu"));
+
+ WriteJsonToFile(values);
+
+ ExtensionManifest manifest;
+ EXPECT_HRESULT_FAILED(manifest.ReadManifestFile(file_dir_, false));
+ EXPECT_TRUE(manifest.public_key().empty());
+}
+
+TEST_F(ExtensionManifestTest, InvalidUserScript) {
+ testing::LogDisabler no_dchecks;
+
+ DictionaryValue values;
+ ListValue* scripts = new ListValue();
+ values.Set(ext_keys::kContentScripts, scripts);
+ DictionaryValue* script_dict = new DictionaryValue;
+ scripts->Append(script_dict);
+
+ // Empty scripts are not allowed.
+ WriteJsonToFile(values);
+ ExtensionManifest manifest;
+ EXPECT_HRESULT_FAILED(manifest.ReadManifestFile(file_dir_, false));
+
+ ListValue* matches1 = new ListValue();
+ script_dict->Set(ext_keys::kMatches, matches1);
+
+ // Matches must have at least one value.
+ WriteJsonToFile(values);
+ EXPECT_HRESULT_FAILED(manifest.ReadManifestFile(file_dir_, false));
+
+ matches1->Append(Value::CreateStringValue(kUrlPattern1));
+
+ // Having a match isn't enough without at least one CSS or JS file.
+ WriteJsonToFile(values);
+ EXPECT_HRESULT_FAILED(manifest.ReadManifestFile(file_dir_, false));
+
+ ListValue* css = new ListValue();
+ script_dict->Set(ext_keys::kCss, css);
+
+ // CSS list must have at least one item.
+ WriteJsonToFile(values);
+ EXPECT_HRESULT_FAILED(manifest.ReadManifestFile(file_dir_, false));
+
+ script_dict->Remove(ext_keys::kCss, NULL);
+ ListValue* js = new ListValue();
+ script_dict->Set(ext_keys::kJs, js);
+
+ // Same thing for JS.
+ WriteJsonToFile(values);
+ EXPECT_HRESULT_FAILED(manifest.ReadManifestFile(file_dir_, false));
+}
+
+TEST_F(ExtensionManifestTest, EmptyValidJsonFile) {
+ testing::LogDisabler no_dchecks;
+
+ WriteJsonToFile(DictionaryValue());
+
+ ExtensionManifest manifest;
+ EXPECT_HRESULT_SUCCEEDED(manifest.ReadManifestFile(file_dir_, false));
+ EXPECT_TRUE(manifest.GetToolstripFileNames().empty());
+ EXPECT_STREQ(manifest.path().value().c_str(), file_dir_.value().c_str());
+}
+
+TEST_F(ExtensionManifestTest, ValidJsonFileWithOneValue) {
+ testing::LogDisabler no_dchecks;
+
+ DictionaryValue values;
+ values.SetString("name", "My Name");
+
+ // We must dynamically allocate since DictionaryValue will free this memory.
+ ListValue * toolstrips = new ListValue();
+ toolstrips->Append(Value::CreateStringValue(kToolstripName1));
+ values.Set(ext_keys::kToolstrips, toolstrips);
+ toolstrips = NULL;
+
+ WriteJsonToFile(values);
+
+ ExtensionManifest manifest;
+ EXPECT_HRESULT_SUCCEEDED(manifest.ReadManifestFile(file_dir_, false));
+ EXPECT_FALSE(manifest.GetToolstripFileNames().empty());
+ EXPECT_STREQ(manifest.GetToolstripFileNames()[0].c_str(), kToolstripName1);
+ EXPECT_TRUE(manifest.public_key().empty());
+ EXPECT_TRUE(manifest.extension_id().empty());
+}
+
+TEST_F(ExtensionManifestTest, ValidJsonFileWithManyValues) {
+ testing::LogDisabler no_dchecks;
+
+ DictionaryValue values;
+ values.SetString("name", "My Name");
+ values.SetString("job", "Your Job");
+ values.SetString(ext_keys::kPublicKey, kPublicKeyName);
+
+ ListValue* toolstrips = new ListValue();
+ toolstrips->Append(Value::CreateStringValue(kToolstripName1));
+ toolstrips->Append(Value::CreateStringValue(kToolstripName2));
+ values.Set(ext_keys::kToolstrips, toolstrips);
+ toolstrips = NULL;
+
+ ListValue* scripts = new ListValue();
+ values.Set(ext_keys::kContentScripts, scripts);
+ DictionaryValue* script_dict1 = new DictionaryValue;
+ scripts->Append(script_dict1);
+ script_dict1->SetString(ext_keys::kRunAt,
+ ext_values::kRunAtDocumentStart);
+
+ ListValue* matches1 = new ListValue();
+ script_dict1->Set(ext_keys::kMatches, matches1);
+ matches1->Append(Value::CreateStringValue(kUrlPattern1));
+
+ ListValue* css = new ListValue();
+ script_dict1->Set(ext_keys::kCss, css);
+ css->Append(Value::CreateStringValue(kCssPath));
+
+ DictionaryValue* script_dict2 = new DictionaryValue;
+ scripts->Append(script_dict2);
+
+ ListValue* matches2 = new ListValue();
+ script_dict2->Set(ext_keys::kMatches, matches2);
+ matches2->Append(Value::CreateStringValue(kUrlPattern1));
+ matches2->Append(Value::CreateStringValue(kUrlPattern2));
+
+ ListValue* js = new ListValue();
+ script_dict2->Set(ext_keys::kJs, js);
+ js->Append(Value::CreateStringValue(kJsPath1));
+ js->Append(Value::CreateStringValue(kJsPath2));
+
+ WriteJsonToFile(values);
+
+ ExtensionManifest manifest;
+ EXPECT_HRESULT_SUCCEEDED(manifest.ReadManifestFile(file_dir_, true));
+ EXPECT_FALSE(manifest.GetToolstripFileNames().empty());
+ // Prevent asserts blocking the tests if the test above failed.
+ if (manifest.GetToolstripFileNames().size() > 1) {
+ EXPECT_STREQ(manifest.GetToolstripFileNames()[0].c_str(), kToolstripName1);
+ EXPECT_STREQ(manifest.GetToolstripFileNames()[1].c_str(), kToolstripName2);
+ }
+ EXPECT_STREQ(manifest.public_key().c_str(), kPublicKeyName);
+ EXPECT_STREQ(manifest.extension_id().c_str(), kComputedId);
+ EXPECT_STREQ(manifest.path().value().c_str(), file_dir_.value().c_str());
+ EXPECT_EQ(manifest.extension_url(), GURL(kExtensionUrl));
+
+ URLPattern url_pattern1(UserScript::kValidUserScriptSchemes);
+ url_pattern1.Parse(kUrlPattern1);
+ URLPattern url_pattern2(UserScript::kValidUserScriptSchemes);
+ url_pattern2.Parse(kUrlPattern2);
+
+ const UserScriptList& script_list = manifest.content_scripts();
+ EXPECT_EQ(script_list.size(), 2);
+ if (script_list.size() == 2) {
+ const UserScript& script1 = script_list[0];
+ EXPECT_EQ(script1.run_location(),
+ UserScript::DOCUMENT_START);
+ const std::vector<URLPattern>& url_patterns1 = script1.url_patterns();
+ EXPECT_EQ(url_patterns1.size(), 1);
+ if (url_patterns1.size() == 1)
+ EXPECT_EQ(url_patterns1[0].GetAsString(), url_pattern1.GetAsString());
+ const UserScript::FileList& css_scripts = script1.css_scripts();
+ EXPECT_EQ(css_scripts.size(), 1);
+ if (css_scripts.size() == 1) {
+ EXPECT_STREQ(css_scripts[0].extension_root().value().c_str(),
+ file_dir_.value().c_str());
+ EXPECT_STREQ(css_scripts[0].relative_path().value().c_str(), kCssWPath);
+ }
+ const UserScript& script2 = script_list[1];
+ const std::vector<URLPattern>& url_patterns2 = script2.url_patterns();
+ EXPECT_EQ(url_patterns2.size(), 2);
+ if (url_patterns2.size() == 2) {
+ EXPECT_EQ(url_patterns2[0].GetAsString(), url_pattern1.GetAsString());
+ EXPECT_EQ(url_patterns2[1].GetAsString(), url_pattern2.GetAsString());
+ }
+ const UserScript::FileList& js_scripts = script2.js_scripts();
+ EXPECT_EQ(js_scripts.size(), 2);
+ if (js_scripts.size() == 2) {
+ EXPECT_STREQ(js_scripts[0].extension_root().value().c_str(),
+ file_dir_.value().c_str());
+ EXPECT_STREQ(js_scripts[0].relative_path().value().c_str(), kJsWPath1);
+ EXPECT_STREQ(js_scripts[1].extension_root().value().c_str(),
+ file_dir_.value().c_str());
+ EXPECT_STREQ(js_scripts[1].relative_path().value().c_str(), kJsWPath2);
+ }
+ }
+}
+
+} // namespace
diff --git a/ceee/ie/common/ie_guids.cc b/ceee/ie/common/ie_guids.cc
new file mode 100644
index 0000000..0babc29
--- /dev/null
+++ b/ceee/ie/common/ie_guids.cc
@@ -0,0 +1,26 @@
+// 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.
+//
+// Pull in all the GUIDs we rely on.
+#include "broker_lib.h" // NOLINT
+#include "chrome_tab.h" // NOLINT
+#include "toolband.h" // NOLINT
+
+#include <initguid.h> // NOLINT
+
+// Dispex guids - I don't know that they come in any library.
+#include <dispex.h>
+
+// Pull in the tab interface guids also.
+#include "ceee/ie/common/ie_tab_interfaces.h"
+#include "third_party/activscp/activdbg.h"
+
+extern "C" {
+#include "broker_lib_i.c" // NOLINT
+#include "toolband_i.c" // NOLINT
+
+const GUID IID_IProcessDebugManager = __uuidof(IProcessDebugManager);
+const GUID IID_IDebugApplication = __uuidof(IDebugApplication);
+const GUID IID_IDebugDocumentHelper = __uuidof(IDebugDocumentHelper);
+} // extern "C"
diff --git a/ceee/ie/common/ie_tab_interfaces.cc b/ceee/ie/common/ie_tab_interfaces.cc
new file mode 100644
index 0000000..eeadd38e
--- /dev/null
+++ b/ceee/ie/common/ie_tab_interfaces.cc
@@ -0,0 +1,46 @@
+// 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.
+//
+// ITabWindow and ITabWindowManager interfaces and approaches
+// related to them.
+
+#include "ceee/ie/common/ie_tab_interfaces.h"
+
+#include <shlguid.h> // SID_SWebBrowserApp
+
+#include "base/logging.h"
+#include "ceee/common/com_utils.h"
+#include "ceee/common/windows_constants.h"
+#include "ceee/common/window_utils.h"
+
+namespace ie_tab_interfaces {
+
+HRESULT TabWindowManagerFromFrame(HWND ie_frame, REFIID riid, void** manager) {
+ DCHECK(ie_frame);
+ DCHECK(manager && !*manager);
+
+ if (!window_utils::IsWindowThread(ie_frame)) {
+ LOG(ERROR) << "Can't get tab window manager from frame in other process "
+ "or thread that created the frame window.";
+ return E_INVALIDARG;
+ }
+
+ IDropTarget* drop_target = reinterpret_cast<IDropTarget*>(
+ ::GetPropW(ie_frame, L"OleDropTargetInterface"));
+ if (!drop_target) {
+ NOTREACHED() << "No drop target";
+ return E_UNEXPECTED;
+ }
+
+ CComQIPtr<IServiceProvider> frame_service_provider(drop_target);
+ if (!frame_service_provider) {
+ NOTREACHED();
+ return E_NOINTERFACE;
+ }
+
+ return frame_service_provider->QueryService(SID_STabWindowManager, riid,
+ manager);
+}
+
+} // namespace ie_tab_interfaces
diff --git a/ceee/ie/common/ie_tab_interfaces.h b/ceee/ie/common/ie_tab_interfaces.h
new file mode 100644
index 0000000..2340692
--- /dev/null
+++ b/ceee/ie/common/ie_tab_interfaces.h
@@ -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.
+//
+// ITabWindow and ITabWindowManager interfaces and approaches
+// related to them.
+
+#ifndef CEEE_IE_COMMON_IE_TAB_INTERFACES_H_
+#define CEEE_IE_COMMON_IE_TAB_INTERFACES_H_
+
+#include <atlbase.h>
+#include <exdisp.h> // IWebBrowser2
+#include <guiddef.h> // DEFINE_GUID
+#include <mshtml.h> // IHTMLDocument2
+
+// Service ID to get the tab window manager.
+// {122F0301-9AB9-4CBE-B5F6-CEADCF6AA9B7}
+DEFINE_GUID(SID_STabWindowManager,
+ 0x122F0301L,
+ 0x9AB9,
+ 0x4CBE,
+ 0xB5, 0xF6, 0xCE, 0xAD, 0xCF, 0x6A, 0xA9, 0xB7);
+
+// Adapted from documentation available at
+// http://www.geoffchappell.com/viewer.htm?doc=studies/windows/ie/ieframe/interfaces/itabwindowmanager.htm
+//
+// Available in IE7, IE8 and IE9. Retrieve by QueryService for
+// SID_STabWindowManager on the IServiceProvider that is the
+// IDropTarget you can retrieve from the browser frame window.
+//
+// The ITabWindowManager interface is NOT the same in IE7, IE8 and
+// IE9, and has different IIDs in each.
+
+interface __declspec(uuid("CAE57FE7-5E06-4804-A285-A985E76708CD"))
+ ITabWindowManagerIe7 : IUnknown {
+ STDMETHOD(AddTab)(LPCITEMIDLIST pidl, UINT, ULONG, long*) PURE;
+ STDMETHOD(SelectTab)(long tab) PURE;
+ STDMETHOD(CloseAllTabs)() PURE;
+ // Actually ITabWindow**
+ STDMETHOD(GetActiveTab)(IUnknown** active_tab) PURE;
+ STDMETHOD(GetCount)(long* count) PURE;
+ // Actually ITabWindow**
+ STDMETHOD(GetItem)(long index, IUnknown** tab_window) PURE;
+ STDMETHOD(IndexFromID)(long id, long* index) PURE;
+ // Window class of window should be "TabWindowClass"
+ STDMETHOD(IndexFromHWND)(HWND window, long* index) PURE;
+ // Actually IBrowserFrame**
+ STDMETHOD(GetBrowserFrame)(IUnknown** browser_frame) PURE;
+ STDMETHOD(AddBlankTab)(unsigned long ul, long* l) PURE;
+ STDMETHOD(AddTabGroup)(LPCITEMIDLIST* pidl, long l, unsigned long ul) PURE;
+ STDMETHOD(GetCurrentTabGroup)(LPCITEMIDLIST** pidl, long* l1, long* l2) PURE;
+ STDMETHOD(OpenHomePages)(int flags) PURE;
+ // @param moving_id ID (as in ITabWindow::GetID) of the tab being moved.
+ // @param dest_id ID of the tab currently in the desired destination position.
+ STDMETHOD(RepositionTab)(long moving_id, long dest_id, int) PURE;
+};
+
+interface __declspec(uuid("9706DA66-D17C-48a5-B42D-39963D174DC0"))
+ ITabWindowManagerIe8 : IUnknown {
+ STDMETHOD(AddTab)(LPCITEMIDLIST pidl, UINT, ULONG, long*) PURE;
+ STDMETHOD(_AddTabByPosition)(void * UNKNOWN_ARGUMENTS) PURE;
+ STDMETHOD(SelectTab)(long) PURE;
+ STDMETHOD(CloseAllTabs)() PURE;
+ // Actually ITabWindow**
+ STDMETHOD(GetActiveTab)(IUnknown** active_tab) PURE;
+ STDMETHOD(GetCount)(long* count) PURE;
+ STDMETHOD(_GetFilteredCount)(void * UNKNOWN_ARGUMENTS) PURE;
+ // Actually ITabWindow**
+ STDMETHOD(GetItem)(long index, IUnknown** tab_window) PURE;
+ STDMETHOD(IndexFromID)(long id, long* index) PURE;
+ STDMETHOD(_FilteredIndexFromID)(void * UNKNOWN_ARGUMENTS) PURE;
+ // Window class of window should be "TabWindowClass"
+ STDMETHOD(IndexFromHWND)(HWND window, long* index) PURE;
+ // Actually IBrowserFrame**
+ STDMETHOD(GetBrowserFrame)(IUnknown** browser_frame) PURE;
+ STDMETHOD(AddBlankTab)(unsigned long, long*) PURE;
+ STDMETHOD(_AddBlankTabEx)(void * UNKNOWN_ARGUMENTS) PURE;
+ STDMETHOD(AddTabGroup)(LPCITEMIDLIST* pidl, long, unsigned long) PURE;
+ STDMETHOD(GetCurrentTabGroup)(LPCITEMIDLIST** pidl, long*, long*) PURE;
+ STDMETHOD(OpenHomePages)(int flags) PURE;
+ // @param moving_id ID (as in ITabWindow::GetID) of the tab being moved.
+ // @param dest_id ID of the tab currently in the desired destination position.
+ STDMETHOD(RepositionTab)(long moving_id, long dest_id, int) PURE;
+};
+
+// In IE9 IID and definition of the interface has changed.
+interface __declspec(uuid("8059E123-28D5-4C75-A298-664B3720ACAE"))
+ ITabWindowManagerIe9 : IUnknown {
+ STDMETHOD(AddTab)(LPCITEMIDLIST pidl, UINT, ULONG, IUnknown*, long*) PURE;
+ STDMETHOD(AddTabByPosition)(LPCITEMIDLIST pidl, UINT, ULONG, long,
+ IUnknown*, DWORD*, long*) PURE;
+ STDMETHOD(SelectTab)(long) PURE;
+ STDMETHOD(CloseAllTabs)(void) PURE;
+ // Actually ITabWindow**
+ STDMETHOD(GetActiveTab)(IUnknown**) PURE;
+ STDMETHOD(GetCount)(long*) PURE;
+ STDMETHOD(GetFilteredCount)(long*, long) PURE;
+ STDMETHOD(GetItem)(long index, IUnknown** tab_window) PURE;
+ STDMETHOD(IndexFromID)(long id, long* index) PURE;
+ STDMETHOD(FilteredIndexFromID)(long, long, long*) PURE;
+ STDMETHOD(IndexFromHWND)(HWND window, long* index) PURE;
+ // Actual IBrowserFrame **
+ STDMETHOD(GetBrowserFrame)(IUnknown**) PURE;
+ STDMETHOD(AddBlankTab)(ULONG, long*) PURE;
+ STDMETHOD(AddBlankTabEx)(ULONG, DWORD*, long*) PURE;
+ STDMETHOD(AddTabGroup)(DWORD, long, ULONG) PURE;
+ STDMETHOD(GetCurrentTabGroup)(DWORD, long*, long*) PURE;
+ STDMETHOD(OpenHomePages)(int) PURE;
+ STDMETHOD(RepositionTab)(long, long, int) PURE;
+ // Actually IClosedTabManager**
+ STDMETHOD(GetUndoTabManager)(IUnknown**) PURE;
+ STDMETHOD(GetRestoreTabManager)(IUnknown**) PURE;
+ // Actually, ITabWindowEvents* (a new interface)
+ STDMETHOD(AddTabWindowEventHandler)(IUnknown*) PURE;
+ // Actually, ITabWindowEvents* (a new interface)
+ STDMETHOD(UnregisterTabWindowEventHandler)(IUnknown*) PURE;
+ STDMETHOD(CloseTabGroup)(long) PURE;
+ STDMETHOD(CreateGroupMapping)(long*) PURE;
+ STDMETHOD(DestroyGroupMapping)(long) PURE;
+ STDMETHOD(SetDecorationPreference)(DWORD, DWORD*) PURE;
+ STDMETHOD(FindTabAdjacentToGroup)(long, long,
+ DWORD, IUnknown**, long*) PURE;
+ STDMETHOD(GetNewGroupID)(long*) PURE;
+ STDMETHOD(CloseOldTabIfFailed)(void) PURE;
+ STDMETHOD(CloseAllTabsExcept)(long) PURE;
+ STDMETHOD(CloseAllTabsExceptActive)(void) PURE;
+};
+
+// Adapted from documentation available at
+// http://www.geoffchappell.com/viewer.htm?doc=studies/windows/ie/ieframe/interfaces/itabwindow.htm
+//
+// Also available differently in IE7 and IE8.
+interface __declspec(uuid("9BAB3405-EE3F-4040-8836-25AA9C2D408E"))
+ ITabWindowIe7 : IUnknown {
+ STDMETHOD(GetID)(long* id) PURE;
+ STDMETHOD(Close)() PURE;
+ STDMETHOD(AsyncExec)(REFGUID cmd_group, DWORD cmd_id, DWORD exec_opt,
+ VARIANT* in_args, VARIANT* out_args) PURE;
+ STDMETHOD(GetTabWindowManager)(ITabWindowManagerIe7** tab_manager) PURE;
+ STDMETHOD(OnBrowserCreated)(int, int, int, int, int, void*) PURE;
+ STDMETHOD(OnNewWindow)(ULONG, IDispatch**) PURE;
+ STDMETHOD(OnBrowserClosed)() PURE;
+ // Actually enum tagTAB_ATTENTION_STATE
+ STDMETHOD(OnRequestAttention)(int i) PURE;
+ STDMETHOD(FrameTranslateAccelerator)(MSG* msg, ULONG) PURE;
+ STDMETHOD(SetTitle)(LPCWSTR title, int title_length) PURE;
+ STDMETHOD(SetIcon)(HICON, int) PURE;
+ STDMETHOD(SetStatusBarState)(int, long) PURE;
+ STDMETHOD(GetTitle)(LPWSTR title, ULONG, int) PURE;
+ STDMETHOD(GetIcon)(HICON* icon, int*, int) PURE;
+ STDMETHOD(GetLocationPidl)(LPCITEMIDLIST* pidl) PURE;
+ STDMETHOD(GetNavigationState)(ULONG* state) PURE;
+ // First param is enum tagNAVIGATION_BAND_PROGRESS_STATE*
+ STDMETHOD(GetProgress)(int*, long*, long*) PURE;
+ STDMETHOD(GetFlags)(ULONG* flags) PURE;
+ STDMETHOD(GetBrowser)(IDispatch** browser) PURE;
+ STDMETHOD(GetBrowserToolbarWindow)(HWND* window) PURE;
+ // Actually enum tagSEARCH_BAND_SEARCH_STATE*
+ STDMETHOD(GetSearchState)(int* state) PURE;
+ // Actually enum tagTAB_ATTENTION_STATE*
+ STDMETHOD(GetAttentionState)(int* state) PURE;
+ STDMETHOD(ResampleImageAsync)() PURE;
+ STDMETHOD(OnTabImageResampled)(HBITMAP bitmap) PURE;
+ STDMETHOD(GetStatusBarState)(int* bar, long* state) PURE;
+};
+
+interface __declspec(uuid("FF18630E-5B18-4A07-8A75-9FD3CE5A2D14"))
+ ITabWindowIe8 : IUnknown {
+ STDMETHOD(GetID)(long* id) PURE;
+ STDMETHOD(Close)() PURE;
+ STDMETHOD(AsyncExec)(REFGUID cmd_group, DWORD cmd_id, DWORD exec_opt,
+ VARIANT* in_args, VARIANT* out_args) PURE;
+ STDMETHOD(GetTabWindowManager)(ITabWindowManagerIe8** tab_manager) PURE;
+ STDMETHOD(SetBrowserWindowParent)(void * UNKNOWN_ARGUMENTS) PURE;
+ STDMETHOD(OnBrowserCreated)(int, int, int, int, int, void*) PURE;
+ STDMETHOD(OnNewWindow)(ULONG, IDispatch**) PURE;
+ STDMETHOD(OnBrowserClosed)() PURE;
+ // Actually enum tagTAB_ATTENTION_STATE
+ STDMETHOD(OnRequestAttention)(int i) PURE;
+ STDMETHOD(FrameTranslateAccelerator)(MSG* msg, ULONG) PURE;
+ STDMETHOD(SetTitle)(LPCWSTR title, int title_length) PURE;
+ // REMOVED from IE7 version: STDMETHOD(SetIcon)(HICON, int) PURE;
+ STDMETHOD(SetStatusBarState)(int, long) PURE;
+
+ STDMETHOD(GetTitle)(LPWSTR title, ULONG, int) PURE;
+ STDMETHOD(GetIcon)(HICON* icon, int*, int) PURE;
+ STDMETHOD(GetLocationPidl)(LPCITEMIDLIST* pidl) PURE;
+ STDMETHOD(GetLocationUri)(void * UNKNOWN_ARGUMENTS) PURE;
+ STDMETHOD(GetNavigationState)(ULONG* state) PURE;
+ // First param is enum tagNAVIGATION_BAND_PROGRESS_STATE*
+ STDMETHOD(GetProgress)(int*, long*, long*) PURE;
+ STDMETHOD(GetFlags)(ULONG* flags) PURE;
+ STDMETHOD(GetBrowser)(IDispatch** browser) PURE;
+ STDMETHOD(GetBrowserToolbarWindow)(HWND* window) PURE;
+ // Actually enum tagSEARCH_BAND_SEARCH_STATE*
+ STDMETHOD(GetSearchState)(int* state) PURE;
+ // Actually enum tagTAB_ATTENTION_STATE*
+ STDMETHOD(GetAttentionState)(int* state) PURE;
+ STDMETHOD(ResampleImageAsync)() PURE;
+ STDMETHOD(OnTabImageResampled)(HBITMAP bitmap) PURE;
+ STDMETHOD(GetStatusBarState)(int* bar, long* state) PURE;
+};
+
+// New version that was introduced between versions 8.0.6001.18928 and
+// 8.0.7600.16385.
+interface __declspec(uuid("F704B7E0-4760-46ff-BBDB-7439E0A2A814"))
+ ITabWindowIe8_1 : IUnknown {
+ STDMETHOD(GetID)(long* id) PURE;
+ STDMETHOD(Close)() PURE;
+ STDMETHOD(AsyncExec)(REFGUID cmd_group, DWORD cmd_id, DWORD exec_opt,
+ VARIANT* in_args, VARIANT* out_args) PURE;
+ STDMETHOD(GetTabWindowManager)(ITabWindowManagerIe8** tab_manager) PURE;
+ STDMETHOD(SetBrowserWindowParent)(void * UNKNOWN_ARGUMENTS) PURE;
+ STDMETHOD(OnBrowserCreated)(int, int, int, int, int, void*) PURE;
+ STDMETHOD(OnNewWindow)(ULONG, IDispatch**) PURE;
+ STDMETHOD(OnBrowserClosed)() PURE;
+ // Actually enum tagTAB_ATTENTION_STATE
+ STDMETHOD(OnRequestAttention)(int i) PURE;
+ STDMETHOD(FrameTranslateAccelerator)(MSG* msg, ULONG) PURE;
+ STDMETHOD(SetTitle)(LPCWSTR title, int title_length) PURE;
+ // REMOVED from IE7 version: STDMETHOD(SetIcon)(HICON, int) PURE;
+ STDMETHOD(SetStatusBarState)(int, long) PURE;
+ STDMETHOD(GetTitle)(LPWSTR title, ULONG, int) PURE;
+ STDMETHOD(GetIcon)(HICON* icon, int*, int) PURE;
+ STDMETHOD(GetIconWeakReference)(void * UNKNOWN_ARGUMENTS) PURE;
+ STDMETHOD(GetLocationPidl)(LPCITEMIDLIST* pidl) PURE;
+ STDMETHOD(GetLocationUri)(void * UNKNOWN_ARGUMENTS) PURE;
+ STDMETHOD(GetNavigationState)(ULONG* state) PURE;
+ // First param is enum tagNAVIGATION_BAND_PROGRESS_STATE*
+ STDMETHOD(GetProgress)(int*, long*, long*) PURE;
+ STDMETHOD(GetFlags)(ULONG* flags) PURE;
+ STDMETHOD(GetBrowser)(IDispatch** browser) PURE;
+ STDMETHOD(GetBrowserToolbarWindow)(HWND* window) PURE;
+ // Actually enum tagSEARCH_BAND_SEARCH_STATE*
+ STDMETHOD(GetSearchState)(int* state) PURE;
+ // Actually enum tagTAB_ATTENTION_STATE*
+ STDMETHOD(GetAttentionState)(int* state) PURE;
+ STDMETHOD(ResampleImageAsync)() PURE;
+ STDMETHOD(OnTabImageResampled)(HBITMAP bitmap) PURE;
+ STDMETHOD(GetStatusBarState)(int* bar, long* state) PURE;
+};
+
+// Modified interface which appeared in IE9 beta.
+interface __declspec(uuid("3927961B-9DB0-4174-B67A-39F34585A692"))
+ ITabWindowIe9 : IUnknown {
+ STDMETHOD(GetID)(long* id) PURE;
+ STDMETHOD(Close)() PURE;
+ STDMETHOD(AsyncExec)(GUID *, ULONG, ULONG, VARIANT *) PURE;
+ STDMETHOD(GetTabWindowManager)(ITabWindowManagerIe8** tab_manager) PURE;
+ STDMETHOD(SetBrowserWindowParent)(HWND) PURE;
+ STDMETHOD(OnBrowserCreated)(HWND, HWND, HWND, IDispatch*,
+ DWORD*, long) PURE;
+ STDMETHOD(OnNewWindow)(ULONG, ULONG, IDispatch**) PURE;
+ STDMETHOD(OnBrowserClosed)(void) PURE;
+ // Actually enum tagTAB_ATTENTION_STATE
+ STDMETHOD(OnRequestAttention)(int i) PURE;
+ STDMETHOD(FrameTranslateAccelerator)(MSG* msg, ULONG) PURE;
+ STDMETHOD(SetTitle)(LPCWSTR title, int title_length) PURE;
+ STDMETHOD(SetStatusBarState)(int, long) PURE;
+ STDMETHOD(SetITBarHolderWindow)(HWND) PURE;
+ STDMETHOD(EnsureITBar)(int, int) PURE;
+ STDMETHOD(GetTitle)(LPWSTR title, ULONG, int) PURE;
+ STDMETHOD(GetIcon)(HICON* icon, int*, int) PURE;
+ STDMETHOD(GetIconWeakReference)(HICON**) PURE;
+ STDMETHOD(GetLocationPidl)(LPCITEMIDLIST* pidl) PURE;
+ STDMETHOD(GetLocationUri)(IUnknown**) PURE;
+ STDMETHOD(GetNavigationState)(ULONG* state) PURE;
+ // First param is enum tagNAVIGATION_BAND_PROGRESS_STATE*
+ STDMETHOD(GetProgress)(int*, long*, long*) PURE;
+ STDMETHOD(GetFlags)(ULONG* flags) PURE;
+ STDMETHOD(GetBrowser)(IDispatch** browser) PURE;
+ STDMETHOD(GetParentComponentHandle)(HWND* window) PURE;
+ // Actually enum tagSEARCH_BAND_SEARCH_STATE*
+ STDMETHOD(GetSearchState)(int* state) PURE;
+ // Actually enum tagTAB_ATTENTION_STATE*
+ STDMETHOD(GetAttentionState)(int* state) PURE;
+ STDMETHOD(ResampleImageAsync)() PURE;
+ STDMETHOD(OnTabImageResampled)(IStream *) PURE;
+ STDMETHOD(GetStatusBarState)(int* bar, long* state) PURE;
+ // Resolved first time here, but existed in earlier versions.
+ STDMETHOD(GetThumbnailWindow)(HWND**) PURE;
+ STDMETHOD(GetBrowserWindow)(HWND**) PURE;
+ STDMETHOD(TransferRecoveryDataForSiteMode)(void) PURE;
+ STDMETHOD(GetTabGroup)(long*) PURE;
+ STDMETHOD(GetTabGroupDecoration)(DWORD*) PURE;
+ STDMETHOD(JoinTabGroup)(long, long) PURE;
+ STDMETHOD(LeaveTabGroup)(void) PURE;
+ STDMETHOD(IsParticipatingInTabGroup)(int*) PURE;
+ STDMETHOD(IsWaitingForGroupRecovery)(int*) PURE;
+ STDMETHOD(SetWaitingForGroupRecovery)(long, int) PURE;
+ STDMETHOD(BrowserTabIsHung)(void) PURE;
+ STDMETHOD(FrameTabWillNotHang)(ULONG, ULONG) PURE;
+ STDMETHOD(BrowserTabRespondsNow)(ULONG, int, int) PURE;
+ STDMETHOD(BrowserTabRespondsNow_SetHungAsync)(ULONG, ULONG) PURE;
+ STDMETHOD(BrowserTabIsPresumedResponsive)(void) PURE;
+ STDMETHOD(RecoverHungTab)(void) PURE;
+ STDMETHOD(Duplicate)(void) PURE;
+ STDMETHOD(ResetBrowserLCIEProxy)(IDispatch*) PURE;
+ STDMETHOD(SetPendingUrl)(LPCWSTR) PURE;
+};
+
+namespace ie_tab_interfaces {
+
+// Retrieves the requested tab manager interface for the specified IEFrame
+// window.
+//
+// @param ie_frame The top-level frame window you wish to manage.
+// @param riid The identifier of the requested interface.
+// @param manager Returns the IE7 tab window manager on success.
+HRESULT TabWindowManagerFromFrame(HWND ie_frame, REFIID riid, void** manager);
+
+} // namespace ie_tab_interfaces
+
+#endif // CEEE_IE_COMMON_IE_TAB_INTERFACES_H_
diff --git a/ceee/ie/common/ie_util.cc b/ceee/ie/common/ie_util.cc
new file mode 100644
index 0000000..5484898
--- /dev/null
+++ b/ceee/ie/common/ie_util.cc
@@ -0,0 +1,134 @@
+// 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 functions to interact with IE.
+
+#include "ceee/ie/common/ie_util.h"
+
+#include <atlcomcli.h>
+#include <exdisp.h> // IWebBrowser2
+
+#include "base/logging.h"
+#include "base/win/registry.h"
+#include "base/string_util.h"
+#include "ceee/common/com_utils.h"
+
+namespace {
+
+const wchar_t kIeVersionKey[] = L"SOFTWARE\\Microsoft\\Internet Explorer";
+const wchar_t kIeVersionValue[] = L"Version";
+
+HRESULT GetShellWindowsEnum(IEnumVARIANT** enum_windows) {
+ CComPtr<IShellWindows> shell_windows;
+ HRESULT hr = shell_windows.CoCreateInstance(CLSID_ShellWindows);
+ DCHECK(SUCCEEDED(hr)) << "Could not CoCreate ShellWindows. " <<
+ com::LogHr(hr);
+ if (FAILED(hr))
+ return hr;
+ CComPtr<IUnknown> enum_punk;
+ hr = shell_windows->_NewEnum(&enum_punk);
+ DCHECK(SUCCEEDED(hr)) << "Could not get Enum IUnknown??? " <<
+ com::LogHr(hr);
+ if (FAILED(hr))
+ return hr;
+ return enum_punk->QueryInterface(IID_IEnumVARIANT,
+ reinterpret_cast<void**>(enum_windows));
+}
+
+bool GetIeVersionString(std::wstring* version) {
+ DCHECK(version != NULL);
+ if (version == NULL)
+ return false;
+ base::win::RegKey key(HKEY_LOCAL_MACHINE, kIeVersionKey, KEY_READ);
+ DCHECK(key.ValueExists(kIeVersionValue));
+ return key.ReadValue(kIeVersionValue, version);
+}
+
+} // namespace
+
+namespace ie_util {
+
+HRESULT GetWebBrowserForTopLevelIeHwnd(
+ HWND window, IWebBrowser2* not_him, IWebBrowser2** browser) {
+ DCHECK(browser != NULL);
+ CComPtr<IEnumVARIANT> enum_windows;
+ HRESULT hr = GetShellWindowsEnum(&enum_windows);
+ DCHECK(SUCCEEDED(hr)) << "Could not get ShellWindows enum. " <<
+ com::LogHr(hr);
+ if (FAILED(hr))
+ return hr;
+
+ hr = enum_windows->Reset();
+ DCHECK(SUCCEEDED(hr)) << "Can't Reset??? " << com::LogHr(hr);
+ CComVariant shell_window;
+ ULONG fetched = 0;
+ while (SUCCEEDED(enum_windows->Next(1, &shell_window, &fetched)) &&
+ fetched == 1) {
+ DCHECK(shell_window.vt == VT_DISPATCH);
+ CComQIPtr<IWebBrowser2> this_browser(shell_window.pdispVal);
+ if (this_browser != NULL) {
+ HWND this_window = NULL;
+ hr = this_browser->get_HWND(reinterpret_cast<SHANDLE_PTR*>(&this_window));
+ // This can happen if the browser gets deconnected as we loop.
+ if (SUCCEEDED(hr) && this_window == window && not_him != this_browser) {
+ return this_browser.CopyTo(browser);
+ }
+ }
+ shell_window.Clear();
+ }
+ return E_FAIL;
+}
+
+IeVersion GetIeVersion() {
+ std::wstring ie_version_str;
+ if (GetIeVersionString(&ie_version_str)) {
+ int ie_version_num = wcstol(
+ ie_version_str.substr(0, ie_version_str.find(L'.')).c_str(), NULL, 10);
+ if (ie_version_num < 6)
+ return IEVERSION_PRE_IE6;
+ else if (ie_version_num == 6)
+ return IEVERSION_IE6;
+ else if (ie_version_num == 7)
+ return IEVERSION_IE7;
+ else if (ie_version_num == 8)
+ return IEVERSION_IE8;
+ else if (ie_version_num == 9)
+ return IEVERSION_IE9;
+ }
+ DCHECK(false) << "Failed to get a known IE version!!!";
+ return IEVERSION_UNKNOWN;
+}
+
+bool GetIEIsInPrivateBrowsing() {
+ // TODO(skare@google.com): unify with version in chrome_frame/utils.cc
+
+ // InPrivate flag will not change over process lifetime, so cache it. See:
+ // http://blogs.msdn.com/ieinternals/archive/2009/06/30/IE8-Privacy-APIs-for-Addons.aspx
+ static bool inprivate_status_cached = false;
+ static bool is_inprivate = false;
+ if (inprivate_status_cached)
+ return is_inprivate;
+
+ // InPrivate is only supported with IE8+.
+ if (GetIeVersion() < IEVERSION_IE8) {
+ inprivate_status_cached = true;
+ return false;
+ }
+
+ typedef BOOL (WINAPI* IEIsInPrivateBrowsingPtr)();
+ HMODULE ieframe_dll = GetModuleHandle(L"ieframe.dll");
+ if (ieframe_dll) {
+ IEIsInPrivateBrowsingPtr IsInPrivate =
+ reinterpret_cast<IEIsInPrivateBrowsingPtr>(GetProcAddress(
+ ieframe_dll, "IEIsInPrivateBrowsing"));
+ if (IsInPrivate) {
+ is_inprivate = !!IsInPrivate();
+ }
+ }
+
+ inprivate_status_cached = true;
+ return is_inprivate;
+}
+
+} // namespace ie_util
diff --git a/ceee/ie/common/ie_util.h b/ceee/ie/common/ie_util.h
new file mode 100644
index 0000000..5d2a19c
--- /dev/null
+++ b/ceee/ie/common/ie_util.h
@@ -0,0 +1,45 @@
+// 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 functions to interact with IE.
+
+#ifndef CEEE_IE_COMMON_IE_UTIL_H_
+#define CEEE_IE_COMMON_IE_UTIL_H_
+
+#include <wtypes.h>
+
+#include <set>
+
+struct IWebBrowser2;
+
+namespace ie_util {
+
+// Returns the IWebBrowser2 interface associated to a given top level IE HWND.
+// but not the one passed into not_him (which could be NULL), since there are
+// some states where there are two web browser assigned to a single HWND.
+HRESULT GetWebBrowserForTopLevelIeHwnd(
+ HWND window, IWebBrowser2* not_him, IWebBrowser2** browser);
+
+// NOTE: Keep these in order so callers can do things like
+// "if (GetIeVersion() > IEVERSION_7) ...". It's OK to change the values,
+// though.
+enum IeVersion {
+ IEVERSION_UNKNOWN = -1,
+ IEVERSION_PRE_IE6 = 0, // Not supported
+ IEVERSION_IE6 = 1,
+ IEVERSION_IE7 = 2,
+ IEVERSION_IE8 = 3,
+ IEVERSION_IE9 = 4,
+};
+
+// Returns the IE version.
+// Returns true/false for success/failure.
+IeVersion GetIeVersion();
+
+// Returns true if IE is in InPrivate mode.
+bool GetIEIsInPrivateBrowsing();
+
+} // namespace ie_util
+
+#endif // CEEE_IE_COMMON_IE_UTIL_H_
diff --git a/ceee/ie/common/mock_ceee_module_util.h b/ceee/ie/common/mock_ceee_module_util.h
new file mode 100644
index 0000000..af70657
--- /dev/null
+++ b/ceee/ie/common/mock_ceee_module_util.h
@@ -0,0 +1,43 @@
+// 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.
+//
+// ceee_module_util mocks.
+
+#ifndef CEEE_IE_COMMON_MOCK_CEEE_MODULE_UTIL_H_
+#define CEEE_IE_COMMON_MOCK_CEEE_MODULE_UTIL_H_
+
+#include "ceee/ie/common/ceee_module_util.h"
+#include "ceee/testing/utils/mock_static.h"
+
+namespace testing {
+
+MOCK_STATIC_CLASS_BEGIN(MockCeeeModuleUtils)
+ MOCK_STATIC_INIT_BEGIN(MockCeeeModuleUtils)
+ MOCK_STATIC_INIT2(ceee_module_util::SetOptionToolbandIsHidden,
+ SetOptionToolbandIsHidden);
+ MOCK_STATIC_INIT2(ceee_module_util::GetOptionToolbandIsHidden,
+ GetOptionToolbandIsHidden);
+ MOCK_STATIC_INIT2(ceee_module_util::SetOptionToolbandForceReposition,
+ SetOptionToolbandForceReposition);
+ MOCK_STATIC_INIT2(ceee_module_util::GetOptionToolbandForceReposition,
+ GetOptionToolbandForceReposition);
+ MOCK_STATIC_INIT2(ceee_module_util::SetIgnoreShowDWChanges,
+ SetIgnoreShowDWChanges);
+ MOCK_STATIC_INIT2(ceee_module_util::GetIgnoreShowDWChanges,
+ GetIgnoreShowDWChanges);
+ MOCK_STATIC_INIT2(ceee_module_util::GetExtensionPath,
+ GetExtensionPath);
+ MOCK_STATIC_INIT_END()
+ MOCK_STATIC1(void, , SetOptionToolbandIsHidden, bool);
+ MOCK_STATIC0(bool, , GetOptionToolbandIsHidden);
+ MOCK_STATIC1(void, , SetOptionToolbandForceReposition, bool);
+ MOCK_STATIC0(bool, , GetOptionToolbandForceReposition);
+ MOCK_STATIC1(void, , SetIgnoreShowDWChanges, bool);
+ MOCK_STATIC0(bool, , GetIgnoreShowDWChanges);
+ MOCK_STATIC0(std::wstring, , GetExtensionPath);
+MOCK_STATIC_CLASS_END(MockCeeeModuleUtils)
+
+} // namespace testing
+
+#endif // CEEE_IE_COMMON_MOCK_CEEE_MODULE_UTIL_H_
diff --git a/ceee/ie/common/mock_ie_tab_interfaces.h b/ceee/ie/common/mock_ie_tab_interfaces.h
new file mode 100644
index 0000000..2a7fe22
--- /dev/null
+++ b/ceee/ie/common/mock_ie_tab_interfaces.h
@@ -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.
+//
+// ie_tab_interfaces mocks.
+
+#ifndef CEEE_IE_COMMON_MOCK_IE_TAB_INTERFACES_H_
+#define CEEE_IE_COMMON_MOCK_IE_TAB_INTERFACES_H_
+
+#include <atlbase.h>
+#include <atlcom.h>
+
+#include "ceee/ie/common/ie_tab_interfaces.h"
+#include "ceee/testing/utils/mock_static.h"
+
+namespace testing {
+
+MOCK_STATIC_CLASS_BEGIN(MockIeTabInterfaces)
+ MOCK_STATIC_INIT_BEGIN(MockIeTabInterfaces)
+ MOCK_STATIC_INIT2(ie_tab_interfaces::TabWindowManagerFromFrame,
+ TabWindowManagerFromFrame)
+ MOCK_STATIC_INIT_END()
+
+ MOCK_STATIC3(HRESULT, , TabWindowManagerFromFrame, HWND, REFIID, void **)
+MOCK_STATIC_CLASS_END(MockIeTabInterfaces)
+
+class MockITabWindowManagerIe7
+ : public CComObjectRootEx<CComSingleThreadModel>,
+ public ITabWindowManagerIe7 {
+ public:
+ DECLARE_NOT_AGGREGATABLE(MockITabWindowManagerIe7)
+ BEGIN_COM_MAP(MockITabWindowManagerIe7)
+ COM_INTERFACE_ENTRY(ITabWindowManagerIe7)
+ END_COM_MAP()
+ DECLARE_PROTECT_FINAL_CONSTRUCT()
+
+ MOCK_METHOD4_WITH_CALLTYPE(__stdcall, AddTab,
+ HRESULT(LPCITEMIDLIST, UINT, ULONG, long*));
+ MOCK_METHOD1_WITH_CALLTYPE(__stdcall, SelectTab, HRESULT(long));
+ MOCK_METHOD0_WITH_CALLTYPE(__stdcall, CloseAllTabs, HRESULT());
+ MOCK_METHOD1_WITH_CALLTYPE(__stdcall, GetActiveTab, HRESULT(IUnknown**));
+ MOCK_METHOD1_WITH_CALLTYPE(__stdcall, GetCount, HRESULT(long*));
+ MOCK_METHOD2_WITH_CALLTYPE(__stdcall, GetItem, HRESULT(long, IUnknown**));
+ MOCK_METHOD2_WITH_CALLTYPE(__stdcall, IndexFromID, HRESULT(long, long*));
+ MOCK_METHOD2_WITH_CALLTYPE(__stdcall, IndexFromHWND, HRESULT(HWND, long*));
+ MOCK_METHOD1_WITH_CALLTYPE(__stdcall, GetBrowserFrame, HRESULT(IUnknown**));
+ MOCK_METHOD2_WITH_CALLTYPE(__stdcall, AddBlankTab,
+ HRESULT(unsigned long, long*));
+ MOCK_METHOD3_WITH_CALLTYPE(__stdcall, AddTabGroup,
+ HRESULT(LPCITEMIDLIST*, long, unsigned long));
+ MOCK_METHOD3_WITH_CALLTYPE(__stdcall, GetCurrentTabGroup,
+ HRESULT(LPCITEMIDLIST**, long*, long*));
+ MOCK_METHOD1_WITH_CALLTYPE(__stdcall, OpenHomePages, HRESULT(int));
+ MOCK_METHOD3_WITH_CALLTYPE(__stdcall, RepositionTab,
+ HRESULT(long, long, int));
+};
+
+class MockITabWindowIe7
+ : public CComObjectRootEx<CComSingleThreadModel>,
+ public ITabWindowIe7 {
+ public:
+ DECLARE_NOT_AGGREGATABLE(MockITabWindowIe7)
+ BEGIN_COM_MAP(MockITabWindowIe7)
+ COM_INTERFACE_ENTRY(ITabWindowIe7)
+ END_COM_MAP()
+ DECLARE_PROTECT_FINAL_CONSTRUCT()
+
+ MOCK_METHOD1_WITH_CALLTYPE(__stdcall, GetID, HRESULT(long*));
+ MOCK_METHOD0_WITH_CALLTYPE(__stdcall, Close, HRESULT());
+ MOCK_METHOD5_WITH_CALLTYPE(__stdcall, AsyncExec,
+ HRESULT(REFGUID, DWORD, DWORD, VARIANT*, VARIANT*));
+ MOCK_METHOD1_WITH_CALLTYPE(__stdcall, GetTabWindowManager,
+ HRESULT(ITabWindowManagerIe7**));
+ MOCK_METHOD6_WITH_CALLTYPE(__stdcall, OnBrowserCreated,
+ HRESULT(int, int, int, int, int, void*));
+ MOCK_METHOD2_WITH_CALLTYPE(__stdcall, OnNewWindow,
+ HRESULT(ULONG, IDispatch**));
+ MOCK_METHOD0_WITH_CALLTYPE(__stdcall, OnBrowserClosed, HRESULT());
+ MOCK_METHOD1_WITH_CALLTYPE(__stdcall, OnRequestAttention, HRESULT(int));
+ MOCK_METHOD2_WITH_CALLTYPE(__stdcall, FrameTranslateAccelerator,
+ HRESULT(MSG*, ULONG));
+ MOCK_METHOD2_WITH_CALLTYPE(__stdcall, SetTitle, HRESULT(LPCWSTR, int));
+ MOCK_METHOD2_WITH_CALLTYPE(__stdcall, SetIcon, HRESULT(HICON, int));
+ MOCK_METHOD2_WITH_CALLTYPE(__stdcall, SetStatusBarState, HRESULT(int, long));
+ MOCK_METHOD3_WITH_CALLTYPE(__stdcall, GetTitle, HRESULT(LPWSTR, ULONG, int));
+ MOCK_METHOD3_WITH_CALLTYPE(__stdcall, GetIcon, HRESULT(HICON*, int*, int));
+ MOCK_METHOD1_WITH_CALLTYPE(__stdcall, GetLocationPidl,
+ HRESULT(LPCITEMIDLIST*));
+ MOCK_METHOD1_WITH_CALLTYPE(__stdcall, GetNavigationState, HRESULT(ULONG*));
+ MOCK_METHOD3_WITH_CALLTYPE(__stdcall, GetProgress,
+ HRESULT(int*, long*, long*));
+ MOCK_METHOD1_WITH_CALLTYPE(__stdcall, GetFlags, HRESULT(ULONG*));
+ MOCK_METHOD1_WITH_CALLTYPE(__stdcall, GetBrowser, HRESULT(IDispatch**));
+ MOCK_METHOD1_WITH_CALLTYPE(__stdcall, GetBrowserToolbarWindow,
+ HRESULT(HWND*));
+ MOCK_METHOD1_WITH_CALLTYPE(__stdcall, GetSearchState, HRESULT(int*));
+ MOCK_METHOD1_WITH_CALLTYPE(__stdcall, GetAttentionState, HRESULT(int*));
+ MOCK_METHOD0_WITH_CALLTYPE(__stdcall, ResampleImageAsync, HRESULT());
+ MOCK_METHOD1_WITH_CALLTYPE(__stdcall, OnTabImageResampled, HRESULT(HBITMAP));
+ MOCK_METHOD2_WITH_CALLTYPE(__stdcall, GetStatusBarState,
+ HRESULT(int*, long*));
+};
+
+} // namespace testing
+
+#endif // CEEE_IE_COMMON_MOCK_IE_TAB_INTERFACES_H_
diff --git a/ceee/ie/common/precompile.cc b/ceee/ie/common/precompile.cc
new file mode 100644
index 0000000..46a23d0
--- /dev/null
+++ b/ceee/ie/common/precompile.cc
@@ -0,0 +1,6 @@
+// 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.
+//
+// Precompile generator file.
+#include "ceee/ie/common/precompile.h"
diff --git a/ceee/ie/common/precompile.h b/ceee/ie/common/precompile.h
new file mode 100644
index 0000000..5a934ee
--- /dev/null
+++ b/ceee/ie/common/precompile.h
@@ -0,0 +1,14 @@
+// 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.
+//
+// Precompile header for CEEE.
+
+#ifndef CEEE_IE_COMMON_PRECOMPILE_H_
+#define CEEE_IE_COMMON_PRECOMPILE_H_
+
+#include <atlbase.h>
+#include <atlcom.h>
+#include <atlstr.h>
+
+#endif // CEEE_IE_COMMON_PRECOMPILE_H_
diff --git a/ceee/ie/ie.gyp b/ceee/ie/ie.gyp
new file mode 100644
index 0000000..e2ea88e
--- /dev/null
+++ b/ceee/ie/ie.gyp
@@ -0,0 +1,146 @@
+# 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.
+
+{
+ 'targets': [
+ {
+ 'target_name': 'ceee_ie_all',
+ 'type': 'none',
+ 'dependencies': [
+ 'common/common.gyp:*',
+ 'broker/broker.gyp:*',
+ 'plugin/bho/bho.gyp:*',
+ 'plugin/scripting/scripting.gyp:*',
+ 'plugin/toolband/toolband.gyp:*',
+ 'ie_unittests',
+ 'mediumtest_ie',
+ ]
+ },
+ {
+ 'target_name': 'ie_unittests',
+ 'type': 'executable',
+ 'sources': [
+ 'broker/api_dispatcher_unittest.cc',
+ 'broker/broker_unittest.cc',
+ 'broker/cookie_api_module_unittest.cc',
+ 'broker/executors_manager_unittest.cc',
+ 'broker/infobar_api_module_unittest.cc',
+ 'broker/tab_api_module_unittest.cc',
+ 'broker/window_api_module_unittest.cc',
+ 'broker/window_events_funnel_unittest.cc',
+ 'common/chrome_frame_host_unittest.cc',
+ 'common/crash_reporter_unittest.cc',
+ 'common/extension_manifest_unittest.cc',
+ 'common/ceee_module_util_unittest.cc',
+ 'plugin/bho/browser_helper_object_unittest.cc',
+ 'plugin/bho/cookie_accountant_unittest.cc',
+ 'plugin/bho/cookie_events_funnel_unittest.cc',
+ 'plugin/bho/dom_utils_unittest.cc',
+ 'plugin/bho/events_funnel_unittest.cc',
+ 'plugin/bho/executor_unittest.cc',
+ 'plugin/bho/extension_port_manager.cc',
+ 'plugin/bho/frame_event_handler_unittest.cc',
+ 'plugin/bho/infobar_events_funnel_unittest.cc',
+ 'plugin/bho/tab_events_funnel_unittest.cc',
+ 'plugin/bho/tool_band_visibility_unittest.cc',
+ 'plugin/bho/webnavigation_events_funnel_unittest.cc',
+ 'plugin/bho/webrequest_events_funnel_unittest.cc',
+ 'plugin/bho/webrequest_notifier_unittest.cc',
+ 'plugin/bho/web_progress_notifier_unittest.cc',
+ 'plugin/scripting/content_script_manager.rc',
+ 'plugin/scripting/content_script_manager_unittest.cc',
+ 'plugin/scripting/content_script_native_api_unittest.cc',
+ 'plugin/scripting/renderer_extension_bindings_unittest.cc',
+ 'plugin/scripting/renderer_extension_bindings_unittest.rc',
+ 'plugin/scripting/script_host_unittest.cc',
+ 'plugin/scripting/userscripts_librarian_unittest.cc',
+ 'plugin/toolband/tool_band_unittest.cc',
+ 'plugin/toolband/toolband_module_reporting_unittest.cc',
+ 'testing/ie_unittest_main.cc',
+ 'testing/mock_broker_and_friends.h',
+ 'testing/mock_chrome_frame_host.h',
+ 'testing/mock_browser_and_friends.h',
+ 'testing/precompile.cc',
+ 'testing/precompile.h',
+ ],
+ 'configurations': {
+ 'Debug': {
+ 'msvs_settings': {
+ 'VCCLCompilerTool': {
+ # GMock and GTest appear to be really fat, so bump
+ # precompile header memory setting to 332 megs.
+ 'AdditionalOptions': ['/Zm332', '/bigobj'],
+ },
+ },
+ 'msvs_precompiled_source': 'testing/precompile.cc',
+ 'msvs_precompiled_header': 'testing/precompile.h',
+ },
+ },
+ 'dependencies': [
+ 'common/common.gyp:ie_common',
+ 'common/common.gyp:ie_common_settings',
+ 'common/common.gyp:ie_guids',
+ 'broker/broker.gyp:broker',
+ 'plugin/bho/bho.gyp:bho',
+ 'plugin/scripting/scripting.gyp:javascript_bindings',
+ 'plugin/scripting/scripting.gyp:scripting',
+ 'plugin/toolband/toolband.gyp:ceee_ie_lib',
+ 'plugin/toolband/toolband.gyp:ie_toolband_common',
+ 'plugin/toolband/toolband.gyp:toolband_idl',
+ '../../base/base.gyp:base',
+ '../../breakpad/breakpad.gyp:breakpad_handler',
+ '../testing/sidestep/sidestep.gyp:sidestep',
+ '../testing/utils/test_utils.gyp:test_utils',
+ '../../testing/gmock.gyp:gmock',
+ '../../testing/gtest.gyp:gtest',
+ ],
+ 'libraries': [
+ 'oleacc.lib',
+ 'iepmapi.lib',
+ ],
+ },
+ {
+ 'target_name': 'mediumtest_ie',
+ 'type': 'executable',
+ 'sources': [
+ 'plugin/bho/mediumtest_browser_event.cc',
+ 'plugin/bho/mediumtest_browser_helper_object.cc',
+ 'testing/mediumtest_ie_common.cc',
+ 'testing/mediumtest_ie_common.h',
+ 'testing/mediumtest_ie_main.cc',
+ 'testing/precompile.cc',
+ 'testing/precompile.h',
+ ],
+ 'configurations': {
+ 'Debug': {
+ 'msvs_settings': {
+ 'VCCLCompilerTool': {
+ # GMock and GTest appear to be really fat, so bump
+ # precompile header memory setting to 332 megs.
+ 'AdditionalOptions': ['/Zm332'],
+ },
+ },
+ 'msvs_precompiled_source': 'testing/precompile.cc',
+ 'msvs_precompiled_header': 'testing/precompile.h',
+ },
+ },
+ 'dependencies': [
+ 'common/common.gyp:ie_common',
+ 'common/common.gyp:ie_common_settings',
+ 'common/common.gyp:ie_guids',
+ 'plugin/bho/bho.gyp:bho',
+ 'plugin/scripting/scripting.gyp:scripting',
+ 'plugin/toolband/toolband.gyp:toolband_idl',
+ '../../base/base.gyp:base',
+ '../../testing/gmock.gyp:gmock',
+ '../../testing/gtest.gyp:gtest',
+ '../testing/utils/test_utils.gyp:test_utils',
+ ],
+ 'libraries': [
+ 'oleacc.lib',
+ 'iepmapi.lib',
+ ],
+ },
+ ]
+}
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
diff --git a/ceee/ie/testing/ie_unittest_main.cc b/ceee/ie/testing/ie_unittest_main.cc
new file mode 100644
index 0000000..2317472
--- /dev/null
+++ b/ceee/ie/testing/ie_unittest_main.cc
@@ -0,0 +1,77 @@
+// 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.
+//
+// Main function for common unittests.
+#include <atlbase.h>
+#include <atlcom.h>
+#include "base/at_exit.h"
+#include "base/command_line.h"
+#include "base/logging.h"
+#include "ceee/testing/utils/gflag_utils.h"
+#include "chrome/common/url_constants.h"
+#include "gmock/gmock.h"
+#include "gtest/gtest.h"
+
+#include "toolband.h" // NOLINT
+
+// Stub for unittesting.
+namespace ceee_module_util {
+
+LONG LockModule() {
+ return 0;
+}
+LONG UnlockModule() {
+ return 0;
+}
+void Lock() {
+}
+void Unlock() {
+}
+void AddRefModuleWorkerThread() {
+}
+void ReleaseModuleWorkerThread() {
+}
+
+// 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) {
+ // Must get some work done so that the function can be mocked.
+ // Otherwise, it would be too short to be side stepped...
+ if (event_name == event_args) {
+ CHECK(event_name == event_args);
+ } else {
+ CHECK(event_name != event_args);
+ }
+}
+
+} // namespace ceee_module_util
+
+
+// We're testing ATL code that requires a module object.
+class ObligatoryModule: public CAtlDllModuleT<ObligatoryModule> {
+ public:
+ DECLARE_LIBID(LIBID_ToolbandLib);
+};
+
+ObligatoryModule g_obligatory_atl_module;
+
+// Run these tests under page heap.
+const DWORD kGFlags = FLG_USER_STACK_TRACE_DB | FLG_HEAP_PAGE_ALLOCS;
+
+int main(int argc, char **argv) {
+ // Disabled, bb2560934.
+ // if (!IsDebuggerPresent())
+ // testing::RelaunchWithGFlags(kTestGFlags);
+
+ base::AtExitManager at_exit;
+ CommandLine::Init(argc, argv);
+
+ // Needs to be called before we can use GURL.
+ chrome::RegisterChromeSchemes();
+
+ testing::InitGoogleMock(&argc, argv);
+ testing::InitGoogleTest(&argc, argv);
+ return RUN_ALL_TESTS();
+}
diff --git a/ceee/ie/testing/mediumtest_ie_common.cc b/ceee/ie/testing/mediumtest_ie_common.cc
new file mode 100644
index 0000000..1b2fd45
--- /dev/null
+++ b/ceee/ie/testing/mediumtest_ie_common.cc
@@ -0,0 +1,267 @@
+// 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 common test fixture for testing against a captive shell browser
+// control instance - which is as close to habitating the belly of the IE
+// beast as can be done with a modicum of safety and sanitation.
+#include "ceee/ie/testing/mediumtest_ie_common.h"
+
+#include <atlcrack.h>
+#include <atlsync.h>
+#include <atlwin.h>
+#include <exdisp.h>
+#include <exdispid.h>
+#include <mshtmdid.h>
+
+#include "base/logging.h"
+#include "base/file_path.h"
+#include "base/path_service.h"
+#include "base/base_paths_win.h"
+#include "base/utf_string_conversions.h"
+#include "gtest/gtest.h"
+#include "googleurl/src/gurl.h"
+#include "net/base/net_util.h"
+#include "ceee/common/com_utils.h"
+#include "ceee/common/initializing_coclass.h"
+#include "ceee/testing/utils/test_utils.h"
+#include "ceee/testing/utils/instance_count_mixin.h"
+
+
+namespace testing {
+
+// A test resource that's a simple single-resource page.
+const wchar_t* kSimplePage = L"simple_page.html";
+// A test resource that's a frameset referencing the two frames below.
+const wchar_t* kTwoFramesPage = L"two_frames.html";
+const wchar_t* kFrameOne = L"frame_one.html";
+const wchar_t* kFrameTwo = L"frame_two.html";
+// Another test resource that's a frameset referencing the two frames below.
+const wchar_t* kAnotherTwoFramesPage = L"another_two_frames.html";
+const wchar_t* kAnotherFrameOne = L"another_frame_one.html";
+const wchar_t* kAnotherFrameTwo = L"another_frame_two.html";
+
+// A test resource that's a top-level frameset with two nested iframes
+// the inner one of which refers frame_one.html.
+const wchar_t* kDeepFramesPage = L"deep_frames.html";
+const wchar_t* kLevelOneFrame = L"level_one_frame.html";
+const wchar_t* kLevelTwoFrame = L"level_two_frame.html";
+
+// A test resource that have javascript generate frames that look orphan.
+const wchar_t* kOrphansPage = L"orphans.html";
+
+std::set<READYSTATE> BrowserEventSinkBase::seen_states_;
+
+// Constructs a res: url to the test resource in our module.
+std::wstring GetTestUrl(const wchar_t* resource_name) {
+ FilePath path;
+ EXPECT_TRUE(PathService::Get(base::DIR_SOURCE_ROOT, &path));
+ path = path.Append(FILE_PATH_LITERAL("ceee"))
+ .Append(FILE_PATH_LITERAL("ie"))
+ .Append(FILE_PATH_LITERAL("testing"))
+ .Append(FILE_PATH_LITERAL("test_data"))
+ .Append(resource_name);
+
+ return UTF8ToWide(net::FilePathToFileURL(path).spec());
+}
+
+// Returns the path to our temp folder.
+std::wstring GetTempPath() {
+ FilePath temp;
+ EXPECT_TRUE(PathService::Get(base::DIR_TEMP, &temp));
+
+ return temp.value();
+}
+
+_ATL_FUNC_INFO handler_type_idispatch_ =
+ { CC_STDCALL, VT_EMPTY, 1, { VT_DISPATCH } };
+_ATL_FUNC_INFO handler_type_idispatch_variantptr_ =
+ { CC_STDCALL, VT_EMPTY, 2, { VT_DISPATCH, VT_BYREF | VT_VARIANT } };
+
+
+void BrowserEventSinkBase::GetDescription(std::string* description) const {
+ description->clear();
+ description->append("TestBrowserEventSink");
+}
+
+HRESULT BrowserEventSinkBase::Initialize(IWebBrowser2* browser) {
+ browser_ = browser;
+ HRESULT hr = DispEventAdvise(browser_);
+ if (SUCCEEDED(hr)) {
+ hr = AtlAdvise(browser_,
+ GetUnknown(),
+ IID_IPropertyNotifySink,
+ &prop_notify_cookie_);
+ }
+
+ return hr;
+}
+
+void BrowserEventSinkBase::TearDown() {
+ EXPECT_HRESULT_SUCCEEDED(DispEventUnadvise(browser_));
+ EXPECT_HRESULT_SUCCEEDED(AtlUnadvise(browser_,
+ IID_IPropertyNotifySink,
+ prop_notify_cookie_));
+}
+
+STDMETHODIMP BrowserEventSinkBase::OnChanged(DISPID changed_property) {
+ ATLTRACE("OnChanged(%d)\n", changed_property);
+
+ READYSTATE ready_state = READYSTATE_UNINITIALIZED;
+ browser_->get_ReadyState(&ready_state);
+ ATLTRACE("READYSTATE: %d\n", ready_state);
+ seen_states_.insert(ready_state);
+
+ return S_OK;
+}
+
+STDMETHODIMP BrowserEventSinkBase::OnRequestEdit(DISPID changed_property) {
+ ATLTRACE("OnRequestEdit(%d)\n", changed_property);
+ return S_OK;
+}
+
+STDMETHODIMP_(void) BrowserEventSinkBase::OnNavigateComplete(
+ IDispatch* browser_disp, VARIANT* url_var) {
+}
+
+STDMETHODIMP_(void) BrowserEventSinkBase::OnDocumentComplete(
+ IDispatch* browser_disp, VARIANT* url) {
+}
+
+void ShellBrowserTestBase::SetUpTestCase() {
+ // CoInitialize(NULL) serves two purposes here:
+ // 1. if we're running in a non-initialized apartment, it initializes it.
+ // 2. we need to be in an STA to use the shell browser, and if we're
+ // for whatever reason running in an MTA, it will fail.
+ ASSERT_HRESULT_SUCCEEDED(::CoInitialize(NULL));
+ ASSERT_TRUE(AtlAxWinInit());
+}
+
+void ShellBrowserTestBase::TearDownTestCase() {
+ EXPECT_TRUE(AtlAxWinTerm());
+ ::CoUninitialize();
+}
+
+void ShellBrowserTestBase::SetUp() {
+ ASSERT_TRUE(Create(HWND_MESSAGE) != NULL);
+
+ // Create the webbrowser control and get the AX host.
+ ASSERT_HRESULT_SUCCEEDED(
+ AtlAxCreateControl(CComBSTR(CLSID_WebBrowser),
+ m_hWnd,
+ NULL,
+ &host_));
+
+ // Get the control's top-level IWebBrowser.
+ CComPtr<IUnknown> control;
+ ASSERT_HRESULT_SUCCEEDED(AtlAxGetControl(m_hWnd, &control));
+ ASSERT_HRESULT_SUCCEEDED(control->QueryInterface(&browser_));
+
+ ASSERT_HRESULT_SUCCEEDED(CreateEventSink(browser_));
+}
+
+void ShellBrowserTestBase::TearDown() {
+ // Navigating the browser to a folder creates a non-webbrowser document,
+ // which shakes off all our frame handlers.
+ EXPECT_TRUE(NavigateBrowser(GetTempPath()));
+
+ // Tear down the rest of our stuff.
+ if (event_sink_)
+ event_sink_->TearDown();
+
+ event_sink_keeper_.Release();
+ host_.Release();
+ browser_.Release();
+
+ // Should have retained no objects past this point.
+ EXPECT_EQ(0, InstanceCountMixinBase::all_instance_count());
+ if (InstanceCountMixinBase::all_instance_count() > 0) {
+ InstanceCountMixinBase::LogLeakedInstances();
+ }
+
+ // Finally blow away the host window.
+ if (IsWindow())
+ DestroyWindow();
+}
+
+bool ShellBrowserTestBase::NavigateBrowser(const std::wstring& url) {
+ CComVariant empty;
+ HRESULT hr = browser_->Navigate2(&CComVariant(url.c_str()),
+ &empty, &empty,
+ &empty, &empty);
+ EXPECT_HRESULT_SUCCEEDED(hr);
+ if (FAILED(hr))
+ return false;
+
+ return WaitForReadystateComplete();
+}
+
+bool ShellBrowserTestBase::WaitForReadystateComplete() {
+ return WaitForReadystate(READYSTATE_COMPLETE);
+}
+
+bool ShellBrowserTestBase::WaitForReadystateLoading() {
+ return WaitForReadystate(READYSTATE_LOADING);
+}
+
+bool ShellBrowserTestBase::WaitForReadystateWithTimerId(
+ READYSTATE waiting_for, UINT_PTR timer_id) {
+ while (true) {
+ if (!browser_ || !event_sink_)
+ return false;
+
+ // Is the browser in the required state now?
+ READYSTATE ready_state = READYSTATE_UNINITIALIZED;
+ HRESULT hr = browser_->get_ReadyState(&ready_state);
+ if (FAILED(hr))
+ return false;
+
+ if (ready_state == waiting_for) {
+ event_sink_->remove_state(waiting_for);
+ return true;
+ }
+
+ // Has the state been seen?
+ if (event_sink_->has_state(waiting_for)) {
+ event_sink_->remove_state(waiting_for);
+ return true;
+ }
+
+ MSG msg = {};
+ if (!GetMessage(&msg, 0, 0, 0)) {
+ // WM_QUIT.
+ return false;
+ }
+
+ if (msg.message == WM_TIMER &&
+ msg.hwnd == NULL &&
+ msg.wParam == timer_id) {
+ // Timeout.
+ return false;
+ }
+
+ ::TranslateMessage(&msg);
+ ::DispatchMessage(&msg);
+ }
+}
+
+bool ShellBrowserTestBase::WaitForReadystate(READYSTATE waiting_for) {
+ if (!event_sink_)
+ return false;
+
+ // Clear the seen states.
+ event_sink_->clear_states();
+
+ // Bound the wait by setting a timer.
+ UINT_PTR timer_id = ::SetTimer(NULL,
+ 0,
+ wait_timeout_ms_,
+ NULL);
+ EXPECT_NE(0, timer_id);
+ bool ret = WaitForReadystateWithTimerId(waiting_for, timer_id);
+ EXPECT_TRUE(::KillTimer(NULL, timer_id));
+
+ return ret;
+}
+
+} // namespace testing
diff --git a/ceee/ie/testing/mediumtest_ie_common.h b/ceee/ie/testing/mediumtest_ie_common.h
new file mode 100644
index 0000000..048e620
--- /dev/null
+++ b/ceee/ie/testing/mediumtest_ie_common.h
@@ -0,0 +1,204 @@
+// 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 common test fixture for testing against a captive shell browser
+// control instance - which is as close to habitating the belly of the IE
+// beast as can be done with a modicum of safety and sanitation.
+#ifndef CEEE_IE_TESTING_MEDIUMTEST_IE_COMMON_H_
+#define CEEE_IE_TESTING_MEDIUMTEST_IE_COMMON_H_
+
+#include <atlbase.h>
+#include <atlcrack.h>
+#include <atlwin.h>
+#include <exdisp.h>
+#include <exdispid.h>
+#include <mshtmdid.h>
+
+#include <set>
+#include <string>
+
+#include "base/logging.h"
+#include "base/file_path.h"
+#include "base/path_service.h"
+#include "base/base_paths_win.h"
+#include "gtest/gtest.h"
+#include "ceee/common/com_utils.h"
+#include "ceee/common/initializing_coclass.h"
+#include "ceee/testing/utils/test_utils.h"
+#include "ceee/testing/utils/instance_count_mixin.h"
+
+namespace testing {
+
+// A test resource that's a simple single-resource page.
+extern const wchar_t* kSimplePage;
+// A test resource that's a frameset referencing the two frames below.
+extern const wchar_t* kTwoFramesPage;
+extern const wchar_t* kFrameOne;
+extern const wchar_t* kFrameTwo;
+
+// Another copy of above on new URLs.
+extern const wchar_t* kAnotherTwoFramesPage;
+extern const wchar_t* kAnotherFrameOne;
+extern const wchar_t* kAnotherFrameTwo;
+
+// A test resource that's a top-level frameset with two nested iframes
+// the inner one of which refers frame_one.html.
+extern const wchar_t* kDeepFramesPage;
+extern const wchar_t* kLevelOneFrame;
+extern const wchar_t* kLevelTwoFrame;
+
+// A test resource that have javascript generate frames that look orphan.
+extern const wchar_t* kOrphansPage;
+
+// Constructs a file: url to the test file our source directory.
+std::wstring GetTestUrl(const wchar_t* resource_name);
+
+// Returns the path to our temp folder.
+std::wstring GetTempPath();
+
+extern _ATL_FUNC_INFO handler_type_idispatch_;
+extern _ATL_FUNC_INFO handler_type_idispatch_variantptr_;
+
+// Base class that implements the rudiments of sinking events from
+// an IWebBrowser.
+class BrowserEventSinkBase
+ : public CComObjectRootEx<CComSingleThreadModel>,
+ public InitializingCoClass<BrowserEventSinkBase>,
+ public InstanceCountMixin<BrowserEventSinkBase>,
+ public IDispEventSimpleImpl<0,
+ BrowserEventSinkBase,
+ &DIID_DWebBrowserEvents2>,
+ public IPropertyNotifySink {
+ public:
+ BEGIN_COM_MAP(BrowserEventSinkBase)
+ COM_INTERFACE_ENTRY(IPropertyNotifySink)
+ END_COM_MAP()
+
+ BEGIN_SINK_MAP(BrowserEventSinkBase)
+ SINK_ENTRY_INFO(0, DIID_DWebBrowserEvents2, DISPID_NAVIGATECOMPLETE2,
+ OnNavigateComplete, &handler_type_idispatch_variantptr_)
+ SINK_ENTRY_INFO(0, DIID_DWebBrowserEvents2, DISPID_DOCUMENTCOMPLETE,
+ OnDocumentComplete, &handler_type_idispatch_variantptr_)
+ END_SINK_MAP()
+
+ DECLARE_PROTECT_FINAL_CONSTRUCT();
+
+ BrowserEventSinkBase() : prop_notify_cookie_(-1) {
+ }
+
+ virtual void GetDescription(std::string* description) const;
+ HRESULT Initialize(IWebBrowser2* browser);
+
+ void TearDown();
+
+ // @name IPropertyNotifySink implementation.
+ // @{
+ STDMETHOD(OnChanged)(DISPID changed_property);
+ STDMETHOD(OnRequestEdit)(DISPID changed_property);
+ // @}
+
+ // @name DWebBrowserEvents event handler implementation.
+ // Override these as needed.
+ // @{
+ STDMETHOD_(void, OnNavigateComplete)(IDispatch* browser_disp,
+ VARIANT* url_var);
+ STDMETHOD_(void, OnDocumentComplete)(IDispatch* browser_disp, VARIANT* url);
+
+ static bool has_state(READYSTATE state) {
+ return seen_states_.find(state) != seen_states_.end();
+ }
+ static void add_state(READYSTATE state) {
+ seen_states_.insert(state);
+ }
+ static void remove_state(READYSTATE state) {
+ seen_states_.erase(state);
+ }
+ static void clear_states() {
+ seen_states_.clear();
+ }
+
+ private:
+ // This bitset records the readystate values seen in OnChange.
+ // The point of this is to make the WaitForReadyState function
+ // below work in the case where the browser takes multiple
+ // readystate changes while processing a single event. This
+ // will occur in the refresh case, where the top-level browser
+ // and individual document objects transition from COMPLETE to
+ // LOADING and then back to COMPLETE on processing a single event.
+ static std::set<READYSTATE> seen_states_;
+
+ // Advise cookie for property notify sink.
+ DWORD prop_notify_cookie_;
+
+ CComPtr<IWebBrowser2> browser_;
+};
+
+// Test fixture base class.
+class ShellBrowserTestBase
+ : public testing::Test,
+ public CWindowImpl<ShellBrowserTestBase> {
+ public:
+ // Default timeout for pumping events.
+ static const UINT kWaitTimeoutMs = 2000;
+
+ ShellBrowserTestBase()
+ : wait_timeout_ms_(kWaitTimeoutMs) {
+ }
+
+ static void SetUpTestCase();
+ static void TearDownTestCase();
+
+ void SetUp();
+ void TearDown();
+
+ // Navigates the browser to the given URL and waits for
+ // the top-level browser's readystate to reach complete.
+ bool NavigateBrowser(const std::wstring& url);
+
+ // Wait for the top-level browser's readystate to reach
+ // READYSTATE_COMPLETE, READYSTATE_LOADING or wait_for,
+ // respectively.
+ bool WaitForReadystateComplete();
+ bool WaitForReadystateLoading();
+ bool WaitForReadystate(READYSTATE wait_for);
+
+ BEGIN_MSG_MAP_EX(BrowserEventTest)
+ END_MSG_MAP()
+
+ protected:
+ bool WaitForReadystateWithTimerId(READYSTATE waiting_for, UINT_PTR timer_id);
+ virtual HRESULT CreateEventSink(IWebBrowser2* browser) = 0;
+
+ // Timeout for pumping messages in WaitForReadyState.
+ UINT wait_timeout_ms_;
+
+ CComPtr<IUnknown> host_;
+ CComPtr<IWebBrowser2> browser_;
+
+ BrowserEventSinkBase* event_sink_;
+ CComPtr<IUnknown> event_sink_keeper_;
+};
+
+template <class EventSinkImpl>
+class ShellBrowserTestImpl: public ShellBrowserTestBase {
+ public:
+ protected:
+ virtual HRESULT CreateEventSink(IWebBrowser2* browser) {
+ EventSinkImpl* sink;
+ HRESULT hr = EventSinkImpl::CreateInitialized(&sink,
+ browser,
+ &event_sink_keeper_);
+ event_sink_ = sink;
+ return hr;
+ }
+
+ // Handy accessor to retrieve the event sink by actual type.
+ EventSinkImpl* event_sink() const {
+ return static_cast<EventSinkImpl*>(event_sink_);
+ }
+};
+
+} // namespace testing
+
+#endif // CEEE_IE_TESTING_MEDIUMTEST_IE_COMMON_H_
diff --git a/ceee/ie/testing/mediumtest_ie_main.cc b/ceee/ie/testing/mediumtest_ie_main.cc
new file mode 100644
index 0000000..b79fe46
--- /dev/null
+++ b/ceee/ie/testing/mediumtest_ie_main.cc
@@ -0,0 +1,68 @@
+// 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.
+//
+// Main function for large IE tests.
+#include <atlbase.h>
+#include <atlcom.h>
+#include "base/at_exit.h"
+#include "base/command_line.h"
+#include "ceee/testing/utils/gflag_utils.h"
+#include "ceee/testing/utils/nt_internals.h"
+#include "chrome/common/url_constants.h"
+#include "gmock/gmock.h"
+#include "gtest/gtest.h"
+
+
+// Stub for unittesting.
+namespace ceee_module_util {
+
+LONG LockModule() {
+ return 0;
+}
+LONG UnlockModule() {
+ return 0;
+}
+void Lock() {
+}
+void Unlock() {
+}
+void AddRefModuleWorkerThread() {
+}
+void ReleaseModuleWorkerThread() {
+}
+void FireEventToBroker(const std::string& event_name,
+ const std::string& event_args) {
+}
+
+} // namespace ceee_module_util
+
+
+// We're testing some ATL code that requires a module object.
+class ObligatoryModule: public CAtlDllModuleT<ObligatoryModule> {
+};
+
+ObligatoryModule g_obligatory_atl_module;
+
+const DWORD kTestGFlags = FLG_USER_STACK_TRACE_DB |
+ FLG_ENABLE_HANDLE_EXCEPTIONS |
+ FLG_ENABLE_CLOSE_EXCEPTIONS |
+ FLG_HEAP_VALIDATE_PARAMETERS;
+
+int main(int argc, char **argv) {
+ // Disabled, bb2560934.
+ // if (!IsDebuggerPresent())
+ // testing::RelaunchWithGFlags(kTestGFlags);
+
+ testing::InitGoogleMock(&argc, argv);
+ testing::InitGoogleTest(&argc, argv);
+
+ // Obligatory Chrome base initialization.
+ CommandLine::Init(argc, argv);
+ base::AtExitManager at_exit_manager;
+
+ // Needs to be called before we can use GURL.
+ chrome::RegisterChromeSchemes();
+
+ return RUN_ALL_TESTS();
+}
diff --git a/ceee/ie/testing/mock_broker_and_friends.h b/ceee/ie/testing/mock_broker_and_friends.h
new file mode 100644
index 0000000..7c02845
--- /dev/null
+++ b/ceee/ie/testing/mock_broker_and_friends.h
@@ -0,0 +1,359 @@
+// 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.
+//
+// Mock implementation of Broker and related objects.
+#ifndef CEEE_IE_TESTING_MOCK_BROKER_AND_FRIENDS_H_
+#define CEEE_IE_TESTING_MOCK_BROKER_AND_FRIENDS_H_
+
+#include <string>
+
+#include "ceee/ie/broker/api_dispatcher.h"
+#include "ceee/ie/plugin/bho/cookie_events_funnel.h"
+#include "ceee/ie/plugin/bho/tab_events_funnel.h"
+#include "ceee/ie/plugin/bho/webnavigation_events_funnel.h"
+#include "ceee/ie/plugin/bho/webrequest_events_funnel.h"
+#include "gmock/gmock.h"
+#include "ceee/common/initializing_coclass.h"
+#include "ceee/testing/utils/instance_count_mixin.h"
+
+#include "broker_lib.h" // NOLINT
+#include "toolband.h" // NOLINT
+
+namespace testing {
+
+class MockBrokerImpl : public ICeeeBroker {
+ public:
+ MOCK_METHOD2_WITH_CALLTYPE(__stdcall, Execute, HRESULT(BSTR, BSTR*));
+ MOCK_METHOD2_WITH_CALLTYPE(__stdcall, FireEvent, HRESULT(BSTR, BSTR));
+};
+
+class MockBrokerRegistrarImpl : public ICeeeBrokerRegistrar {
+ public:
+ MOCK_METHOD2_WITH_CALLTYPE(__stdcall, RegisterWindowExecutor,
+ HRESULT(long, IUnknown*));
+ MOCK_METHOD1_WITH_CALLTYPE(__stdcall, UnregisterExecutor, HRESULT(long));
+ MOCK_METHOD2_WITH_CALLTYPE(__stdcall, RegisterTabExecutor,
+ HRESULT(long, IUnknown*));
+ MOCK_METHOD2_WITH_CALLTYPE(__stdcall, SetTabIdForHandle,
+ HRESULT(long, CeeeWindowHandle));
+};
+
+class MockBroker
+ : public CComObjectRootEx<CComSingleThreadModel>,
+ public InitializingCoClass<MockBroker>,
+ public InstanceCountMixin<MockBroker>,
+ public StrictMock<MockBrokerRegistrarImpl>,
+ public StrictMock<MockBrokerImpl> {
+ public:
+ BEGIN_COM_MAP(MockBroker)
+ COM_INTERFACE_ENTRY(ICeeeBrokerRegistrar)
+ COM_INTERFACE_ENTRY(ICeeeBroker)
+ END_COM_MAP()
+
+ HRESULT Initialize(MockBroker** self) {
+ *self = this;
+ return S_OK;
+ }
+};
+
+class MockExecutorIUnknown
+ : public CComObjectRootEx<CComSingleThreadModel>,
+ public InitializingCoClass<MockExecutorIUnknown>,
+ public InstanceCountMixin<MockExecutorIUnknown>,
+ public IObjectWithSiteImpl<MockExecutorIUnknown> {
+ public:
+ BEGIN_COM_MAP(MockExecutorIUnknown)
+ COM_INTERFACE_ENTRY(IObjectWithSite)
+ END_COM_MAP()
+
+ HRESULT Initialize(MockExecutorIUnknown** self) {
+ *self = this;
+ return S_OK;
+ }
+};
+
+class MockCeeeWindowExecutorImpl : public ICeeeWindowExecutor {
+ public:
+ MOCK_METHOD1_WITH_CALLTYPE(__stdcall, Initialize, HRESULT(CeeeWindowHandle));
+ MOCK_METHOD2_WITH_CALLTYPE(__stdcall, GetWindow,
+ HRESULT(BOOL, CeeeWindowInfo*));
+ MOCK_METHOD1_WITH_CALLTYPE(__stdcall, GetTabs, HRESULT(BSTR*));
+ MOCK_METHOD5_WITH_CALLTYPE(__stdcall, UpdateWindow, HRESULT(long, long,
+ long, long, CeeeWindowInfo*));
+ MOCK_METHOD0_WITH_CALLTYPE(__stdcall, RemoveWindow, HRESULT());
+ MOCK_METHOD2_WITH_CALLTYPE(__stdcall, GetTabIndex, HRESULT(CeeeWindowHandle,
+ long*));
+ MOCK_METHOD2_WITH_CALLTYPE(__stdcall, MoveTab, HRESULT(CeeeWindowHandle,
+ long));
+ MOCK_METHOD1_WITH_CALLTYPE(__stdcall, RemoveTab, HRESULT(CeeeWindowHandle));
+ MOCK_METHOD1_WITH_CALLTYPE(__stdcall, SelectTab, HRESULT(CeeeWindowHandle));
+};
+
+class MockWindowExecutor
+ : public CComObjectRootEx<CComSingleThreadModel>,
+ public InitializingCoClass<MockWindowExecutor>,
+ public InstanceCountMixin<MockWindowExecutor>,
+ public IObjectWithSiteImpl<MockWindowExecutor>,
+ public StrictMock<MockCeeeWindowExecutorImpl> {
+ public:
+ BEGIN_COM_MAP(MockWindowExecutor)
+ COM_INTERFACE_ENTRY(ICeeeWindowExecutor)
+ COM_INTERFACE_ENTRY(IObjectWithSite)
+ END_COM_MAP()
+
+ HRESULT Initialize(MockWindowExecutor** self) {
+ *self = this;
+ return S_OK;
+ }
+};
+
+class MockCeeeTabExecutorImpl : public ICeeeTabExecutor {
+ public:
+ MOCK_METHOD1_WITH_CALLTYPE(__stdcall, Initialize, HRESULT(CeeeWindowHandle));
+ MOCK_METHOD1_WITH_CALLTYPE(__stdcall, GetTabInfo,
+ HRESULT(CeeeTabInfo*));
+ MOCK_METHOD3_WITH_CALLTYPE(__stdcall, Navigate, HRESULT(BSTR, long, BSTR));
+ MOCK_METHOD4_WITH_CALLTYPE(__stdcall, InsertCode, HRESULT(
+ BSTR, BSTR, BOOL, CeeeTabCodeType));
+ MOCK_METHOD1_WITH_CALLTYPE(__stdcall, PopupizeFrameWindow, HRESULT(long));
+};
+
+class MockTabExecutor
+ : public CComObjectRootEx<CComSingleThreadModel>,
+ public InitializingCoClass<MockTabExecutor>,
+ public InstanceCountMixin<MockTabExecutor>,
+ public IObjectWithSiteImpl<MockTabExecutor>,
+ public StrictMock<MockCeeeTabExecutorImpl> {
+ public:
+ BEGIN_COM_MAP(MockTabExecutor)
+ COM_INTERFACE_ENTRY(ICeeeTabExecutor)
+ COM_INTERFACE_ENTRY(IObjectWithSite)
+ END_COM_MAP()
+
+ HRESULT Initialize(MockTabExecutor** self) {
+ *self = this;
+ return S_OK;
+ }
+};
+
+class MockTabEventsFunnel : public TabEventsFunnel {
+ public:
+ MOCK_METHOD4(OnMoved, HRESULT(HWND tab, int window_id, int from_index,
+ int to_index));
+ MOCK_METHOD1(OnRemoved, HRESULT(HWND tab));
+ MOCK_METHOD2(OnSelectionChanged, HRESULT(HWND tab, int window_id));
+ MOCK_METHOD3(OnCreated, HRESULT(HWND tab, BSTR url, bool completed));
+ MOCK_METHOD3(OnUpdated, HRESULT(HWND tab, BSTR url,
+ READYSTATE ready_state));
+ MOCK_METHOD2(OnTabUnmapped, HRESULT(HWND tab, int tab_id));
+};
+
+class MockCeeeCookieExecutorImpl : public ICeeeCookieExecutor {
+ public:
+ MOCK_METHOD3_WITH_CALLTYPE(__stdcall, GetCookie,
+ HRESULT(BSTR, BSTR, CeeeCookieInfo*));
+ MOCK_METHOD0_WITH_CALLTYPE(__stdcall, RegisterCookieStore, HRESULT());
+ MOCK_METHOD0_WITH_CALLTYPE(__stdcall, CookieStoreIsRegistered, HRESULT());
+};
+
+class MockCookieExecutor
+ : public CComObjectRootEx<CComSingleThreadModel>,
+ public InitializingCoClass<MockCookieExecutor>,
+ public InstanceCountMixin<MockCookieExecutor>,
+ public IObjectWithSiteImpl<MockCookieExecutor>,
+ public StrictMock<MockCeeeCookieExecutorImpl> {
+ public:
+ BEGIN_COM_MAP(MockCookieExecutor)
+ COM_INTERFACE_ENTRY(ICeeeCookieExecutor)
+ COM_INTERFACE_ENTRY(IObjectWithSite)
+ END_COM_MAP()
+
+ HRESULT Initialize(MockCookieExecutor** self) {
+ *self = this;
+ return S_OK;
+ }
+};
+
+class MockCookieEventsFunnel : public CookieEventsFunnel {
+ public:
+ MOCK_METHOD2(OnChanged, HRESULT(bool removed,
+ const cookie_api::CookieInfo& cookie));
+};
+
+class MockCeeeInfobarExecutorImpl : public ICeeeInfobarExecutor {
+ public:
+ MOCK_METHOD1_WITH_CALLTYPE(__stdcall, SetExtensionId, HRESULT(BSTR));
+ MOCK_METHOD2_WITH_CALLTYPE(__stdcall, ShowInfobar,
+ HRESULT(BSTR, CeeeWindowHandle*));
+ MOCK_METHOD1_WITH_CALLTYPE(__stdcall, OnTopFrameBeforeNavigate,
+ HRESULT(BSTR));
+};
+
+class MockInfobarExecutor
+ : public CComObjectRootEx<CComSingleThreadModel>,
+ public InitializingCoClass<MockInfobarExecutor>,
+ public InstanceCountMixin<MockInfobarExecutor>,
+ public IObjectWithSiteImpl<MockInfobarExecutor>,
+ public StrictMock<MockCeeeInfobarExecutorImpl> {
+ public:
+ BEGIN_COM_MAP(MockInfobarExecutor)
+ COM_INTERFACE_ENTRY(ICeeeInfobarExecutor)
+ COM_INTERFACE_ENTRY(IObjectWithSite)
+ END_COM_MAP()
+
+ HRESULT Initialize(MockInfobarExecutor** self) {
+ *self = this;
+ return S_OK;
+ }
+};
+
+class MockTabInfobarExecutor
+ : public CComObjectRootEx<CComSingleThreadModel>,
+ public InitializingCoClass<MockTabInfobarExecutor>,
+ public InstanceCountMixin<MockTabInfobarExecutor>,
+ public IObjectWithSiteImpl<MockTabInfobarExecutor>,
+ public StrictMock<MockCeeeTabExecutorImpl>,
+ public StrictMock<MockCeeeInfobarExecutorImpl> {
+ public:
+ BEGIN_COM_MAP(MockTabInfobarExecutor)
+ COM_INTERFACE_ENTRY(ICeeeTabExecutor)
+ COM_INTERFACE_ENTRY(ICeeeInfobarExecutor)
+ COM_INTERFACE_ENTRY(IObjectWithSite)
+ END_COM_MAP()
+
+ HRESULT Initialize(MockTabInfobarExecutor** self) {
+ *self = this;
+ return S_OK;
+ }
+};
+
+class MockApiDispatcher : public ApiDispatcher {
+ public:
+ MOCK_METHOD2(HandleApiRequest, void(BSTR, BSTR*));
+ MOCK_METHOD2(RegisterInvocation, void(const char* function_name,
+ InvocationFactory factory));
+ MOCK_METHOD3(RegisterEphemeralEventHandler,
+ void(const char*, EphemeralEventHandler, InvocationResult*));
+ MOCK_METHOD3(GetExecutor, void(HWND, REFIID, void**));
+ MOCK_METHOD2(FireEvent, void(BSTR event_name, BSTR event_args));
+
+ MOCK_CONST_METHOD1(GetTabHandleFromId, HWND(int));
+ MOCK_CONST_METHOD1(GetWindowHandleFromId, HWND(int));
+ MOCK_CONST_METHOD1(GetTabIdFromHandle, int(HWND));
+ MOCK_CONST_METHOD1(GetWindowIdFromHandle, int(HWND));
+};
+
+// A mock class for an API invocation class that derives from ApiResultCreator,
+// defined in ceee\ie\broker\api_dispatcher.h. This class enables the injection
+// of a mock API result instance when the API is invoked.
+template<class ResultType, class MockResultType, class BaseClass>
+class MockApiInvocation : public BaseClass {
+ public:
+ MockApiInvocation() : request_id_(kRequestId) {}
+
+ // Calls the ContinueExecution method of the base class, using the mock
+ // invocation result and mock API dispatcher.
+ HRESULT CallContinueExecution(const std::string& input_args) {
+ HRESULT hr = ContinueExecution(input_args, invocation_result_.get(),
+ GetDispatcher());
+
+ // NOTE: the ContinueExecution method has already deleted the invocation
+ // result if its return is not S_FALSE. In that case, we have to release
+ // the pointer so that we don't delete the same object twice.
+ if (hr != S_FALSE)
+ invocation_result_.release();
+
+ return hr;
+ }
+
+ // We need to create the results before we get asked for one
+ // so that we can set expectations on it. And we need to allocate them
+ // because the callers of CreateApiResult take ownership of the memory.
+ void AllocateNewResult(int request_id) {
+ request_id_ = request_id;
+ invocation_result_.reset(new StrictMock<MockResultType>(request_id));
+ }
+ virtual ResultType* CreateApiResult(int request_id) {
+ EXPECT_EQ(request_id, request_id_);
+ EXPECT_NE(static_cast<ResultType*>(NULL), invocation_result_.get());
+ return invocation_result_.release(); // The caller becomes the owner.
+ }
+ virtual ApiDispatcher* GetDispatcher() {
+ return &mock_api_dispatcher_;
+ }
+
+ // public so that the tests can set expectations on them.
+ scoped_ptr<StrictMock<MockResultType> > invocation_result_;
+ StrictMock<MockApiDispatcher> mock_api_dispatcher_;
+
+ private:
+ int request_id_;
+};
+
+class MockWebNavigationEventsFunnel : public WebNavigationEventsFunnel {
+ public:
+ MOCK_METHOD5(OnBeforeNavigate, HRESULT(CeeeWindowHandle tab_handle,
+ BSTR url,
+ int frame_id,
+ int request_id,
+ const base::Time& time_stamp));
+ MOCK_METHOD4(OnBeforeRetarget, HRESULT(CeeeWindowHandle source_tab_handle,
+ BSTR source_url,
+ BSTR target_url,
+ const base::Time& time_stamp));
+ MOCK_METHOD6(OnCommitted, HRESULT(CeeeWindowHandle tab_handle,
+ BSTR url,
+ int frame_id,
+ const char* transition_type,
+ const char* transition_qualifiers,
+ const base::Time& time_stamp));
+ MOCK_METHOD4(OnCompleted, HRESULT(CeeeWindowHandle tab_handle,
+ BSTR url,
+ int frame_id,
+ const base::Time& time_stamp));
+ MOCK_METHOD4(OnDOMContentLoaded, HRESULT(CeeeWindowHandle tab_handle,
+ BSTR url,
+ int frame_id,
+ const base::Time& time_stamp));
+ MOCK_METHOD5(OnErrorOccurred, HRESULT(CeeeWindowHandle tab_handle,
+ BSTR url,
+ int frame_id,
+ BSTR error,
+ const base::Time& time_stamp));
+};
+
+class MockWebRequestEventsFunnel : public WebRequestEventsFunnel {
+ public:
+ MOCK_METHOD5(OnBeforeRedirect, HRESULT(int request_id,
+ const wchar_t* url,
+ DWORD status_code,
+ const wchar_t* redirect_url,
+ const base::Time& time_stamp));
+ MOCK_METHOD6(OnBeforeRequest, HRESULT(int request_id,
+ const wchar_t* url,
+ const char* method,
+ CeeeWindowHandle tab_handle,
+ const char* type,
+ const base::Time& time_stamp));
+ MOCK_METHOD4(OnCompleted, HRESULT(int request_id,
+ const wchar_t* url,
+ DWORD status_code,
+ const base::Time& time_stamp));
+ MOCK_METHOD4(OnErrorOccurred, HRESULT(int request_id,
+ const wchar_t* url,
+ const wchar_t* error,
+ const base::Time& time_stamp));
+ MOCK_METHOD4(OnHeadersReceived, HRESULT(int request_id,
+ const wchar_t* url,
+ DWORD status_code,
+ const base::Time& time_stamp));
+ MOCK_METHOD4(OnRequestSent, HRESULT(int request_id,
+ const wchar_t* url,
+ const char* ip,
+ const base::Time& time_stamp));
+};
+
+} // namespace testing
+
+#endif // CEEE_IE_TESTING_MOCK_BROKER_AND_FRIENDS_H_
diff --git a/ceee/ie/testing/mock_browser_and_friends.h b/ceee/ie/testing/mock_browser_and_friends.h
new file mode 100644
index 0000000..76db63c
--- /dev/null
+++ b/ceee/ie/testing/mock_browser_and_friends.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.
+//
+// Implementation of TestBrowser and related objects.
+#ifndef CEEE_IE_TESTING_MOCK_BROWSER_AND_FRIENDS_H_
+#define CEEE_IE_TESTING_MOCK_BROWSER_AND_FRIENDS_H_
+
+#include <string>
+
+#include "gmock/gmock.h"
+#include "ceee/common/initializing_coclass.h"
+#include "ceee/testing/utils/instance_count_mixin.h"
+#include "ceee/testing/utils/mock_com.h"
+#include "ceee/testing/utils/test_utils.h"
+
+namespace testing {
+
+class MockIOleWindow : public IOleWindow {
+ public:
+ // Simple IOleWindow implementation here, no need for a MockXxxImpl.
+ MOCK_METHOD1_WITH_CALLTYPE(__stdcall, GetWindow, HRESULT(HWND* window));
+ MOCK_METHOD1_WITH_CALLTYPE(__stdcall, ContextSensitiveHelp, HRESULT(BOOL));
+};
+
+class TestBrowserSite
+ : public CComObjectRootEx<CComSingleThreadModel>,
+ public InstanceCountMixin<TestBrowserSite>,
+ public InitializingCoClass<TestBrowserSite>,
+ public IServiceProviderImpl<TestBrowserSite>,
+ public StrictMock<MockIOleWindow> {
+ public:
+ BEGIN_COM_MAP(TestBrowserSite)
+ COM_INTERFACE_ENTRY(IServiceProvider)
+ COM_INTERFACE_ENTRY(IOleWindow)
+ END_COM_MAP()
+
+ BEGIN_SERVICE_MAP(TestDumbSite)
+ if (browser_ && (guidService == SID_SWebBrowserApp ||
+ guidService == SID_SShellBrowser)) {
+ return browser_->QueryInterface(riid, ppvObject);
+ }
+ END_SERVICE_MAP()
+
+ HRESULT Initialize(TestBrowserSite** self) {
+ *self = this;
+ return S_OK;
+ }
+
+ public:
+ CComPtr<IDispatch> browser_;
+};
+
+class TestBrowser
+ : public CComObjectRootEx<CComSingleThreadModel>,
+ public InstanceCountMixin<TestBrowser>,
+ public InitializingCoClass<TestBrowser>,
+ public StrictMock<IWebBrowser2MockImpl>,
+ public IConnectionPointImpl<TestBrowser, &DIID_DWebBrowserEvents2>,
+ public IConnectionPointContainerImpl<TestBrowser> {
+ public:
+ typedef IConnectionPointImpl<TestBrowser, &DIID_DWebBrowserEvents2>
+ WebBrowserEvents;
+
+ BEGIN_COM_MAP(TestBrowser)
+ COM_INTERFACE_ENTRY(IDispatch)
+ COM_INTERFACE_ENTRY(IWebBrowser)
+ COM_INTERFACE_ENTRY(IWebBrowserApp)
+ COM_INTERFACE_ENTRY(IWebBrowser2)
+ COM_INTERFACE_ENTRY(IConnectionPointContainer)
+ END_COM_MAP()
+
+ BEGIN_CONNECTION_POINT_MAP(TestBrowser)
+ CONNECTION_POINT_ENTRY(DIID_DWebBrowserEvents2)
+ END_CONNECTION_POINT_MAP()
+
+ // Override from IConnectionPointContainerImpl.
+ STDMETHOD(FindConnectionPoint)(REFIID iid, IConnectionPoint** cp) {
+ typedef IConnectionPointContainerImpl<TestBrowser> CPC;
+
+ if (iid == DIID_DWebBrowserEvents2 && no_events_)
+ return CONNECT_E_NOCONNECTION;
+
+ return CPC::FindConnectionPoint(iid, cp);
+ }
+
+ void FireOnNavigateComplete(IDispatch* browser, VARIANT *url) {
+ CComVariant args[] = { 0, browser };
+ args[0].vt = VT_BYREF | VT_BSTR;
+ args[0].pvarVal = url;
+
+ testing::FireEvent(static_cast<WebBrowserEvents*>(this),
+ DISPID_NAVIGATECOMPLETE2,
+ arraysize(args),
+ args);
+ }
+
+ TestBrowser() : no_events_(false) {
+ }
+
+ HRESULT Initialize(TestBrowser** self) {
+ *self = this;
+ return S_OK;
+ }
+
+ public:
+ // If true, won't return the WebBrowserEvents connection point.
+ bool no_events_;
+};
+
+} // namespace testing
+
+#endif // CEEE_IE_TESTING_MOCK_BROWSER_AND_FRIENDS_H_
diff --git a/ceee/ie/testing/mock_chrome_frame_host.h b/ceee/ie/testing/mock_chrome_frame_host.h
new file mode 100644
index 0000000..8ef07ff
--- /dev/null
+++ b/ceee/ie/testing/mock_chrome_frame_host.h
@@ -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.
+//
+// Mock implementation of ChromeFrameHost.
+#ifndef CEEE_IE_TESTING_MOCK_CHROME_FRAME_HOST_H_
+#define CEEE_IE_TESTING_MOCK_CHROME_FRAME_HOST_H_
+
+#include <string>
+#include "ceee/ie/common/chrome_frame_host.h"
+#include "gmock/gmock.h"
+#include "ceee/common/initializing_coclass.h"
+#include "ceee/testing/utils/instance_count_mixin.h"
+
+namespace testing {
+
+class IChromeFrameHostMockImpl : public IChromeFrameHost {
+ public:
+ MOCK_METHOD0_WITH_CALLTYPE(__stdcall, SetAsChromeFrameMaster, void());
+ MOCK_METHOD1_WITH_CALLTYPE(__stdcall, SetChromeProfileName,
+ void(const wchar_t* chrome_profile_name));
+ MOCK_METHOD1_WITH_CALLTYPE(__stdcall, SetUrl, HRESULT(BSTR url));
+ MOCK_METHOD0_WITH_CALLTYPE(__stdcall, StartChromeFrame, HRESULT());
+ MOCK_METHOD2_WITH_CALLTYPE(__stdcall, PostMessage,
+ HRESULT(BSTR message, BSTR target));
+ MOCK_METHOD0_WITH_CALLTYPE(__stdcall, TearDown, HRESULT());
+ MOCK_METHOD1_WITH_CALLTYPE(__stdcall, SetEventSink,
+ void(IChromeFrameHostEvents* event_sink));
+ MOCK_METHOD1_WITH_CALLTYPE(__stdcall, InstallExtension,
+ HRESULT(BSTR crx_path));
+ MOCK_METHOD1_WITH_CALLTYPE(__stdcall, LoadExtension,
+ HRESULT(BSTR extension_dir));
+ MOCK_METHOD0_WITH_CALLTYPE(__stdcall, GetEnabledExtensions, HRESULT());
+ MOCK_METHOD1_WITH_CALLTYPE(__stdcall, GetExtensionApisToAutomate,
+ HRESULT(BSTR* enabled_functions));
+ MOCK_METHOD1_WITH_CALLTYPE(__stdcall, GetSessionId, HRESULT(int*));
+};
+
+// A mock implementation of ChromeFrameHost.
+class MockChromeFrameHost
+ : public CComObjectRootEx<CComSingleThreadModel>,
+ public InitializingCoClass<StrictMock<MockChromeFrameHost> >,
+ public InstanceCountMixin<MockChromeFrameHost>,
+ public StrictMock<IChromeFrameHostMockImpl> {
+ public:
+ BEGIN_COM_MAP(MockChromeFrameHost)
+ COM_INTERFACE_ENTRY_IID(IID_IChromeFrameHost, IChromeFrameHost)
+ END_COM_MAP()
+
+ HRESULT Initialize() {
+ return S_OK;
+ }
+
+ HRESULT Initialize(MockChromeFrameHost** self) {
+ *self = this;
+ return S_OK;
+ }
+};
+
+} // namespace testing
+
+#endif // CEEE_IE_TESTING_MOCK_CHROME_FRAME_HOST_H_
diff --git a/ceee/ie/testing/mock_frame_event_handler_host.h b/ceee/ie/testing/mock_frame_event_handler_host.h
new file mode 100644
index 0000000..18e9572
--- /dev/null
+++ b/ceee/ie/testing/mock_frame_event_handler_host.h
@@ -0,0 +1,81 @@
+// 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.
+//
+// Mock implementation of ChromeFrameHost.
+#ifndef CEEE_IE_TESTING_MOCK_FRAME_EVENT_HANDLER_HOST_H_
+#define CEEE_IE_TESTING_MOCK_FRAME_EVENT_HANDLER_HOST_H_
+
+#include <string>
+
+#include "ceee/common/initializing_coclass.h"
+#include "ceee/ie/plugin/bho/frame_event_handler.h"
+#include "ceee/ie/plugin/scripting/content_script_native_api.h"
+#include "ceee/testing/utils/instance_count_mixin.h"
+#include "gmock/gmock.h"
+
+namespace testing {
+
+class IFrameEventHandlerHostMockImpl : public IFrameEventHandlerHost {
+ public:
+ MOCK_METHOD3(AttachBrowser, HRESULT(IWebBrowser2* browser,
+ IWebBrowser2* parent_browser,
+ IFrameEventHandler* handler));
+ MOCK_METHOD3(DetachBrowser, HRESULT(IWebBrowser2* browser,
+ IWebBrowser2* parent_browser,
+ IFrameEventHandler* handler));
+ MOCK_METHOD1(GetTopLevelBrowser, HRESULT(IWebBrowser2** browser));
+ MOCK_METHOD1(GetReadyState, HRESULT(READYSTATE* ready_state));
+ MOCK_METHOD1(OnReadyStateChanged, HRESULT(READYSTATE ready_state));
+ 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 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));
+};
+
+template<class BaseClass>
+class MockFrameEventHandlerHostBase
+ : public CComObjectRootEx<CComSingleThreadModel>,
+ public InitializingCoClass<BaseClass>,
+ public InstanceCountMixin<BaseClass>,
+ public StrictMock<IFrameEventHandlerHostMockImpl>,
+ public StrictMock<IExtensionPortMessagingProviderMockImpl> {
+ public:
+ BEGIN_COM_MAP(MockFrameEventHandlerHostBase)
+ COM_INTERFACE_ENTRY_IID(IID_IFrameEventHandlerHost, IFrameEventHandlerHost)
+ END_COM_MAP()
+};
+
+class MockFrameEventHandlerHost
+ : public MockFrameEventHandlerHostBase<MockFrameEventHandlerHost> {
+ public:
+ HRESULT Initialize(MockFrameEventHandlerHost** self) {
+ *self = this;
+ return S_OK;
+ }
+};
+
+} // namespace testing
+
+#endif // CEEE_IE_TESTING_MOCK_FRAME_EVENT_HANDLER_HOST_H_
diff --git a/ceee/ie/testing/precompile.cc b/ceee/ie/testing/precompile.cc
new file mode 100644
index 0000000..975a615
--- /dev/null
+++ b/ceee/ie/testing/precompile.cc
@@ -0,0 +1,6 @@
+// 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.
+//
+// Precompile generator file.
+#include "ceee/ie/testing/precompile.h"
diff --git a/ceee/ie/testing/precompile.h b/ceee/ie/testing/precompile.h
new file mode 100644
index 0000000..ef85751
--- /dev/null
+++ b/ceee/ie/testing/precompile.h
@@ -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.
+//
+// Precompile header for IE CEEE unittests.
+
+#ifndef CEEE_IE_TESTING_PRECOMPILE_H_
+#define CEEE_IE_TESTING_PRECOMPILE_H_
+
+#include <atlbase.h>
+#include <atlcom.h>
+#include <atlstr.h>
+
+#include "ceee/testing/utils/mock_com.h"
+#include "ceee/testing/utils/test_utils.h"
+#include "testing/gmock/include/gmock/gmock.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+#endif // CEEE_IE_TESTING_PRECOMPILE_H_
diff --git a/ceee/ie/testing/test_data/another_frame_one.html b/ceee/ie/testing/test_data/another_frame_one.html
new file mode 100644
index 0000000..a97a903
--- /dev/null
+++ b/ceee/ie/testing/test_data/another_frame_one.html
@@ -0,0 +1,12 @@
+<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN"
+ "http://www.w3.org/TR/html4/loose.dtd">
+<!-- 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.
+-->
+<html>
+<head><title>Another Frame One</title></head>
+<body>
+<h1>Another Frame One</h1>
+</body>
+</html>
diff --git a/ceee/ie/testing/test_data/another_frame_two.html b/ceee/ie/testing/test_data/another_frame_two.html
new file mode 100644
index 0000000..fbd64e6
--- /dev/null
+++ b/ceee/ie/testing/test_data/another_frame_two.html
@@ -0,0 +1,12 @@
+<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN"
+ "http://www.w3.org/TR/html4/loose.dtd">
+<!-- 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.
+-->
+<html>
+<head><title>Another Frame Two</title></head>
+<body>
+<h1>Another Frame Two</h1>
+</body>
+</html>
diff --git a/ceee/ie/testing/test_data/another_two_frames.html b/ceee/ie/testing/test_data/another_two_frames.html
new file mode 100644
index 0000000..339f133
--- /dev/null
+++ b/ceee/ie/testing/test_data/another_two_frames.html
@@ -0,0 +1,13 @@
+<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN"
+ "http://www.w3.org/TR/html4/loose.dtd">
+<!-- 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.
+-->
+<html>
+<head><title>Another two frames in a frameset</title></head>
+<frameset cols="50%,50%">
+ <frame src="another_frame_one.html" />
+ <frame src="another_frame_two.html" />
+</frameset>
+</html>
diff --git a/ceee/ie/testing/test_data/deep_frames.html b/ceee/ie/testing/test_data/deep_frames.html
new file mode 100644
index 0000000..a89090c
--- /dev/null
+++ b/ceee/ie/testing/test_data/deep_frames.html
@@ -0,0 +1,12 @@
+<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN"
+ "http://www.w3.org/TR/html4/loose.dtd">
+<!-- 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.
+-->
+<html>
+<head><title>Deep frames</title></head>
+<frameset cols="50%,50%">
+ <frame src="level_one_frame.html" name="deep_frames" />
+</frameset>
+</html>
diff --git a/ceee/ie/testing/test_data/frame_one.html b/ceee/ie/testing/test_data/frame_one.html
new file mode 100644
index 0000000..3bba64e
--- /dev/null
+++ b/ceee/ie/testing/test_data/frame_one.html
@@ -0,0 +1,12 @@
+<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN"
+ "http://www.w3.org/TR/html4/loose.dtd">
+<!-- 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.
+-->
+<html>
+<head><title>Frame One</title></head>
+<body>
+<h1>Frame One</h1>
+</body>
+</html>
diff --git a/ceee/ie/testing/test_data/frame_two.html b/ceee/ie/testing/test_data/frame_two.html
new file mode 100644
index 0000000..e4bdd8c
--- /dev/null
+++ b/ceee/ie/testing/test_data/frame_two.html
@@ -0,0 +1,12 @@
+<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN"
+ "http://www.w3.org/TR/html4/loose.dtd">
+<!-- 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.
+-->
+<html>
+<head><title>Frame Two</title></head>
+<body>
+<h1>Frame Two</h1>
+</body>
+</html>
diff --git a/ceee/ie/testing/test_data/level_one_frame.html b/ceee/ie/testing/test_data/level_one_frame.html
new file mode 100644
index 0000000..4422375
--- /dev/null
+++ b/ceee/ie/testing/test_data/level_one_frame.html
@@ -0,0 +1,14 @@
+<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN"
+ "http://www.w3.org/TR/html4/loose.dtd">
+<!-- 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.
+-->
+<html>
+<head><title>Level One Frame</title></head>
+<body>
+ <h1>Level One Frame</h1>
+ <iframe width="100%" height="400px" src="level_two_frame.html"
+ name="level_one" />
+</body>
+</html>
diff --git a/ceee/ie/testing/test_data/level_two_frame.html b/ceee/ie/testing/test_data/level_two_frame.html
new file mode 100644
index 0000000..0c84867
--- /dev/null
+++ b/ceee/ie/testing/test_data/level_two_frame.html
@@ -0,0 +1,13 @@
+<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN"
+ "http://www.w3.org/TR/html4/loose.dtd">
+<!-- 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.
+-->
+<html>
+<head><title>Level Two Frame</title></head>
+<body>
+ <h1>Level Two Frame</h1>
+ <iframe width="100%" height="400px" src="frame_one.html" name="level_two"/>
+</body>
+</html>
diff --git a/ceee/ie/testing/test_data/orphans.html b/ceee/ie/testing/test_data/orphans.html
new file mode 100644
index 0000000..65fcf10
--- /dev/null
+++ b/ceee/ie/testing/test_data/orphans.html
@@ -0,0 +1,72 @@
+<!-- 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.
+-->
+<html>
+ <head>
+ <script>
+var isIECache = "unset";
+function isIE() {
+ if (isIECache == "unset") {
+ isIECache = 0;
+ try {
+ if (navigator.appName == "Microsoft Internet Explorer") {
+ var a = navigator.userAgent, b = /MSIE ([0-9]{1,}[.0-9]{0,})/;
+ if (b.exec(a) != null) {
+ rv = parseFloat(RegExp.$1);
+ if (rv > 0)
+ isIECache = rv;
+ }
+ }
+ } catch (c) {
+ }
+ }
+ return isIECache;
+}
+
+function ie7Mode() {
+ if (isIE())
+ if (document.compatMode == "BackCompat")
+ return true;
+ else if (isIE() < 8)
+ return true;
+ else if (document.documentMode > 0 && document.documentMode < 8)
+ return true;
+ return false;
+}
+
+function go() {
+ var temp_frame = document.createElement("iframe");
+ temp_frame.src="#BlaBlaBla";
+ var j = document.getElementById("segmentDiv");
+ j.appendChild(temp_frame);
+ var inner_content = null;
+ if (temp_frame.contentDocument) {
+ inner_content = temp_frame.contentDocument;
+ } else if (temp_frame.contentWindow) {
+ inner_content = temp_frame.contentWindow;
+ }
+ if (inner_content) {
+ var inner_document = null;
+ if (ie7Mode())
+ inner_document = inner_content.window.document;
+ else {
+ inner_content.open();
+ inner_content.close();
+ inner_document = inner_content;
+ }
+ inner_document.write('<html><body><div id="iframeDIV" ></div></body></html>');
+ inner_document.close();
+
+ inner_document.getElementById("iframeDIV").innerHTML = '<iframe src="./frame_one.html"></iframe>';
+ } else {
+ alert('no content!');
+ }
+}
+ </script>
+ </head>
+ <body>
+ <div id="segmentDiv"></div>
+ <script> go(); </script>
+ </body>
+</html>
diff --git a/ceee/ie/testing/test_data/simple_page.html b/ceee/ie/testing/test_data/simple_page.html
new file mode 100644
index 0000000..74fd506
--- /dev/null
+++ b/ceee/ie/testing/test_data/simple_page.html
@@ -0,0 +1,12 @@
+<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN"
+ "http://www.w3.org/TR/html4/loose.dtd">
+<!-- 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.
+-->
+<html>
+<head><title>Simple Page</title></head>
+<body>
+<h1>This is the simplest possible page.</h1>
+</body>
+</html>
diff --git a/ceee/ie/testing/test_data/two_frames.html b/ceee/ie/testing/test_data/two_frames.html
new file mode 100644
index 0000000..dd8b0c4
--- /dev/null
+++ b/ceee/ie/testing/test_data/two_frames.html
@@ -0,0 +1,13 @@
+<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN"
+ "http://www.w3.org/TR/html4/loose.dtd">
+<!-- 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.
+-->
+<html>
+<head><title>Two frames in a frameset</title></head>
+<frameset cols="50%,50%">
+ <frame src="frame_one.html" />
+ <frame src="frame_two.html" />
+</frameset>
+</html>