summaryrefslogtreecommitdiffstats
path: root/ceee/ie/broker
diff options
context:
space:
mode:
Diffstat (limited to 'ceee/ie/broker')
-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
48 files changed, 11479 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