// Copyright (c) 2010 The Chromium 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 #include #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 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 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(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(INVALID_HANDLE_VALUE); scoped_ptr 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(tab_handle))); #endif // DEBUG HWND tab_window = reinterpret_cast(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 executor; dispatcher->GetExecutor(tab_window, IID_ICeeeTabExecutor, reinterpret_cast(&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 result(new DictionaryValue()); result->SetInteger(ext::kIdKey, tab_id); // TODO(mad@chromium.org): Support the pin field. result->SetBoolean(ext::kPinnedKey, false); // 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 executor; dispatcher->GetExecutor(frame_window, IID_ICeeeWindowExecutor, reinterpret_cast(&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(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(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(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(&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(&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 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 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 result(static_cast(user_data)); scoped_ptr 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 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(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 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 executor; dispatcher->GetExecutor(frame_window, IID_ICeeeWindowExecutor, reinterpret_cast(&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 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 executor; dispatcher->GetExecutor(tab_window, IID_ICeeeTabExecutor, reinterpret_cast(&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 frame_executor; dispatcher->GetExecutor(window_utils::GetTopLevelParent(tab_window), IID_ICeeeWindowExecutor, reinterpret_cast(&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(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 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 frame_executor; dispatcher->GetExecutor(window_utils::GetTopLevelParent(tab_window), IID_ICeeeWindowExecutor, reinterpret_cast(&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(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 result(static_cast(user_data)); scoped_ptr 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 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 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 executor; dispatcher->GetExecutor(existing_tab, IID_ICeeeTabExecutor, reinterpret_cast(&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 result(static_cast(user_data)); // Check if it has been created with the same info we were created for. scoped_ptr 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 frame_executor; dispatcher->GetExecutor(frame_window, __uuidof(ICeeeWindowExecutor), reinterpret_cast(&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(destination_index_int); HRESULT hr = frame_executor->MoveTab( reinterpret_cast(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 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(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(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 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 frame_executor; dispatcher->GetExecutor(frame_window, IID_ICeeeWindowExecutor, reinterpret_cast(&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(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 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 tab_executor; dispatcher->GetExecutor(tab_window, IID_ICeeeTabExecutor, reinterpret_cast(&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 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 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