diff options
Diffstat (limited to 'ceee/ie/broker/tab_api_module.cc')
-rw-r--r-- | ceee/ie/broker/tab_api_module.cc | 1185 |
1 files changed, 1185 insertions, 0 deletions
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 |