diff options
author | jstritar@chromium.org <jstritar@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98> | 2011-09-28 03:32:30 +0000 |
---|---|---|
committer | jstritar@chromium.org <jstritar@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98> | 2011-09-28 03:32:30 +0000 |
commit | 8c3495cdfbc29b4d903103bf3f0cea7e5bfc24de (patch) | |
tree | 025f57b645272c61fe681d1418acfc4523309381 /chrome/browser | |
parent | 98af60b92b1828945dca0c8cf384645a62682f6d (diff) | |
download | chromium_src-8c3495cdfbc29b4d903103bf3f0cea7e5bfc24de.zip chromium_src-8c3495cdfbc29b4d903103bf3f0cea7e5bfc24de.tar.gz chromium_src-8c3495cdfbc29b4d903103bf3f0cea7e5bfc24de.tar.bz2 |
Extend chrome.tabs* API to support multiple tabs.
This adds support for handling multiple tabs, including selecting, removing and moving many tabs at once. (Selecting is now called highlighting for backwards compatibility). The changes are outlined below.
Adds:
- 'highlighted' property to Tab, chrome.tabs.update
- chrome.tabs.query for searching across tabs
- chrome.tabs.highlight for selecting multiple tabs
- chrome.tabs.onHighlightChanged for listening to multi-select events
Modifies:
- renames 'selected' to 'active' on Tab, chrome.tabs.create, chrome.tabs.update
- renames onSelectionChanged to onActiveChanged
- chrome.tabs.move to accept an array of tabs or a single tab
- chrome.tabs.remove to accept an array of tabs or a single tab
- deprecates chrome.tabs.getSelected in favor of chrome.tabs.query
- deprecates chrome.tabs.getAllInWindow in favor of chrome.tabs.query
BUG=81411
TEST=ExtensionApiTest.*Tab*
Review URL: http://codereview.chromium.org/7731004
git-svn-id: svn://svn.chromium.org/chrome/trunk/src@103084 0039d316-1c4b-4281-b951-d872f2087c98
Diffstat (limited to 'chrome/browser')
20 files changed, 462 insertions, 128 deletions
diff --git a/chrome/browser/extensions/extension_browser_event_router.cc b/chrome/browser/extensions/extension_browser_event_router.cc index 661cdb4..66b3236 100644 --- a/chrome/browser/extensions/extension_browser_event_router.cc +++ b/chrome/browser/extensions/extension_browser_event_router.cc @@ -345,8 +345,41 @@ void ExtensionBrowserEventRouter::ActiveTabChanged( std::string json_args; base::JSONWriter::Write(&args, false, &json_args); + // The onTabSelectionChanged event has been deprecated by onActiveChanged. DispatchEvent(new_contents->profile(), events::kOnTabSelectionChanged, json_args); + DispatchEvent(new_contents->profile(), events::kOnTabActiveChanged, + json_args); +} + +void ExtensionBrowserEventRouter::TabSelectionChanged( + TabStripModel* tab_strip_model, + const TabStripSelectionModel& old_model) { + TabStripSelectionModel::SelectedIndices new_selection = + tab_strip_model->selection_model().selected_indices(); + ListValue* all = new ListValue(); + + for (size_t i = 0; i < new_selection.size(); ++i) { + int index = new_selection[i]; + int tab_id = ExtensionTabUtil::GetTabId( + tab_strip_model->GetTabContentsAt(index)->tab_contents()); + all->Append(Value::CreateIntegerValue(tab_id)); + } + + ListValue args; + DictionaryValue* select_info = new DictionaryValue(); + + select_info->Set(tab_keys::kWindowIdKey, Value::CreateIntegerValue( + ExtensionTabUtil::GetWindowIdOfTabStripModel(tab_strip_model))); + + select_info->Set(tab_keys::kTabIdsKey, all); + args.Append(select_info); + + std::string json_args; + base::JSONWriter::Write(&args, false, &json_args); + + DispatchEvent(tab_strip_model->profile(), events::kOnTabHighlightChanged, + json_args); } void ExtensionBrowserEventRouter::TabMoved(TabContentsWrapper* contents, diff --git a/chrome/browser/extensions/extension_browser_event_router.h b/chrome/browser/extensions/extension_browser_event_router.h index 409e744..c3f104f 100644 --- a/chrome/browser/extensions/extension_browser_event_router.h +++ b/chrome/browser/extensions/extension_browser_event_router.h @@ -68,6 +68,9 @@ class ExtensionBrowserEventRouter : public TabStripModelObserver, TabContentsWrapper* new_contents, int index, bool user_gesture) OVERRIDE; + virtual void TabSelectionChanged( + TabStripModel* tab_strip_model, + const TabStripSelectionModel& old_model) OVERRIDE; virtual void TabMoved(TabContentsWrapper* contents, int from_index, int to_index) OVERRIDE; virtual void TabChangedAt(TabContentsWrapper* contents, int index, diff --git a/chrome/browser/extensions/extension_event_names.cc b/chrome/browser/extensions/extension_event_names.cc index 76904bf..8c67a4d 100644 --- a/chrome/browser/extensions/extension_event_names.cc +++ b/chrome/browser/extensions/extension_event_names.cc @@ -6,9 +6,11 @@ namespace extension_event_names { +const char kOnTabActiveChanged[] = "tabs.onActiveChanged"; const char kOnTabAttached[] = "tabs.onAttached"; const char kOnTabCreated[] = "tabs.onCreated"; const char kOnTabDetached[] = "tabs.onDetached"; +const char kOnTabHighlightChanged[] = "tabs.onHighlightChanged"; const char kOnTabMoved[] = "tabs.onMoved"; const char kOnTabRemoved[] = "tabs.onRemoved"; const char kOnTabSelectionChanged[] = "tabs.onSelectionChanged"; diff --git a/chrome/browser/extensions/extension_event_names.h b/chrome/browser/extensions/extension_event_names.h index 2e44797..67b3dd2 100644 --- a/chrome/browser/extensions/extension_event_names.h +++ b/chrome/browser/extensions/extension_event_names.h @@ -11,9 +11,11 @@ namespace extension_event_names { // Tabs. +extern const char kOnTabActiveChanged[]; extern const char kOnTabAttached[]; extern const char kOnTabCreated[]; extern const char kOnTabDetached[]; +extern const char kOnTabHighlightChanged[]; extern const char kOnTabMoved[]; extern const char kOnTabRemoved[]; extern const char kOnTabSelectionChanged[]; diff --git a/chrome/browser/extensions/extension_function_dispatcher.cc b/chrome/browser/extensions/extension_function_dispatcher.cc index 74341d0..619d841 100644 --- a/chrome/browser/extensions/extension_function_dispatcher.cc +++ b/chrome/browser/extensions/extension_function_dispatcher.cc @@ -145,11 +145,13 @@ void FactoryRegistry::ResetFunctions() { RegisterFunction<GetCurrentTabFunction>(); RegisterFunction<GetSelectedTabFunction>(); RegisterFunction<GetAllTabsInWindowFunction>(); + RegisterFunction<QueryTabsFunction>(); + RegisterFunction<HighlightTabsFunction>(); RegisterFunction<CreateTabFunction>(); RegisterFunction<UpdateTabFunction>(); - RegisterFunction<MoveTabFunction>(); + RegisterFunction<MoveTabsFunction>(); RegisterFunction<ReloadTabFunction>(); - RegisterFunction<RemoveTabFunction>(); + RegisterFunction<RemoveTabsFunction>(); RegisterFunction<DetectTabLanguageFunction>(); RegisterFunction<CaptureVisibleTabFunction>(); RegisterFunction<TabsExecuteScriptFunction>(); diff --git a/chrome/browser/extensions/extension_tab_util.cc b/chrome/browser/extensions/extension_tab_util.cc index 6bf27d6..51030fe 100644 --- a/chrome/browser/extensions/extension_tab_util.cc +++ b/chrome/browser/extensions/extension_tab_util.cc @@ -20,6 +20,16 @@ int ExtensionTabUtil::GetWindowId(const Browser* browser) { return browser->session_id().id(); } +int ExtensionTabUtil::GetWindowIdOfTabStripModel( + const TabStripModel* tab_strip_model) { + for (BrowserList::const_iterator it = BrowserList::begin(); + it != BrowserList::end(); ++it) { + if ((*it)->tabstrip_model() == tab_strip_model) + return GetWindowId(*it); + } + return -1; +} + // TODO(sky): this function should really take a TabContentsWrapper. int ExtensionTabUtil::GetTabId(const TabContents* tab_contents) { const TabContentsWrapper* tab = @@ -38,6 +48,17 @@ int ExtensionTabUtil::GetWindowIdOfTab(const TabContents* tab_contents) { return tab ? tab->restore_tab_helper()->window_id().id() : -1; } +// Return the type name for a browser window type. +std::string ExtensionTabUtil::GetWindowTypeText(const Browser* browser) { + if (browser->is_type_popup()) + return keys::kWindowTypeValuePopup; + if (browser->is_type_panel()) + return keys::kWindowTypeValuePanel; + if (browser->is_app()) + return keys::kWindowTypeValueApp; + return keys::kWindowTypeValueNormal; +} + DictionaryValue* ExtensionTabUtil::CreateTabValue( const TabContents* contents) { // Find the tab strip and index of this guy. @@ -72,8 +93,12 @@ DictionaryValue* ExtensionTabUtil::CreateTabValue(const TabContents* contents, ExtensionTabUtil::GetWindowIdOfTab(contents)); result->SetString(keys::kUrlKey, contents->GetURL().spec()); result->SetString(keys::kStatusKey, GetTabStatusText(is_loading)); + result->SetBoolean(keys::kActiveKey, + tab_strip && tab_index == tab_strip->active_index()); result->SetBoolean(keys::kSelectedKey, tab_strip && tab_index == tab_strip->active_index()); + result->SetBoolean(keys::kHighlightedKey, + tab_strip && tab_strip->IsTabSelected(tab_index)); result->SetBoolean(keys::kPinnedKey, tab_strip && tab_strip->IsTabPinned(tab_index)); result->SetString(keys::kTitleKey, contents->GetTitle()); @@ -99,17 +124,6 @@ DictionaryValue* ExtensionTabUtil::CreateTabValueActive( return result; } -// Return the type name for a browser window type. -static std::string GetWindowTypeText(const Browser* browser) { - if (browser->is_type_popup()) - return keys::kWindowTypeValuePopup; - if (browser->is_type_panel()) - return keys::kWindowTypeValuePanel; - if (browser->is_app()) - return keys::kWindowTypeValueApp; - return keys::kWindowTypeValueNormal; -} - // if |populate| is true, each window gets a list property |tabs| which contains // fully populated tab objects. DictionaryValue* ExtensionTabUtil::CreateWindowValue(const Browser* browser, diff --git a/chrome/browser/extensions/extension_tab_util.h b/chrome/browser/extensions/extension_tab_util.h index 5dfb36f..37ada14 100644 --- a/chrome/browser/extensions/extension_tab_util.h +++ b/chrome/browser/extensions/extension_tab_util.h @@ -23,12 +23,14 @@ class ListValue; class ExtensionTabUtil { public: static int GetWindowId(const Browser* browser); + static int GetWindowIdOfTabStripModel(const TabStripModel* tab_strip_model); static int GetTabId(const TabContents* tab_contents); static bool GetTabIdFromArgument(const base::ListValue &args, int argument_index, int *tab_id, std::string* error_message); static std::string GetTabStatusText(bool is_loading); static int GetWindowIdOfTab(const TabContents* tab_contents); + static std::string GetWindowTypeText(const Browser* browser); static base::ListValue* CreateTabList(const Browser* browser); static base::DictionaryValue* CreateTabValue( const TabContents* tab_contents); diff --git a/chrome/browser/extensions/extension_tabs_apitest.cc b/chrome/browser/extensions/extension_tabs_apitest.cc index 3156ce7..c834e36 100644 --- a/chrome/browser/extensions/extension_tabs_apitest.cc +++ b/chrome/browser/extensions/extension_tabs_apitest.cc @@ -108,6 +108,16 @@ IN_PROC_BROWSER_TEST_F(ExtensionApiTest, TabRelativeURLs) { << message_; } +IN_PROC_BROWSER_TEST_F(ExtensionApiTest, TabQuery) { + ASSERT_TRUE(StartTestServer()); + ASSERT_TRUE(RunExtensionSubtest("tabs/basics", "query.html")) << message_; +} + +IN_PROC_BROWSER_TEST_F(ExtensionApiTest, TabHighlight) { + ASSERT_TRUE(StartTestServer()); + ASSERT_TRUE(RunExtensionSubtest("tabs/basics", "highlight.html")) << message_; +} + IN_PROC_BROWSER_TEST_F(ExtensionApiTest, TabCrashBrowser) { ASSERT_TRUE(StartTestServer()); ASSERT_TRUE(RunExtensionSubtest("tabs/basics", "crash.html")) << message_; diff --git a/chrome/browser/extensions/extension_tabs_module.cc b/chrome/browser/extensions/extension_tabs_module.cc index 5f55e70..1a2fd2d 100644 --- a/chrome/browser/extensions/extension_tabs_module.cc +++ b/chrome/browser/extensions/extension_tabs_module.cc @@ -10,6 +10,7 @@ #include "base/base64.h" #include "base/memory/ref_counted_memory.h" #include "base/stl_util.h" +#include "base/string16.h" #include "base/string_number_conversions.h" #include "base/string_util.h" #include "base/stringprintf.h" @@ -129,6 +130,53 @@ bool IsCrashURL(const GURL& url) { fixed_url.host() == chrome::kChromeUICrashHost)); } +// Reads the |value| as either a single integer value or a list of integers. +bool ReadOneOrMoreIntegers( + Value* value, std::vector<int>* result) { + if (value->IsType(Value::TYPE_INTEGER)) { + int tab_id; + if (!value->GetAsInteger(&tab_id)) + return false; + result->push_back(tab_id); + return true; + + } else if (value->IsType(Value::TYPE_LIST)) { + ListValue* tabs = static_cast<ListValue*>(value); + for (size_t i = 0; i < tabs->GetSize(); ++i) { + int tab_id; + if (!tabs->GetInteger(i, &tab_id)) + return false; + result->push_back(tab_id); + } + return true; + } + return false; +} + +// A three state enum to distinguish between when a boolean query argument is +// set or not. +enum QueryArg { + NOT_SET = -1, + MATCH_FALSE, + MATCH_TRUE +}; + +bool MatchesQueryArg(QueryArg arg, bool value) { + if (arg == NOT_SET) + return true; + + return (arg == MATCH_TRUE && value) || (arg == MATCH_FALSE && !value); +} + +QueryArg ParseBoolQueryArg(base::DictionaryValue* query, const char* key) { + if (query->HasKey(key)) { + bool value = false; + CHECK(query->GetBoolean(key, &value)); + return value ? MATCH_TRUE : MATCH_FALSE; + } + return NOT_SET; +} + } // namespace // Windows --------------------------------------------------------------------- @@ -561,6 +609,92 @@ bool GetAllTabsInWindowFunction::RunImpl() { return true; } +bool QueryTabsFunction::RunImpl() { + DictionaryValue* query = NULL; + EXTENSION_FUNCTION_VALIDATE(args_->GetDictionary(0, &query)); + + QueryArg active = ParseBoolQueryArg(query, keys::kActiveKey); + QueryArg pinned = ParseBoolQueryArg(query, keys::kPinnedKey); + QueryArg selected = ParseBoolQueryArg(query, keys::kHighlightedKey); + + QueryArg loading = NOT_SET; + if (query->HasKey(keys::kStatusKey)) { + std::string status; + EXTENSION_FUNCTION_VALIDATE(query->GetString(keys::kStatusKey, &status)); + loading = (status == keys::kStatusValueLoading) ? MATCH_TRUE : MATCH_FALSE; + } + + URLPattern url_pattern(URLPattern::SCHEME_ALL, "<all_urls>"); + if (query->HasKey(keys::kUrlKey)) { + std::string value; + EXTENSION_FUNCTION_VALIDATE(query->GetString(keys::kUrlKey, &value)); + url_pattern = URLPattern(URLPattern::SCHEME_ALL, value); + } + + std::string title; + if (query->HasKey(keys::kTitleKey)) + EXTENSION_FUNCTION_VALIDATE( + query->GetString(keys::kTitleKey, &title)); + + int window_id = -1; + if (query->HasKey(keys::kWindowIdKey)) + EXTENSION_FUNCTION_VALIDATE( + query->GetInteger(keys::kWindowIdKey, &window_id)); + + std::string window_type; + if (query->HasKey(keys::kWindowTypeLongKey)) + EXTENSION_FUNCTION_VALIDATE( + query->GetString(keys::kWindowTypeLongKey, &window_type)); + + ListValue* result = new ListValue(); + for (BrowserList::const_iterator browser = BrowserList::begin(); + browser != BrowserList::end(); ++browser) { + if (!profile()->IsSameProfile((*browser)->profile())) + continue; + + if (!(*browser)->window()) + continue; + + if (window_id >= 0 && window_id != ExtensionTabUtil::GetWindowId(*browser)) + continue; + + if (!window_type.empty() && + window_type != ExtensionTabUtil::GetWindowTypeText(*browser)) + continue; + + TabStripModel* tab_strip = (*browser)->tabstrip_model(); + for (int i = 0; i < tab_strip->count(); ++i) { + const TabContents* tab_contents = + tab_strip->GetTabContentsAt(i)->tab_contents(); + + if (!MatchesQueryArg(selected, tab_strip->IsTabSelected(i))) + continue; + + if (!MatchesQueryArg(active, i == tab_strip->active_index())) + continue; + + if (!MatchesQueryArg(pinned, tab_strip->IsTabPinned(i))) + continue; + + if (!title.empty() && !MatchPattern(tab_contents->GetTitle(), + UTF8ToUTF16(title))) + continue; + + if (!url_pattern.MatchesURL(tab_contents->GetURL())) + continue; + + if (!MatchesQueryArg(loading, tab_contents->IsLoading())) + continue; + + result->Append(ExtensionTabUtil::CreateTabValue( + tab_contents, tab_strip, i)); + } + } + + result_.reset(result); + return true; +} + bool CreateTabFunction::RunImpl() { DictionaryValue* args; EXTENSION_FUNCTION_VALIDATE(args_->GetDictionary(0, &args)); @@ -605,11 +739,17 @@ bool CreateTabFunction::RunImpl() { } // Default to foreground for the new tab. The presence of 'selected' property - // will override this default. - bool selected = true; + // will override this default. This property is deprecated ('active' should + // be used instead). + bool active = true; if (args->HasKey(keys::kSelectedKey)) - EXTENSION_FUNCTION_VALIDATE(args->GetBoolean(keys::kSelectedKey, - &selected)); + EXTENSION_FUNCTION_VALIDATE( + args->GetBoolean(keys::kSelectedKey, &active)); + + // The 'active' property has replaced the 'selected' property. + if (args->HasKey(keys::kActiveKey)) + EXTENSION_FUNCTION_VALIDATE( + args->GetBoolean(keys::kActiveKey, &active)); // Default to not pinning the tab. Setting the 'pinned' property to true // will override this default. @@ -640,18 +780,18 @@ bool CreateTabFunction::RunImpl() { index = std::min(std::max(index, -1), tab_strip->count()); - int add_types = selected ? TabStripModel::ADD_ACTIVE : + int add_types = active ? TabStripModel::ADD_ACTIVE : TabStripModel::ADD_NONE; add_types |= TabStripModel::ADD_FORCE_INDEX; if (pinned) add_types |= TabStripModel::ADD_PINNED; browser::NavigateParams params(browser, url, PageTransition::LINK); - params.disposition = selected ? NEW_FOREGROUND_TAB : NEW_BACKGROUND_TAB; + params.disposition = active ? NEW_FOREGROUND_TAB : NEW_BACKGROUND_TAB; params.tabstrip_index = index; params.tabstrip_add_types = add_types; browser::Navigate(¶ms); - if (selected) + if (active) params.target_contents->view()->SetInitialFocus(); // Return data about the newly created tab. @@ -693,6 +833,64 @@ bool GetCurrentTabFunction::RunImpl() { return true; } +bool HighlightTabsFunction::RunImpl() { + DictionaryValue* info = NULL; + EXTENSION_FUNCTION_VALIDATE(args_->GetDictionary(0, &info)); + + // Get the window id from the params. + int window_id = -1; + EXTENSION_FUNCTION_VALIDATE( + info->GetInteger(keys::kWindowIdKey, &window_id)); + + Browser* browser = GetBrowserInProfileWithId( + profile(), window_id, include_incognito(), &error_); + + if (!browser || !browser->window()) { + error_ = ExtensionErrorUtils::FormatErrorMessage( + keys::kWindowNotFoundError, base::IntToString(window_id)); + return false; + } + + TabStripModel* tabstrip = browser->tabstrip_model(); + TabStripSelectionModel selection; + int active_index = -1; + + Value* tab_value = NULL; + EXTENSION_FUNCTION_VALIDATE(info->Get(keys::kTabsKey, &tab_value)); + + std::vector<int> tab_indices; + EXTENSION_FUNCTION_VALIDATE(ReadOneOrMoreIntegers(tab_value, &tab_indices)); + + // Create a new selection model as we read the list of tab indices. + for (size_t i = 0; i < tab_indices.size(); ++i) { + int index = tab_indices[i]; + + // Make sure the index is in range. + if (!tabstrip->ContainsIndex(index)) { + error_ = ExtensionErrorUtils::FormatErrorMessage( + keys::kTabIndexNotFoundError, base::IntToString(index)); + return false; + } + + // By default, we make the first tab in the list active. + if (active_index == -1) + active_index = index; + + selection.AddIndexToSelection(index); + } + + // Make sure they actually specified tabs to select. + if (selection.empty()) { + error_ = keys::kNoHighlightedTabError; + return false; + } + + selection.set_active(active_index); + browser->tabstrip_model()->SetSelectionFromModel(selection); + result_.reset(ExtensionTabUtil::CreateWindowValue(browser, true)); + return true; +} + UpdateTabFunction::UpdateTabFunction() { } @@ -790,20 +988,32 @@ bool UpdateTabFunction::RunImpl() { DCHECK_EQ(url.spec(), contents->tab_contents()->GetURL().spec()); } - bool selected = false; - // TODO(rafaelw): Setting |selected| from js doesn't make much sense. + bool active = false; + // TODO(rafaelw): Setting |active| from js doesn't make much sense. // Move tab selection management up to window. - if (update_props->HasKey(keys::kSelectedKey)) { + if (update_props->HasKey(keys::kSelectedKey)) EXTENSION_FUNCTION_VALIDATE(update_props->GetBoolean( - keys::kSelectedKey, - &selected)); - if (selected) { - if (tab_strip->active_index() != tab_index) { - tab_strip->ActivateTabAt(tab_index, false); - DCHECK_EQ(contents, tab_strip->GetActiveTabContents()); - } - contents->tab_contents()->Focus(); + keys::kSelectedKey, &active)); + + // The 'active' property has replaced 'selected'. + if (update_props->HasKey(keys::kActiveKey)) + EXTENSION_FUNCTION_VALIDATE(update_props->GetBoolean( + keys::kActiveKey, &active)); + + if (active) { + if (tab_strip->active_index() != tab_index) { + tab_strip->ActivateTabAt(tab_index, false); + DCHECK_EQ(contents, tab_strip->GetActiveTabContents()); } + contents->tab_contents()->Focus(); + } + + bool highlighted = false; + if (update_props->HasKey(keys::kHighlightedKey)) { + EXTENSION_FUNCTION_VALIDATE(update_props->GetBoolean( + keys::kHighlightedKey, &highlighted)); + if (highlighted != tab_strip->IsTabSelected(tab_index)) + tab_strip->ToggleSelectionAt(tab_index); } bool pinned = false; @@ -865,9 +1075,13 @@ void UpdateTabFunction::OnExecuteCodeFinished(int request_id, Release(); // balanced in Execute() } -bool MoveTabFunction::RunImpl() { - int tab_id; - EXTENSION_FUNCTION_VALIDATE(args_->GetInteger(0, &tab_id)); +bool MoveTabsFunction::RunImpl() { + Value* tab_value = NULL; + EXTENSION_FUNCTION_VALIDATE(args_->Get(0, &tab_value)); + + std::vector<int> tab_ids; + EXTENSION_FUNCTION_VALIDATE(ReadOneOrMoreIntegers(tab_value, &tab_ids)); + DictionaryValue* update_props; EXTENSION_FUNCTION_VALIDATE(args_->GetDictionary(1, &update_props)); @@ -876,86 +1090,103 @@ bool MoveTabFunction::RunImpl() { keys::kIndexKey, &new_index)); EXTENSION_FUNCTION_VALIDATE(new_index >= 0); - Browser* source_browser = NULL; - TabStripModel* source_tab_strip = NULL; - TabContentsWrapper* contents = NULL; - int tab_index = -1; - if (!GetTabById(tab_id, profile(), include_incognito(), - &source_browser, &source_tab_strip, &contents, - &tab_index, &error_)) - return false; - - // Don't let the extension move the tab if the user is dragging tabs. - if (!source_browser->IsTabStripEditable()) { - error_ = keys::kTabStripNotEditableError; - return false; - } - - if (update_props->HasKey(keys::kWindowIdKey)) { - Browser* target_browser; - int window_id; - EXTENSION_FUNCTION_VALIDATE(update_props->GetInteger( - keys::kWindowIdKey, &window_id)); - target_browser = GetBrowserInProfileWithId(profile(), window_id, - include_incognito(), &error_); - if (!target_browser) + ListValue tab_values; + for (size_t i = 0; i < tab_ids.size(); ++i) { + Browser* source_browser = NULL; + TabStripModel* source_tab_strip = NULL; + TabContentsWrapper* contents = NULL; + int tab_index = -1; + if (!GetTabById(tab_ids[i], profile(), include_incognito(), + &source_browser, &source_tab_strip, &contents, + &tab_index, &error_)) return false; - if (!target_browser->IsTabStripEditable()) { + // Don't let the extension move the tab if the user is dragging tabs. + if (!source_browser->IsTabStripEditable()) { error_ = keys::kTabStripNotEditableError; return false; } - if (!target_browser->is_type_tabbed()) { - error_ = keys::kCanOnlyMoveTabsWithinNormalWindowsError; - return false; - } + // Insert the tabs one after another. + new_index += i; + + if (update_props->HasKey(keys::kWindowIdKey)) { + Browser* target_browser; + int window_id; + EXTENSION_FUNCTION_VALIDATE(update_props->GetInteger( + keys::kWindowIdKey, &window_id)); + target_browser = GetBrowserInProfileWithId(profile(), window_id, + include_incognito(), &error_); + if (!target_browser) + return false; - if (target_browser->profile() != source_browser->profile()) { - error_ = keys::kCanOnlyMoveTabsWithinSameProfileError; - return false; - } + if (!target_browser->IsTabStripEditable()) { + error_ = keys::kTabStripNotEditableError; + return false; + } - // If windowId is different from the current window, move between windows. - if (ExtensionTabUtil::GetWindowId(target_browser) != - ExtensionTabUtil::GetWindowId(source_browser)) { - TabStripModel* target_tab_strip = target_browser->tabstrip_model(); - contents = source_tab_strip->DetachTabContentsAt(tab_index); - if (!contents) { - error_ = ExtensionErrorUtils::FormatErrorMessage( - keys::kTabNotFoundError, base::IntToString(tab_id)); + if (!target_browser->is_type_tabbed()) { + error_ = keys::kCanOnlyMoveTabsWithinNormalWindowsError; return false; } - // Clamp move location to the last position. - // This is ">" because it can append to a new index position. - if (new_index > target_tab_strip->count()) - new_index = target_tab_strip->count(); + if (target_browser->profile() != source_browser->profile()) { + error_ = keys::kCanOnlyMoveTabsWithinSameProfileError; + return false; + } - target_tab_strip->InsertTabContentsAt(new_index, contents, - TabStripModel::ADD_NONE); + // If windowId is different from the current window, move between windows. + if (ExtensionTabUtil::GetWindowId(target_browser) != + ExtensionTabUtil::GetWindowId(source_browser)) { + TabStripModel* target_tab_strip = target_browser->tabstrip_model(); + contents = source_tab_strip->DetachTabContentsAt(tab_index); + if (!contents) { + error_ = ExtensionErrorUtils::FormatErrorMessage( + keys::kTabNotFoundError, base::IntToString(tab_ids[i])); + return false; + } - if (has_callback()) - result_.reset(ExtensionTabUtil::CreateTabValue(contents->tab_contents(), - target_tab_strip, new_index)); + // Clamp move location to the last position. + // This is ">" because it can append to a new index position. + if (new_index > target_tab_strip->count()) + new_index = target_tab_strip->count(); - return true; + target_tab_strip->InsertTabContentsAt( + new_index, contents, TabStripModel::ADD_NONE); + + if (has_callback()) + tab_values.Append(ExtensionTabUtil::CreateTabValue( + contents->tab_contents(), target_tab_strip, new_index)); + + continue; + } } - } - // Perform a simple within-window move. - // Clamp move location to the last position. - // This is ">=" because the move must be to an existing location. - if (new_index >= source_tab_strip->count()) - new_index = source_tab_strip->count() - 1; + // Perform a simple within-window move. + // Clamp move location to the last position. + // This is ">=" because the move must be to an existing location. + if (new_index >= source_tab_strip->count()) + new_index = source_tab_strip->count() - 1; - if (new_index != tab_index) - source_tab_strip->MoveTabContentsAt(tab_index, new_index, false); + if (new_index != tab_index) + source_tab_strip->MoveTabContentsAt(tab_index, new_index, false); + + if (has_callback()) + tab_values.Append(ExtensionTabUtil::CreateTabValue( + contents->tab_contents(), source_tab_strip, new_index)); + } - if (has_callback()) - result_.reset(ExtensionTabUtil::CreateTabValue(contents->tab_contents(), - source_tab_strip, - new_index)); + if (!has_callback()) + return true; + + // Only return the results as an array if there are multiple tabs. + if (tab_ids.size() > 1) { + result_.reset(tab_values.DeepCopy()); + } else if (tab_ids.size() == 1) { + Value* value = NULL; + CHECK(tab_values.Get(0, &value)); + result_.reset(value->DeepCopy()); + } return true; } @@ -1014,28 +1245,33 @@ bool ReloadTabFunction::RunImpl() { return true; } -bool RemoveTabFunction::RunImpl() { - int tab_id; - EXTENSION_FUNCTION_VALIDATE(args_->GetInteger(0, &tab_id)); +bool RemoveTabsFunction::RunImpl() { + Value* tab_value = NULL; + EXTENSION_FUNCTION_VALIDATE(args_->Get(0, &tab_value)); - Browser* browser = NULL; - TabContentsWrapper* contents = NULL; - if (!GetTabById(tab_id, profile(), include_incognito(), - &browser, NULL, &contents, NULL, &error_)) - return false; + std::vector<int> tab_ids; + EXTENSION_FUNCTION_VALIDATE(ReadOneOrMoreIntegers(tab_value, &tab_ids)); - // Don't let the extension remove a tab if the user is dragging tabs around. - if (!browser->IsTabStripEditable()) { - error_ = keys::kTabStripNotEditableError; - return false; - } + for (size_t i = 0; i < tab_ids.size(); ++i) { + Browser* browser = NULL; + TabContentsWrapper* contents = NULL; + if (!GetTabById(tab_ids[i], profile(), include_incognito(), + &browser, NULL, &contents, NULL, &error_)) + return false; - // Close the tab in this convoluted way, since there's a chance that the tab - // is being dragged, or we're in some other nested event loop. This code path - // should ensure that the tab is safely closed under such circumstances, - // whereas |Browser::CloseTabContents()| does not. - RenderViewHost* render_view_host = contents->render_view_host(); - render_view_host->delegate()->Close(render_view_host); + // Don't let the extension remove a tab if the user is dragging tabs around. + if (!browser->IsTabStripEditable()) { + error_ = keys::kTabStripNotEditableError; + return false; + } + + // Close the tab in this convoluted way, since there's a chance that the tab + // is being dragged, or we're in some other nested event loop. This code + // path should ensure that the tab is safely closed under such + // circumstances, whereas |Browser::CloseTabContents()| does not. + RenderViewHost* render_view_host = contents->render_view_host(); + render_view_host->delegate()->Close(render_view_host); + } return true; } diff --git a/chrome/browser/extensions/extension_tabs_module.h b/chrome/browser/extensions/extension_tabs_module.h index a248c47..31ab2b5 100644 --- a/chrome/browser/extensions/extension_tabs_module.h +++ b/chrome/browser/extensions/extension_tabs_module.h @@ -75,11 +75,21 @@ class GetAllTabsInWindowFunction : public SyncExtensionFunction { virtual bool RunImpl() OVERRIDE; DECLARE_EXTENSION_FUNCTION_NAME("tabs.getAllInWindow") }; +class QueryTabsFunction : public SyncExtensionFunction { + virtual ~QueryTabsFunction() {} + virtual bool RunImpl() OVERRIDE; + DECLARE_EXTENSION_FUNCTION_NAME("tabs.query") +}; class CreateTabFunction : public SyncExtensionFunction { virtual ~CreateTabFunction() {} virtual bool RunImpl() OVERRIDE; DECLARE_EXTENSION_FUNCTION_NAME("tabs.create") }; +class HighlightTabsFunction : public SyncExtensionFunction { + virtual ~HighlightTabsFunction() {} + virtual bool RunImpl() OVERRIDE; + DECLARE_EXTENSION_FUNCTION_NAME("tabs.highlight") +}; class UpdateTabFunction : public AsyncExtensionFunction, public TabContentsObserver { public: @@ -93,8 +103,8 @@ class UpdateTabFunction : public AsyncExtensionFunction, const std::string& error); DECLARE_EXTENSION_FUNCTION_NAME("tabs.update") }; -class MoveTabFunction : public SyncExtensionFunction { - virtual ~MoveTabFunction() {} +class MoveTabsFunction : public SyncExtensionFunction { + virtual ~MoveTabsFunction() {} virtual bool RunImpl() OVERRIDE; DECLARE_EXTENSION_FUNCTION_NAME("tabs.move") }; @@ -103,8 +113,8 @@ class ReloadTabFunction : public SyncExtensionFunction { virtual bool RunImpl(); DECLARE_EXTENSION_FUNCTION_NAME("tabs.reload") }; -class RemoveTabFunction : public SyncExtensionFunction { - virtual ~RemoveTabFunction() {} +class RemoveTabsFunction : public SyncExtensionFunction { + virtual ~RemoveTabsFunction() {} virtual bool RunImpl() OVERRIDE; DECLARE_EXTENSION_FUNCTION_NAME("tabs.remove") }; diff --git a/chrome/browser/extensions/extension_tabs_module_constants.cc b/chrome/browser/extensions/extension_tabs_module_constants.cc index 6d469f2..b60e7da 100644 --- a/chrome/browser/extensions/extension_tabs_module_constants.cc +++ b/chrome/browser/extensions/extension_tabs_module_constants.cc @@ -6,6 +6,7 @@ namespace extension_tabs_module_constants { +const char kActiveKey[] = "active"; const char kAllFramesKey[] = "allFrames"; const char kBypassCache[] = "bypassCache"; const char kCodeKey[] = "code"; @@ -27,9 +28,11 @@ const char kOldWindowIdKey[] = "oldWindowId"; const char kPinnedKey[] = "pinned"; const char kPopulateKey[] = "populate"; const char kQualityKey[] = "quality"; +const char kHighlightedKey[] = "highlighted"; const char kSelectedKey[] = "selected"; const char kStatusKey[] = "status"; const char kTabIdKey[] = "tabId"; +const char kTabIdsKey[] = "tabIds"; const char kTabsKey[] = "tabs"; const char kTabUrlKey[] = "tabUrl"; const char kTitleKey[] = "title"; @@ -40,6 +43,7 @@ const char kWindowClosing[] = "isWindowClosing"; const char kWidthKey[] = "width"; const char kWindowIdKey[] = "windowId"; const char kWindowTypeKey[] = "type"; +const char kWindowTypeLongKey[] = "windowType"; const char kFormatValueJpeg[] = "jpeg"; const char kFormatValuePng[] = "png"; @@ -64,10 +68,12 @@ const char kNoCrashBrowserError[] = const char kNoCurrentWindowError[] = "No current window"; const char kNoLastFocusedWindowError[] = "No last-focused window"; const char kWindowNotFoundError[] = "No window with id: *."; +const char kTabIndexNotFoundError[] = "No tab at index: *."; const char kTabNotFoundError[] = "No tab with id: *."; const char kTabStripNotEditableError[] = "Tabs cannot be edited right now (user may be dragging a tab)."; const char kNoSelectedTabError[] = "No selected tab"; +const char kNoHighlightedTabError[] = "No highlighted tab"; const char kIncognitoModeIsDisabled[] = "Incognito mode is disabled."; const char kInvalidUrlError[] = "Invalid url: \"*\"."; const char kInternalVisibleTabCaptureError[] = diff --git a/chrome/browser/extensions/extension_tabs_module_constants.h b/chrome/browser/extensions/extension_tabs_module_constants.h index 5347af7..d7f3167 100644 --- a/chrome/browser/extensions/extension_tabs_module_constants.h +++ b/chrome/browser/extensions/extension_tabs_module_constants.h @@ -11,6 +11,7 @@ namespace extension_tabs_module_constants { // Keys used in serializing tab data & events. +extern const char kActiveKey[]; extern const char kAllFramesKey[]; extern const char kBypassCache[]; extern const char kCodeKey[]; @@ -31,9 +32,11 @@ extern const char kOldWindowIdKey[]; extern const char kPinnedKey[]; extern const char kPopulateKey[]; extern const char kQualityKey[]; +extern const char kHighlightedKey[]; extern const char kSelectedKey[]; extern const char kStatusKey[]; extern const char kTabIdKey[]; +extern const char kTabIdsKey[]; extern const char kTabsKey[]; extern const char kTabUrlKey[]; extern const char kTitleKey[]; @@ -45,6 +48,7 @@ extern const char kWidthKey[]; extern const char kWindowIdKey[]; extern const char kIncognitoKey[]; extern const char kWindowTypeKey[]; +extern const char kWindowTypeLongKey[]; // Value consts. extern const char kCanOnlyMoveTabsWithinNormalWindowsError[]; @@ -65,8 +69,10 @@ extern const char kNoCrashBrowserError[]; extern const char kNoCurrentWindowError[]; extern const char kNoLastFocusedWindowError[]; extern const char kWindowNotFoundError[]; +extern const char kTabIndexNotFoundError[]; extern const char kTabNotFoundError[]; extern const char kTabStripNotEditableError[]; +extern const char kNoHighlightedTabError[]; extern const char kNoSelectedTabError[]; extern const char kIncognitoModeIsDisabled[]; extern const char kInvalidUrlError[]; diff --git a/chrome/browser/tabs/tab_strip_model.cc b/chrome/browser/tabs/tab_strip_model.cc index 278dcdb..2cc1dbd 100644 --- a/chrome/browser/tabs/tab_strip_model.cc +++ b/chrome/browser/tabs/tab_strip_model.cc @@ -247,7 +247,7 @@ TabContentsWrapper* TabStripModel::DetachTabContentsAt(int index) { // |old_model| is stored after calling DecrementFrom(). if (was_selected) { FOR_EACH_OBSERVER(TabStripModelObserver, observers_, - TabSelectionChanged(old_model)); + TabSelectionChanged(this, old_model)); } } return removed_contents; @@ -1225,7 +1225,7 @@ void TabStripModel::NotifyIfActiveOrSelectionChanged( if (!selection_model().Equals(old_model)) { FOR_EACH_OBSERVER(TabStripModelObserver, observers_, - TabSelectionChanged(old_model)); + TabSelectionChanged(this, old_model)); } } diff --git a/chrome/browser/tabs/tab_strip_model_observer.cc b/chrome/browser/tabs/tab_strip_model_observer.cc index cf0d1eb..e3035cf 100644 --- a/chrome/browser/tabs/tab_strip_model_observer.cc +++ b/chrome/browser/tabs/tab_strip_model_observer.cc @@ -28,6 +28,7 @@ void TabStripModelObserver::ActiveTabChanged(TabContentsWrapper* old_contents, } void TabStripModelObserver::TabSelectionChanged( + TabStripModel* tab_strip_model, const TabStripSelectionModel& model) { } diff --git a/chrome/browser/tabs/tab_strip_model_observer.h b/chrome/browser/tabs/tab_strip_model_observer.h index 5a69eb5..5b5e8ea 100644 --- a/chrome/browser/tabs/tab_strip_model_observer.h +++ b/chrome/browser/tabs/tab_strip_model_observer.h @@ -77,10 +77,12 @@ class TabStripModelObserver { int index, bool user_gesture); - // Sent when the selection changes. More precisely when selected tabs, anchor - // tab or active tab change. |old_model| is a snapshot of the selection model - // before the change. See also ActiveTabChanged for details. - virtual void TabSelectionChanged(const TabStripSelectionModel& old_model); + // Sent when the selection changes in |tab_strip_model|. More precisely when + // selected tabs, anchor tab or active tab change. |old_model| is a snapshot + // of the selection model before the change. See also ActiveTabChanged for + // details. + virtual void TabSelectionChanged(TabStripModel* tab_strip_model, + const TabStripSelectionModel& old_model); // The specified TabContents at |from_index| was moved to |to_index|. virtual void TabMoved(TabContentsWrapper* contents, diff --git a/chrome/browser/tabs/tab_strip_model_unittest.cc b/chrome/browser/tabs/tab_strip_model_unittest.cc index e615744..64b40b0 100644 --- a/chrome/browser/tabs/tab_strip_model_unittest.cc +++ b/chrome/browser/tabs/tab_strip_model_unittest.cc @@ -358,7 +358,8 @@ class MockTabStripModelObserver : public TabStripModelObserver { s->user_gesture = user_gesture; states_.push_back(s); } - virtual void TabSelectionChanged(const TabStripSelectionModel& old_model) { + virtual void TabSelectionChanged(TabStripModel* tab_strip_model, + const TabStripSelectionModel& old_model) { if (!log_tab_selection_changed()) return; diff --git a/chrome/browser/ui/gtk/tabs/tab_strip_gtk.cc b/chrome/browser/ui/gtk/tabs/tab_strip_gtk.cc index d1eb188..8a7455d 100644 --- a/chrome/browser/ui/gtk/tabs/tab_strip_gtk.cc +++ b/chrome/browser/ui/gtk/tabs/tab_strip_gtk.cc @@ -1028,7 +1028,8 @@ void TabStripGtk::ActiveTabChanged(TabContentsWrapper* old_contents, ReStack(); } -void TabStripGtk::TabSelectionChanged(const TabStripSelectionModel& old_model) { +void TabStripGtk::TabSelectionChanged(TabStripModel* tab_strip_model, + const TabStripSelectionModel& old_model) { // We have "tiny tabs" if the tabs are so tiny that the unselected ones are // a different size to the selected ones. bool tiny_tabs = current_unselected_width_ != current_selected_width_; diff --git a/chrome/browser/ui/gtk/tabs/tab_strip_gtk.h b/chrome/browser/ui/gtk/tabs/tab_strip_gtk.h index 8a296c4..84a69d0 100644 --- a/chrome/browser/ui/gtk/tabs/tab_strip_gtk.h +++ b/chrome/browser/ui/gtk/tabs/tab_strip_gtk.h @@ -114,7 +114,8 @@ class TabStripGtk : public TabStripModelObserver, TabContentsWrapper* new_contents, int index, bool user_gesture); - virtual void TabSelectionChanged(const TabStripSelectionModel& old_model); + virtual void TabSelectionChanged(TabStripModel* tab_strip_model, + const TabStripSelectionModel& old_model); virtual void TabChangedAt(TabContentsWrapper* contents, int index, TabChangeType change_type); virtual void TabReplacedAt(TabStripModel* tab_strip_model, diff --git a/chrome/browser/ui/views/tabs/browser_tab_strip_controller.cc b/chrome/browser/ui/views/tabs/browser_tab_strip_controller.cc index 1a9ab98..b48d674 100644 --- a/chrome/browser/ui/views/tabs/browser_tab_strip_controller.cc +++ b/chrome/browser/ui/views/tabs/browser_tab_strip_controller.cc @@ -347,6 +347,7 @@ void BrowserTabStripController::TabDetachedAt(TabContentsWrapper* contents, } void BrowserTabStripController::TabSelectionChanged( + TabStripModel* tab_strip_model, const TabStripSelectionModel& old_model) { tabstrip_->SetSelection(old_model, model_->selection_model()); } diff --git a/chrome/browser/ui/views/tabs/browser_tab_strip_controller.h b/chrome/browser/ui/views/tabs/browser_tab_strip_controller.h index f1a2f85..d08147e 100644 --- a/chrome/browser/ui/views/tabs/browser_tab_strip_controller.h +++ b/chrome/browser/ui/views/tabs/browser_tab_strip_controller.h @@ -73,6 +73,7 @@ class BrowserTabStripController : public TabStripController, virtual void TabDetachedAt(TabContentsWrapper* contents, int model_index) OVERRIDE; virtual void TabSelectionChanged( + TabStripModel* tab_strip_model, const TabStripSelectionModel& old_model) OVERRIDE; virtual void TabMoved(TabContentsWrapper* contents, int from_model_index, |