Chromium Code Reviews
chromiumcodereview-hr@appspot.gserviceaccount.com (chromiumcodereview-hr) | Please choose your nickname with Settings | Help | Chromium Project | Gerrit Changes | Sign out
(4272)

Unified Diff: chrome/browser/ui/toolbar/recent_tabs_sub_menu_model.cc

Issue 11298004: alternate ntp: add "Recent Tabs" submenu to wrench menu (Closed) Base URL: svn://svn.chromium.org/chrome/trunk/src
Patch Set: Created 8 years, 1 month ago
Use n/p to move between diff chunks; N/P to move between comments. Draft comments are only viewable by you.
Jump to:
View side-by-side diff with in-line comments
Download patch
Index: chrome/browser/ui/toolbar/recent_tabs_sub_menu_model.cc
diff --git a/chrome/browser/ui/toolbar/recent_tabs_sub_menu_model.cc b/chrome/browser/ui/toolbar/recent_tabs_sub_menu_model.cc
new file mode 100644
index 0000000000000000000000000000000000000000..9a242fea595c1822c4f14e4f1a0985394b7d6a83
--- /dev/null
+++ b/chrome/browser/ui/toolbar/recent_tabs_sub_menu_model.cc
@@ -0,0 +1,378 @@
+// Copyright 2012 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "chrome/browser/ui/toolbar/recent_tabs_sub_menu_model.h"
+
+#include "base/bind.h"
+#include "base/string_number_conversions.h"
+#include "base/utf_string_conversions.h"
+#include "chrome/app/chrome_command_ids.h"
+#include "chrome/browser/favicon/favicon_service_factory.h"
+#include "chrome/browser/prefs/scoped_user_pref_update.h"
+#include "chrome/browser/profiles/profile.h"
+#include "chrome/browser/sessions/session_restore.h"
+#include "chrome/browser/sessions/tab_restore_service_delegate.h"
+#include "chrome/browser/sessions/tab_restore_service_factory.h"
+#include "chrome/browser/sync/glue/session_model_associator.h"
+#include "chrome/browser/sync/glue/synced_session.h"
+#include "chrome/browser/sync/profile_sync_service.h"
+#include "chrome/browser/sync/profile_sync_service_factory.h"
+#include "chrome/browser/ui/browser.h"
+#include "chrome/browser/ui/browser_commands.h"
+#include "chrome/browser/ui/browser_tabstrip.h"
+#include "chrome/common/pref_names.h"
+#include "chrome/common/time_format.h"
+#include "chrome/common/url_constants.h"
+#include "grit/generated_resources.h"
+#include "grit/ui_resources.h"
+#include "ui/base/accelerators/accelerator.h"
+#include "ui/base/l10n/l10n_util.h"
+#include "ui/base/resource/resource_bundle.h"
+#include "ui/gfx/favicon_size.h"
+
+namespace {
+
+// Comparator function for use with std::sort that will sort sessions by
+// descending modified_time (i.e., most recent first).
+bool SortSessionsByRecency(const browser_sync::SyncedSession* s1,
+ const browser_sync::SyncedSession* s2) {
+ return s1->modified_time > s2->modified_time;
+}
+
+// Helper function that returns the largest tab timestamp for the window.
+base::Time GetMostRecentTabTimestamp(const SessionWindow* window) {
+ base::Time max_timestamp;
+ for (size_t i = 0, num_tabs = window->tabs.size(); i < num_tabs; ++i) {
+ SessionTab* tab = window->tabs[i];
+ if (tab->timestamp > max_timestamp)
+ max_timestamp = tab->timestamp;
+ }
+ return max_timestamp;
+}
+
+// Comparator function to sort windows by last-modified time. Windows in a
+// session share the same timestamp, so we need to use tab timestamps instead.
+// instead.
+bool SortWindowsByRecency(const SessionWindow* w1, const SessionWindow* w2) {
+ return GetMostRecentTabTimestamp(w1) > GetMostRecentTabTimestamp(w2);
+}
+
+} // namepace
+
+struct RecentTabsSubMenuModel::NavigationItem {
sky 2012/11/07 00:10:47 Add description.
kuan 2012/11/07 02:29:46 Done.
+ NavigationItem() : tab_id(-1) {
+ }
+ NavigationItem(const std::string& session_tag,
sky 2012/11/07 00:10:47 nit: newline between 65/66 and 70/71.
kuan 2012/11/07 02:29:46 Done.
+ const SessionID::id_type& tab_id,
+ const GURL& url)
+ : session_tag(session_tag), tab_id(tab_id), url(url) {
sky 2012/11/07 00:10:47 since you wrapped the constructor wrap each param
kuan 2012/11/07 02:29:46 Done.
+ }
+ // For use by std::set for sorting.
+ bool operator<(const NavigationItem& other) const {
+ return url < other.url;
+ }
+
+ std::string session_tag;
+ SessionID::id_type tab_id;
+ GURL url;
+};
+
+RecentTabsSubMenuModel::RecentTabsSubMenuModel(
+ ui::AcceleratorProvider* accelerator_provider,
+ Browser* browser)
+ : ALLOW_THIS_IN_INITIALIZER_LIST(ui::SimpleMenuModel(this)),
+ accelerator_provider_(accelerator_provider),
+ browser_(browser),
+ default_favicon_(NULL),
+ ALLOW_THIS_IN_INITIALIZER_LIST(weak_ptr_factory_(this)) {
+ Build();
+}
+
+RecentTabsSubMenuModel::~RecentTabsSubMenuModel() {
+ weak_ptr_factory_.InvalidateWeakPtrs();
+}
+
+bool RecentTabsSubMenuModel::IsCommandIdChecked(int command_id) const {
+ return false;
+}
+
+bool RecentTabsSubMenuModel::IsCommandIdEnabled(int command_id) const {
+ return command_id == IDC_RESTORE_TAB ?
+ chrome::IsCommandEnabled(browser_, command_id) : !model_.empty();
+}
+
+bool RecentTabsSubMenuModel::GetAcceleratorForCommandId(
+ int command_id, ui::Accelerator* accelerator) {
+ return command_id == IDC_RESTORE_TAB ?
+ accelerator_provider_->GetAcceleratorForCommandId(command_id,
+ accelerator) :
+ false;
+}
+
+void RecentTabsSubMenuModel::ExecuteCommand(int command_id) {
+ ExecuteCommand(command_id, 0);
+}
+
+void RecentTabsSubMenuModel::ExecuteCommand(int command_id, int event_flags) {
+ if (command_id == IDC_RESTORE_TAB) {
+ chrome::ExecuteCommandWithDisposition(browser_, command_id,
+ chrome::DispositionFromEventFlags(event_flags));
+ return;
+ }
+
+ // An empty |model_| means there's no executable menu items.
+ // When building menu items, |command_id_| was set to zero for device section
+ // header, which is non-executable.
+ if (model_.empty() || command_id == 0)
+ return;
+
+ DCHECK_LT(command_id, static_cast<int>(model_.size()));
+ const NavigationItem& item = model_[command_id];
+ DCHECK(item.tab_id > -1 && item.url.is_valid());
+
+ if (item.session_tag.empty()) { // Restore tab of local session.
+ TabRestoreService* service =
+ TabRestoreServiceFactory::GetForProfile(browser_->profile());
+ if (!service)
+ return;
+ TabRestoreServiceDelegate* delegate =
+ TabRestoreServiceDelegate::FindDelegateForWebContents(
+ chrome::GetActiveWebContents(browser_));
+ if (!delegate)
+ return;
+ service->RestoreEntryById(delegate, item.tab_id,
+ chrome::DispositionFromEventFlags(event_flags));
+ } else { // Restore tab of foreign session.
+ browser_sync::SessionModelAssociator* associator = GetModelAssociator();
+ if (!associator)
+ return;
+ const SessionTab* tab;
+ if (!associator->GetForeignTab(item.session_tag, item.tab_id, &tab))
+ return;
+ if (tab->navigations.empty())
+ return;
+ SessionRestore::RestoreForeignSessionTab(
+ chrome::GetActiveWebContents(browser_), *tab,
+ chrome::DispositionFromEventFlags(event_flags));
+ }
+}
+
+void RecentTabsSubMenuModel::Build() {
+ // For now when only last opened tab can be restored (instead of multiple),
+ // |model_| only contains items for other devices:
+ // - section header for device name (command_id = 0)
+ // - tabs from other devices (command_id = index into |model_|, which will
+ // never be 0 since there would be always be a section header)
+ // - separator that separates tabs of each device (comand id = -1)
+ // Later, if multiple tabs in local session can be restored, |model_| will
+ // contain items in the entire menu, and first separator for other devices
+ // is only needed if there's at least 1 local tab to restore.
+ BuildRecentlyClosed();
+ BuildDevices();
+ if (model_.empty())
+ AddItemWithStringId(0, IDS_RECENT_TABS_NO_DEVICE_TABS);
+}
+
+void RecentTabsSubMenuModel::BuildRecentlyClosed() {
+#if !defined(SHOW_MULTIPLE_RECENTLY_CLOSED_TABS)
sky 2012/11/07 00:10:47 Why the ifdef?
kuan 2012/11/07 02:29:46 i explained it in the cl description. david wrote
sky 2012/11/07 14:40:12 The ifdefs make the code a lot harder to follow. A
kuan 2012/11/07 16:48:52 Done. "Reopen closed tab" is disabled if there's
+ AddItemWithStringId(IDC_RESTORE_TAB, IDS_RESTORE_TAB);
+ AddSeparator(ui::NORMAL_SEPARATOR);
+#else
+ TabRestoreService* service =
+ TabRestoreServiceFactory::GetForProfile(browser_->profile());
sky 2012/11/07 00:10:47 What happens if the contents of the tabrestoreserv
kuan 2012/11/07 02:29:46 since this code is not being used for now, i've ad
sky 2012/11/07 14:40:12 Yet more of a reason to nuke this code.
kuan 2012/11/07 16:48:52 Done.
+
+ const int kRecentlyClosedCount = 10;
+ const TabRestoreService::Entries& entries = service->entries();
+ int added_count = 0;
+ std::set<NavigationItem> seen_tabs;
sky 2012/11/07 00:10:47 Is there a particular reason for trying to avoid c
kuan 2012/11/07 02:29:46 i believe david didn't want to repeat tabs with th
+ for (TabRestoreService::Entries::const_iterator it = entries.begin();
+ it != entries.end() && added_count < kRecentlyClosedCount; ++it) {
+ TabRestoreService::Entry* entry = *it;
+
+ if (entry->type == TabRestoreService::WINDOW) {
+ TabRestoreService::Window* window_entry =
+ static_cast<TabRestoreService::Window*>(entry);
+ std::vector<TabRestoreService::Tab>& tabs = window_entry->tabs;
+ if (tabs.empty())
+ continue;
+
+ // Loop over the window's tabs and add them to the menu.
+ std::vector<TabRestoreService::Tab>::const_iterator it;
sky 2012/11/07 00:10:47 Use a different name thant that of it defined on 1
kuan 2012/11/07 02:29:46 Done.
+ for (it = tabs.begin(); it != tabs.end(); ++it) {
+ TabRestoreService::Tab tab = *it;
+ if (BuildRecentlyClosedTabItem(tab, &seen_tabs))
sky 2012/11/07 00:10:47 Why are you extracting the tabs from window entrie
kuan 2012/11/07 02:29:46 i assume u're talking about RecentClosedTabsHandle
sky 2012/11/07 17:35:02 I'm referring to why do you look through all the t
kuan 2012/11/07 17:38:23 this code is nuked in patch set 3.
+ ++added_count;
+ }
+ } else if (entry->type == TabRestoreService::TAB) {
+ TabRestoreService::Tab* tab = static_cast<TabRestoreService::Tab*>(entry);
+ if (!tab)
sky 2012/11/07 00:10:47 If this were NULL you would have crashed on 193.
kuan 2012/11/07 02:29:46 hm.. i thot that even if entry is !NULL, if it's t
sky 2012/11/07 14:40:12 No, you would basically be casting to the wrong th
kuan 2012/11/07 16:48:52 Done. code is nuked.
+ continue;
+
+ if (BuildRecentlyClosedTabItem(*tab, &seen_tabs))
+ ++added_count;
+ }
+ }
+#endif // defined(SHOW_MULTIPLE_RECENTLY_CLOSED_TABS)
+}
+
+bool RecentTabsSubMenuModel::BuildRecentlyClosedTabItem(
+ const TabRestoreService::Tab& entry,
+ std::set<NavigationItem>* seen_tabs) {
+#if !defined(SHOW_MULTIPLE_RECENTLY_CLOSED_TABS)
+ return false;
+#else
+ const TabNavigation& current_navigation =
+ entry.navigations[entry.current_navigation_index];
+ NavigationItem item(std::string(), entry.id,
+ current_navigation.virtual_url());
+ if (seen_tabs->count(item))
+ return false;
sky 2012/11/07 00:10:47 nit: indentation is off.
kuan 2012/11/07 02:29:46 Done.
+ BuildTabToRestore(item, current_navigation.title());
+ seen_tabs->insert(item);
+ return true;
+#endif // defined(SHOW_MULTIPLE_RECENTLY_CLOSED_TABS)
+}
+
+bool RecentTabsSubMenuModel::BuildForeignTabItem(
+ const std::string& session_tag,
+ const SessionTab& tab,
+ const std::string& session_name,
+ bool need_separator) {
+ if (tab.navigations.empty())
+ return false;
+ int selected_index = std::min(tab.current_navigation_index,
+ static_cast<int>(tab.navigations.size() - 1));
+ const TabNavigation& current_navigation = tab.navigations.at(selected_index);
+ const GURL& tab_url = current_navigation.virtual_url();
+ if (tab_url == GURL(chrome::kChromeUINewTabURL))
+ return false;
+ if (need_separator) {
+ AddSeparator(ui::NORMAL_SEPARATOR);
+ model_.push_back(NavigationItem());
+ }
+ if (!session_name.empty()) {
+ AddItem(0, UTF8ToUTF16(session_name));
+ model_.push_back(NavigationItem());
+ }
+ NavigationItem item(session_tag, tab.tab_id.id(),
+ current_navigation.virtual_url());
+ BuildTabToRestore(item, current_navigation.title());
+ return true;
+}
+
+void RecentTabsSubMenuModel::BuildTabToRestore(const NavigationItem& item,
+ const string16& tab_title) {
+ AddItem(model_.size(), tab_title);
+ AddFavicon(model_.size(), item.url);
+ model_.push_back(item);
+}
+
+void RecentTabsSubMenuModel::AddFavicon(int model_index, const GURL& url) {
+ if (!default_favicon_) {
+ ResourceBundle& rb = ResourceBundle::GetSharedInstance();
+ default_favicon_ = &rb.GetNativeImageNamed(IDR_DEFAULT_FAVICON);
+ }
+ // Set default icon first.
+ SetIcon(GetIndexInMenu(model_index), *default_favicon_);
+ // Start request to fetch actual icon if possible.
+ FaviconService* favicon_service = FaviconServiceFactory::GetForProfile(
+ browser_->profile(), Profile::EXPLICIT_ACCESS);
+ if (!favicon_service)
+ return;
+ FaviconService::Handle handle = favicon_service->GetFaviconImageForURL(
+ FaviconService::FaviconForURLParams(browser_->profile(), url,
sky 2012/11/07 00:10:47 nit: indentation is off here.
kuan 2012/11/07 02:29:46 Done.
+ history::FAVICON, gfx::kFaviconSize, &favicon_consumer_),
+ base::Bind(&RecentTabsSubMenuModel::OnFaviconDataAvailable,
+ weak_ptr_factory_.GetWeakPtr()));
+ favicon_consumer_.SetClientData(favicon_service, handle, model_index);
+}
+
+void RecentTabsSubMenuModel::OnFaviconDataAvailable(
+ FaviconService::Handle handle,
+ const history::FaviconImageResult& image_result) {
+ if (image_result.image.IsEmpty())
+ return;
+ DCHECK(!model_.empty());
+ int model_index = favicon_consumer_.GetClientData(
+ FaviconServiceFactory::GetForProfile(browser_->profile(),
+ Profile::EXPLICIT_ACCESS),
+ handle);
+ DCHECK(model_index > 0 && model_index < static_cast<int>(model_.size()));
+ DCHECK(model_[model_index].tab_id > -1 && model_[model_index].url.is_valid());
+ int index_in_menu = GetIndexInMenu(model_index);
+ SetIcon(index_in_menu, image_result.image);
+ if (menu_model_delegate())
+ menu_model_delegate()->OnIconChanged(index_in_menu);
+}
+
+void RecentTabsSubMenuModel::BuildDevices() {
+ browser_sync::SessionModelAssociator* associator = GetModelAssociator();
+ if (!associator)
+ return;
+
+ std::vector<const browser_sync::SyncedSession*> sessions;
+ if (!associator->GetAllForeignSessions(&sessions))
+ return;
+
+ // Sort sessions from most recent to least recent.
+ std::sort(sessions.begin(), sessions.end(), SortSessionsByRecency);
+
+ const size_t kMaxSessionsToShow = 10;
+ bool need_separator = false;
+ for (size_t i = 0; i < sessions.size() && i < kMaxSessionsToShow; ++i) {
+ const browser_sync::SyncedSession* session = sessions[i];
+ const std::string& session_tag = session->session_tag;
+
+ // Get windows of session.
+ std::vector<const SessionWindow*> windows;
+ if (!associator->GetForeignSession(session_tag, &windows) ||
+ windows.empty()) {
+ continue;
+ }
+ // Sort windows from most recent to least recent, based on tabs in windows.
+ std::sort(windows.begin(), windows.end(), SortWindowsByRecency);
+
+ const int kMaxTabsPerSessionToShow = 18;
+ int num_tabs_in_session = 0;
+ for (size_t j = 0;
+ j < windows.size() && num_tabs_in_session < kMaxTabsPerSessionToShow;
+ ++j) {
+ const SessionWindow* window = windows[j];
+ // Get tabs of window.
+ for (size_t k = 0;
+ k < window->tabs.size() &&
+ num_tabs_in_session < kMaxTabsPerSessionToShow;
+ ++k) {
+ if (BuildForeignTabItem(session_tag, *window->tabs[k],
+ // Only need |session_name| for the first tab of the session.
+ !num_tabs_in_session ? session->session_name : std::string(),
+ need_separator)) {
+ need_separator = false;
+ ++num_tabs_in_session;
+ }
+ } // for all tabs of one window
+ } // for all windows of one session
+
+ if (num_tabs_in_session > 0)
+ need_separator = true;
+ } // for all sessions
+}
+
+int RecentTabsSubMenuModel::GetIndexInMenu(int model_index) const {
+ // For now when only last closed tab can be restored, |model_| does not
+ // include IDC_RESTORE_TAB and the separator after.
+ const int kNumItemsNotInModel = 2;
+ // |model_index| indexes into |model_|, which doesn't include
+ // kNumItemsNotInModel, so add that to get index in menu.
+ return model_index + kNumItemsNotInModel;
+}
+
+browser_sync::SessionModelAssociator*
+ RecentTabsSubMenuModel::GetModelAssociator() const {
+ ProfileSyncService* service = ProfileSyncServiceFactory::GetInstance()->
+ GetForProfile(browser_->profile());
+ // Only return the associator if it exists and it is done syncing sessions.
+ return service && service->ShouldPushChanges() ?
+ service->GetSessionModelAssociator() : NULL;
+}

Powered by Google App Engine
This is Rietveld 408576698