blob: 0ab3291188a7d7226e938272c0ed03b7daf2ff2c [file] [log] [blame]
Torne (Richard Coles)2a99a7e2013-03-28 15:31:22 +00001// Copyright 2012 The Chromium Authors. All rights reserved.
2// Use of this source code is governed by a BSD-style license that can be
3// found in the LICENSE file.
4
5#include "chrome/browser/ui/toolbar/recent_tabs_sub_menu_model.h"
6
7#include "base/bind.h"
Ben Murdocheb525c52013-07-10 11:40:50 +01008#include "base/metrics/histogram.h"
Torne (Richard Coles)2a99a7e2013-03-28 15:31:22 +00009#include "base/strings/string_number_conversions.h"
Torne (Richard Coles)868fa2f2013-06-11 10:57:03 +010010#include "base/strings/utf_string_conversions.h"
Torne (Richard Coles)2a99a7e2013-03-28 15:31:22 +000011#include "chrome/app/chrome_command_ids.h"
12#include "chrome/browser/favicon/favicon_service_factory.h"
13#include "chrome/browser/prefs/scoped_user_pref_update.h"
14#include "chrome/browser/profiles/profile.h"
Torne (Richard Coles)a36e5922013-08-05 13:57:33 +010015#include "chrome/browser/search/search.h"
Torne (Richard Coles)2a99a7e2013-03-28 15:31:22 +000016#include "chrome/browser/sessions/session_restore.h"
Torne (Richard Coles)c2e0dbd2013-05-09 18:35:53 +010017#include "chrome/browser/sessions/tab_restore_service.h"
Torne (Richard Coles)2a99a7e2013-03-28 15:31:22 +000018#include "chrome/browser/sessions/tab_restore_service_delegate.h"
19#include "chrome/browser/sessions/tab_restore_service_factory.h"
20#include "chrome/browser/sync/glue/session_model_associator.h"
21#include "chrome/browser/sync/glue/synced_session.h"
22#include "chrome/browser/sync/profile_sync_service.h"
23#include "chrome/browser/sync/profile_sync_service_factory.h"
24#include "chrome/browser/ui/browser.h"
25#include "chrome/browser/ui/browser_commands.h"
26#include "chrome/browser/ui/tabs/tab_strip_model.h"
Ben Murdoch9ab55632013-07-18 11:57:30 +010027#include "chrome/common/favicon/favicon_types.h"
Torne (Richard Coles)2a99a7e2013-03-28 15:31:22 +000028#include "chrome/common/pref_names.h"
Torne (Richard Coles)c2e0dbd2013-05-09 18:35:53 +010029#include "grit/browser_resources.h"
Torne (Richard Coles)2a99a7e2013-03-28 15:31:22 +000030#include "grit/generated_resources.h"
31#include "grit/theme_resources.h"
32#include "grit/ui_resources.h"
33#include "ui/base/accelerators/accelerator.h"
34#include "ui/base/l10n/l10n_util.h"
35#include "ui/base/resource/resource_bundle.h"
36#include "ui/gfx/favicon_size.h"
37
38#if defined(USE_ASH)
39#include "ash/accelerators/accelerator_table.h"
40#endif // defined(USE_ASH)
41
42namespace {
43
44// First comamnd id for navigatable (and hence executable) tab menu item.
Torne (Richard Coles)c2e0dbd2013-05-09 18:35:53 +010045// The models and menu are not 1-1:
Torne (Richard Coles)2a99a7e2013-03-28 15:31:22 +000046// - menu has "Reopen closed tab", "No tabs from other devices", device section
47// headers, separators and executable tab items.
Torne (Richard Coles)c2e0dbd2013-05-09 18:35:53 +010048// - |tab_navigation_items_| only has navigatabale/executable tab items.
49// - |window_items_| only has executable open window items.
50// Using an initial command ids for tab/window items makes it easier and less
51// error-prone to manipulate the models and menu.
52// These values must be bigger than the maximum possible number of items in
53// menu, so that index of last menu item doesn't clash with this value when menu
54// items are retrieved via GetIndexOfCommandId.
Torne (Richard Coles)2a99a7e2013-03-28 15:31:22 +000055const int kFirstTabCommandId = 100;
Torne (Richard Coles)c2e0dbd2013-05-09 18:35:53 +010056const int kFirstWindowCommandId = 200;
Torne (Richard Coles)2a99a7e2013-03-28 15:31:22 +000057
Torne (Richard Coles)c2e0dbd2013-05-09 18:35:53 +010058// The maximum number of recently closed entries to be shown in the menu.
59const int kMaxRecentlyClosedEntries = 8;
Torne (Richard Coles)2a99a7e2013-03-28 15:31:22 +000060
61// Comparator function for use with std::sort that will sort sessions by
62// descending modified_time (i.e., most recent first).
63bool SortSessionsByRecency(const browser_sync::SyncedSession* s1,
64 const browser_sync::SyncedSession* s2) {
65 return s1->modified_time > s2->modified_time;
66}
67
68// Comparator function for use with std::sort that will sort tabs by
69// descending timestamp (i.e., most recent first).
70bool SortTabsByRecency(const SessionTab* t1, const SessionTab* t2) {
71 return t1->timestamp > t2->timestamp;
72}
73
Torne (Richard Coles)c2e0dbd2013-05-09 18:35:53 +010074// Returns true if the command id is related to a tab model index.
75bool IsTabModelCommandId(int command_id) {
76 return command_id >= kFirstTabCommandId && command_id < kFirstWindowCommandId;
77}
78
79// Returns true if the command id is related to a window model index.
80bool IsWindowModelCommandId(int command_id) {
81 return command_id >= kFirstWindowCommandId &&
82 command_id < RecentTabsSubMenuModel::kRecentlyClosedHeaderCommandId;
83}
84
85// Convert |tab_model_index| to command id of menu item.
86int TabModelIndexToCommandId(int tab_model_index) {
87 int command_id = tab_model_index + kFirstTabCommandId;
88 DCHECK_LT(command_id, kFirstWindowCommandId);
Torne (Richard Coles)2a99a7e2013-03-28 15:31:22 +000089 return command_id;
90}
91
Torne (Richard Coles)c2e0dbd2013-05-09 18:35:53 +010092// Convert |command_id| of menu item to index in tab model.
93int CommandIdToTabModelIndex(int command_id) {
94 DCHECK_GE(command_id, kFirstTabCommandId);
95 DCHECK_LT(command_id, kFirstWindowCommandId);
Torne (Richard Coles)2a99a7e2013-03-28 15:31:22 +000096 return command_id - kFirstTabCommandId;
97}
98
Torne (Richard Coles)c2e0dbd2013-05-09 18:35:53 +010099// Convert |window_model_index| to command id of menu item.
100int WindowModelIndexToCommandId(int window_model_index) {
101 int command_id = window_model_index + kFirstWindowCommandId;
102 DCHECK_LT(command_id, RecentTabsSubMenuModel::kRecentlyClosedHeaderCommandId);
103 return command_id;
104}
105
106// Convert |command_id| of menu item to index in window model.
107int CommandIdToWindowModelIndex(int command_id) {
108 DCHECK_GE(command_id, kFirstWindowCommandId);
109 DCHECK_LT(command_id, RecentTabsSubMenuModel::kRecentlyClosedHeaderCommandId);
110 return command_id - kFirstWindowCommandId;
111}
112
Torne (Richard Coles)7d4cd472013-06-19 11:58:07 +0100113} // namespace
Torne (Richard Coles)2a99a7e2013-03-28 15:31:22 +0000114
Ben Murdocheb525c52013-07-10 11:40:50 +0100115enum RecentTabAction {
116 LOCAL_SESSION_TAB = 0,
117 OTHER_DEVICE_TAB,
118 RESTORE_WINDOW,
119 SHOW_MORE,
120 LIMIT_RECENT_TAB_ACTION
121};
122
Torne (Richard Coles)c2e0dbd2013-05-09 18:35:53 +0100123// An element in |RecentTabsSubMenuModel::tab_navigation_items_| that stores
124// the navigation information of a local or foreign tab required to restore the
125// tab.
126struct RecentTabsSubMenuModel::TabNavigationItem {
127 TabNavigationItem() : tab_id(-1) {}
Torne (Richard Coles)2a99a7e2013-03-28 15:31:22 +0000128
Torne (Richard Coles)c2e0dbd2013-05-09 18:35:53 +0100129 TabNavigationItem(const std::string& session_tag,
130 const SessionID::id_type& tab_id,
Ben Murdocheb525c52013-07-10 11:40:50 +0100131 const string16& title,
Torne (Richard Coles)c2e0dbd2013-05-09 18:35:53 +0100132 const GURL& url)
Torne (Richard Coles)2a99a7e2013-03-28 15:31:22 +0000133 : session_tag(session_tag),
134 tab_id(tab_id),
Ben Murdocheb525c52013-07-10 11:40:50 +0100135 title(title),
Torne (Richard Coles)2a99a7e2013-03-28 15:31:22 +0000136 url(url) {}
137
138 // For use by std::set for sorting.
Torne (Richard Coles)c2e0dbd2013-05-09 18:35:53 +0100139 bool operator<(const TabNavigationItem& other) const {
Torne (Richard Coles)2a99a7e2013-03-28 15:31:22 +0000140 return url < other.url;
141 }
142
143 std::string session_tag; // Empty for local tabs, non-empty for foreign tabs.
144 SessionID::id_type tab_id; // -1 for invalid, >= 0 otherwise.
Ben Murdocheb525c52013-07-10 11:40:50 +0100145 string16 title;
Torne (Richard Coles)2a99a7e2013-03-28 15:31:22 +0000146 GURL url;
147};
148
Torne (Richard Coles)c2e0dbd2013-05-09 18:35:53 +0100149const int RecentTabsSubMenuModel::kRecentlyClosedHeaderCommandId = 500;
Torne (Richard Coles)7d4cd472013-06-19 11:58:07 +0100150const int RecentTabsSubMenuModel::kDisabledRecentlyClosedHeaderCommandId = 501;
151const int RecentTabsSubMenuModel::kDeviceNameCommandId = 1000;
Torne (Richard Coles)c2e0dbd2013-05-09 18:35:53 +0100152
Torne (Richard Coles)2a99a7e2013-03-28 15:31:22 +0000153RecentTabsSubMenuModel::RecentTabsSubMenuModel(
154 ui::AcceleratorProvider* accelerator_provider,
155 Browser* browser,
156 browser_sync::SessionModelAssociator* associator)
Torne (Richard Coles)c2e0dbd2013-05-09 18:35:53 +0100157 : ui::SimpleMenuModel(this),
Torne (Richard Coles)2a99a7e2013-03-28 15:31:22 +0000158 browser_(browser),
159 associator_(associator),
160 default_favicon_(ResourceBundle::GetSharedInstance().
161 GetNativeImageNamed(IDR_DEFAULT_FAVICON)),
Torne (Richard Coles)c2e0dbd2013-05-09 18:35:53 +0100162 weak_ptr_factory_(this) {
Torne (Richard Coles)2a99a7e2013-03-28 15:31:22 +0000163 Build();
164
165 // Retrieve accelerator key for IDC_RESTORE_TAB now, because on ASH, it's not
166 // defined in |accelerator_provider|, but in shell, so simply retrieve it now
167 // for all ASH and non-ASH for use in |GetAcceleratorForCommandId|.
168#if defined(USE_ASH)
169 for (size_t i = 0; i < ash::kAcceleratorDataLength; ++i) {
170 const ash::AcceleratorData& accel_data = ash::kAcceleratorData[i];
171 if (accel_data.action == ash::RESTORE_TAB) {
172 reopen_closed_tab_accelerator_ = ui::Accelerator(accel_data.keycode,
173 accel_data.modifiers);
174 break;
175 }
176 }
177#else
178 if (accelerator_provider) {
179 accelerator_provider->GetAcceleratorForCommandId(
180 IDC_RESTORE_TAB, &reopen_closed_tab_accelerator_);
181 }
182#endif // defined(USE_ASH)
183}
184
185RecentTabsSubMenuModel::~RecentTabsSubMenuModel() {
186}
187
188bool RecentTabsSubMenuModel::IsCommandIdChecked(int command_id) const {
189 return false;
190}
191
192bool RecentTabsSubMenuModel::IsCommandIdEnabled(int command_id) const {
Torne (Richard Coles)c2e0dbd2013-05-09 18:35:53 +0100193 if (command_id == kRecentlyClosedHeaderCommandId ||
Torne (Richard Coles)7d4cd472013-06-19 11:58:07 +0100194 command_id == kDisabledRecentlyClosedHeaderCommandId ||
195 command_id == kDeviceNameCommandId ||
Torne (Richard Coles)2a99a7e2013-03-28 15:31:22 +0000196 command_id == IDC_RECENT_TABS_NO_DEVICE_TABS) {
197 return false;
198 }
Torne (Richard Coles)c2e0dbd2013-05-09 18:35:53 +0100199 return true;
Torne (Richard Coles)2a99a7e2013-03-28 15:31:22 +0000200}
201
202bool RecentTabsSubMenuModel::GetAcceleratorForCommandId(
203 int command_id, ui::Accelerator* accelerator) {
Torne (Richard Coles)7d4cd472013-06-19 11:58:07 +0100204 // If there are no recently closed items, we show the accelerator beside
205 // the header, otherwise, we show it beside the first item underneath it.
206 int index_in_menu = GetIndexOfCommandId(command_id);
207 int header_index = GetIndexOfCommandId(kRecentlyClosedHeaderCommandId);
208 if ((command_id == kDisabledRecentlyClosedHeaderCommandId ||
209 (header_index != -1 && index_in_menu == header_index + 1)) &&
Torne (Richard Coles)2a99a7e2013-03-28 15:31:22 +0000210 reopen_closed_tab_accelerator_.key_code() != ui::VKEY_UNKNOWN) {
211 *accelerator = reopen_closed_tab_accelerator_;
212 return true;
213 }
214 return false;
215}
216
Torne (Richard Coles)2a99a7e2013-03-28 15:31:22 +0000217void RecentTabsSubMenuModel::ExecuteCommand(int command_id, int event_flags) {
Torne (Richard Coles)2a99a7e2013-03-28 15:31:22 +0000218 if (command_id == IDC_SHOW_HISTORY) {
Ben Murdocheb525c52013-07-10 11:40:50 +0100219 UMA_HISTOGRAM_ENUMERATION("WrenchMenu.RecentTabsSubMenu", SHOW_MORE,
220 LIMIT_RECENT_TAB_ACTION);
Torne (Richard Coles)2a99a7e2013-03-28 15:31:22 +0000221 // We show all "other devices" on the history page.
222 chrome::ExecuteCommandWithDisposition(browser_, IDC_SHOW_HISTORY,
223 ui::DispositionFromEventFlags(event_flags));
224 return;
225 }
226
Torne (Richard Coles)7d4cd472013-06-19 11:58:07 +0100227 DCHECK_NE(kDeviceNameCommandId, command_id);
Torne (Richard Coles)2a99a7e2013-03-28 15:31:22 +0000228 DCHECK_NE(IDC_RECENT_TABS_NO_DEVICE_TABS, command_id);
229
Torne (Richard Coles)2a99a7e2013-03-28 15:31:22 +0000230 WindowOpenDisposition disposition =
231 ui::DispositionFromEventFlags(event_flags);
232 if (disposition == CURRENT_TAB) // Force to open a new foreground tab.
233 disposition = NEW_FOREGROUND_TAB;
234
Torne (Richard Coles)c2e0dbd2013-05-09 18:35:53 +0100235 TabRestoreService* service =
236 TabRestoreServiceFactory::GetForProfile(browser_->profile());
237 TabRestoreServiceDelegate* delegate =
238 TabRestoreServiceDelegate::FindDelegateForWebContents(
239 browser_->tab_strip_model()->GetActiveWebContents());
240 if (IsTabModelCommandId(command_id)) {
241 int model_idx = CommandIdToTabModelIndex(command_id);
242 DCHECK(model_idx >= 0 &&
243 model_idx < static_cast<int>(tab_navigation_items_.size()));
244 const TabNavigationItem& item = tab_navigation_items_[model_idx];
245 DCHECK(item.tab_id > -1 && item.url.is_valid());
246
247 if (item.session_tag.empty()) { // Restore tab of local session.
248 if (service && delegate) {
Ben Murdocheb525c52013-07-10 11:40:50 +0100249 UMA_HISTOGRAM_ENUMERATION("WrenchMenu.RecentTabsSubMenu",
250 LOCAL_SESSION_TAB, LIMIT_RECENT_TAB_ACTION);
Torne (Richard Coles)c2e0dbd2013-05-09 18:35:53 +0100251 service->RestoreEntryById(delegate, item.tab_id,
252 browser_->host_desktop_type(), disposition);
253 }
254 } else { // Restore tab of foreign session.
255 browser_sync::SessionModelAssociator* associator = GetModelAssociator();
256 if (!associator)
257 return;
258 const SessionTab* tab;
259 if (!associator->GetForeignTab(item.session_tag, item.tab_id, &tab))
260 return;
261 if (tab->navigations.empty())
262 return;
Ben Murdocheb525c52013-07-10 11:40:50 +0100263 UMA_HISTOGRAM_ENUMERATION("WrenchMenu.RecentTabsSubMenu",
264 OTHER_DEVICE_TAB, LIMIT_RECENT_TAB_ACTION);
Torne (Richard Coles)c2e0dbd2013-05-09 18:35:53 +0100265 SessionRestore::RestoreForeignSessionTab(
266 browser_->tab_strip_model()->GetActiveWebContents(),
267 *tab, disposition);
268 }
269 } else {
270 DCHECK(IsWindowModelCommandId(command_id));
271 if (service && delegate) {
272 int model_idx = CommandIdToWindowModelIndex(command_id);
273 DCHECK(model_idx >= 0 &&
274 model_idx < static_cast<int>(window_items_.size()));
Ben Murdocheb525c52013-07-10 11:40:50 +0100275 UMA_HISTOGRAM_ENUMERATION("WrenchMenu.RecentTabsSubMenu", RESTORE_WINDOW,
276 LIMIT_RECENT_TAB_ACTION);
Torne (Richard Coles)c2e0dbd2013-05-09 18:35:53 +0100277 service->RestoreEntryById(delegate, window_items_[model_idx],
278 browser_->host_desktop_type(), disposition);
279 }
Torne (Richard Coles)2a99a7e2013-03-28 15:31:22 +0000280 }
281}
282
Torne (Richard Coles)7d4cd472013-06-19 11:58:07 +0100283const gfx::Font* RecentTabsSubMenuModel::GetLabelFontAt(int index) const {
284 int command_id = GetCommandIdAt(index);
285 if (command_id == kDeviceNameCommandId ||
286 command_id == kRecentlyClosedHeaderCommandId) {
287 return &ResourceBundle::GetSharedInstance().GetFont(
288 ResourceBundle::BoldFont);
289 }
290 return NULL;
291}
292
Torne (Richard Coles)2a99a7e2013-03-28 15:31:22 +0000293int RecentTabsSubMenuModel::GetMaxWidthForItemAtIndex(int item_index) const {
294 int command_id = GetCommandIdAt(item_index);
295 if (command_id == IDC_RECENT_TABS_NO_DEVICE_TABS ||
Torne (Richard Coles)7d4cd472013-06-19 11:58:07 +0100296 command_id == kRecentlyClosedHeaderCommandId ||
297 command_id == kDisabledRecentlyClosedHeaderCommandId) {
Torne (Richard Coles)2a99a7e2013-03-28 15:31:22 +0000298 return -1;
299 }
300 return 320;
301}
302
Ben Murdocheb525c52013-07-10 11:40:50 +0100303bool RecentTabsSubMenuModel::GetURLAndTitleForItemAtIndex(
304 int index,
305 std::string* url,
306 string16* title) const {
307 int command_id = GetCommandIdAt(index);
308 if (IsTabModelCommandId(command_id)) {
309 int model_idx = CommandIdToTabModelIndex(command_id);
310 DCHECK(model_idx >= 0 &&
311 model_idx < static_cast<int>(tab_navigation_items_.size()));
312 *url = tab_navigation_items_[model_idx].url.possibly_invalid_spec();
313 *title = tab_navigation_items_[model_idx].title;
314 return true;
315 }
316 return false;
317}
318
Torne (Richard Coles)2a99a7e2013-03-28 15:31:22 +0000319void RecentTabsSubMenuModel::Build() {
320 // The menu contains:
Torne (Richard Coles)c2e0dbd2013-05-09 18:35:53 +0100321 // - Recently closed tabs header, then list of tabs, then separator
Torne (Richard Coles)2a99a7e2013-03-28 15:31:22 +0000322 // - device 1 section header, then list of tabs from device, then separator
323 // - device 2 section header, then list of tabs from device, then separator
Torne (Richard Coles)c2e0dbd2013-05-09 18:35:53 +0100324 // - device 3 section header, then list of tabs from device, then separator
325 // - More... to open the history tab to get more other devices.
326 // |tab_navigation_items_| only contains navigatable (and hence executable)
327 // tab items for other devices, and |window_items_| contains the recently
328 // closed windows.
329 BuildRecentTabs();
Torne (Richard Coles)2a99a7e2013-03-28 15:31:22 +0000330 BuildDevices();
Torne (Richard Coles)c2e0dbd2013-05-09 18:35:53 +0100331}
332
333void RecentTabsSubMenuModel::BuildRecentTabs() {
334 ListValue recently_closed_list;
335 TabRestoreService* service =
336 TabRestoreServiceFactory::GetForProfile(browser_->profile());
Ben Murdocheb525c52013-07-10 11:40:50 +0100337 if (service) {
338 // This does nothing if the tabs have already been loaded or they
339 // shouldn't be loaded.
340 service->LoadTabsFromLastSession();
341 }
342
Torne (Richard Coles)c2e0dbd2013-05-09 18:35:53 +0100343 if (!service || service->entries().size() == 0) {
344 // This is to show a disabled restore tab entry with the accelerator to
345 // teach users about this command.
Torne (Richard Coles)7d4cd472013-06-19 11:58:07 +0100346 AddItemWithStringId(kDisabledRecentlyClosedHeaderCommandId,
347 IDS_NEW_TAB_RECENTLY_CLOSED);
Torne (Richard Coles)c2e0dbd2013-05-09 18:35:53 +0100348 return;
349 }
350
351 AddItemWithStringId(kRecentlyClosedHeaderCommandId,
352 IDS_NEW_TAB_RECENTLY_CLOSED);
353 ResourceBundle& rb = ResourceBundle::GetSharedInstance();
354 SetIcon(GetItemCount() - 1,
355 rb.GetNativeImageNamed(IDR_RECENTLY_CLOSED_WINDOW));
356
357 int added_count = 0;
358 TabRestoreService::Entries entries = service->entries();
359 for (TabRestoreService::Entries::const_iterator it = entries.begin();
360 it != entries.end() && added_count < kMaxRecentlyClosedEntries; ++it) {
361 TabRestoreService::Entry* entry = *it;
362 if (entry->type == TabRestoreService::TAB) {
363 TabRestoreService::Tab* tab = static_cast<TabRestoreService::Tab*>(entry);
364 const sessions::SerializedNavigationEntry& current_navigation =
365 tab->navigations.at(tab->current_navigation_index);
366 BuildLocalTabItem(
367 entry->id,
368 current_navigation.title(),
369 current_navigation.virtual_url());
370 } else {
371 DCHECK_EQ(entry->type, TabRestoreService::WINDOW);
372 BuildWindowItem(
373 entry->id,
374 static_cast<TabRestoreService::Window*>(entry)->tabs.size());
375 }
376 ++added_count;
Torne (Richard Coles)2a99a7e2013-03-28 15:31:22 +0000377 }
378}
379
380void RecentTabsSubMenuModel::BuildDevices() {
381 browser_sync::SessionModelAssociator* associator = GetModelAssociator();
Torne (Richard Coles)2a99a7e2013-03-28 15:31:22 +0000382 std::vector<const browser_sync::SyncedSession*> sessions;
Torne (Richard Coles)c2e0dbd2013-05-09 18:35:53 +0100383 if (!associator || !associator->GetAllForeignSessions(&sessions)) {
384 AddSeparator(ui::NORMAL_SEPARATOR);
385 AddItemWithStringId(IDC_RECENT_TABS_NO_DEVICE_TABS,
386 IDS_RECENT_TABS_NO_DEVICE_TABS);
Torne (Richard Coles)2a99a7e2013-03-28 15:31:22 +0000387 return;
Torne (Richard Coles)c2e0dbd2013-05-09 18:35:53 +0100388 }
Torne (Richard Coles)2a99a7e2013-03-28 15:31:22 +0000389
390 // Sort sessions from most recent to least recent.
391 std::sort(sessions.begin(), sessions.end(), SortSessionsByRecency);
392
393 const size_t kMaxSessionsToShow = 3;
394 size_t num_sessions_added = 0;
395 for (size_t i = 0;
396 i < sessions.size() && num_sessions_added < kMaxSessionsToShow; ++i) {
397 const browser_sync::SyncedSession* session = sessions[i];
398 const std::string& session_tag = session->session_tag;
399
400 // Get windows of session.
401 std::vector<const SessionWindow*> windows;
402 if (!associator->GetForeignSession(session_tag, &windows) ||
403 windows.empty()) {
404 continue;
405 }
406
407 // Collect tabs from all windows of session, pruning those that are not
408 // syncable or are NewTabPage, then sort them from most recent to least
409 // recent, independent of which window the tabs were from.
410 std::vector<const SessionTab*> tabs_in_session;
411 for (size_t j = 0; j < windows.size(); ++j) {
412 const SessionWindow* window = windows[j];
413 for (size_t t = 0; t < window->tabs.size(); ++t) {
414 const SessionTab* tab = window->tabs[t];
415 if (tab->navigations.empty())
416 continue;
Torne (Richard Coles)c2e0dbd2013-05-09 18:35:53 +0100417 const sessions::SerializedNavigationEntry& current_navigation =
Torne (Richard Coles)2a99a7e2013-03-28 15:31:22 +0000418 tab->navigations.at(tab->normalized_navigation_index());
Torne (Richard Coles)a36e5922013-08-05 13:57:33 +0100419 if (chrome::IsNTPURL(current_navigation.virtual_url(),
420 browser_->profile())) {
Torne (Richard Coles)2a99a7e2013-03-28 15:31:22 +0000421 continue;
422 }
423 tabs_in_session.push_back(tab);
424 }
425 }
426 if (tabs_in_session.empty())
427 continue;
428 std::sort(tabs_in_session.begin(), tabs_in_session.end(),
429 SortTabsByRecency);
430
431 // Add the header for the device session.
432 DCHECK(!session->session_name.empty());
433 AddSeparator(ui::NORMAL_SEPARATOR);
Torne (Richard Coles)7d4cd472013-06-19 11:58:07 +0100434 AddItem(kDeviceNameCommandId, UTF8ToUTF16(session->session_name));
Torne (Richard Coles)2a99a7e2013-03-28 15:31:22 +0000435 AddDeviceFavicon(GetItemCount() - 1, session->device_type);
436
437 // Build tab menu items from sorted session tabs.
438 const size_t kMaxTabsPerSessionToShow = 4;
439 for (size_t k = 0;
440 k < std::min(tabs_in_session.size(), kMaxTabsPerSessionToShow);
441 ++k) {
442 BuildForeignTabItem(session_tag, *tabs_in_session[k]);
443 } // for all tabs in one session
444
445 ++num_sessions_added;
446 } // for all sessions
447
448 // We are not supposed to get here unless at least some items were added.
449 DCHECK_GT(GetItemCount(), 0);
450 AddSeparator(ui::NORMAL_SEPARATOR);
451 AddItemWithStringId(IDC_SHOW_HISTORY, IDS_RECENT_TABS_MORE);
452}
453
Torne (Richard Coles)c2e0dbd2013-05-09 18:35:53 +0100454void RecentTabsSubMenuModel::BuildLocalTabItem(
455 int session_id,
456 const string16& title,
457 const GURL& url) {
Ben Murdocheb525c52013-07-10 11:40:50 +0100458 TabNavigationItem item("", session_id, title, url);
Torne (Richard Coles)c2e0dbd2013-05-09 18:35:53 +0100459 int command_id = TabModelIndexToCommandId(tab_navigation_items_.size());
460 // There may be no tab title, in which case, use the url as tab title.
461 AddItem(command_id, title.empty() ? UTF8ToUTF16(item.url.spec()) : title);
462 AddTabFavicon(tab_navigation_items_.size(), command_id, item.url);
463 tab_navigation_items_.push_back(item);
464}
465
Torne (Richard Coles)2a99a7e2013-03-28 15:31:22 +0000466void RecentTabsSubMenuModel::BuildForeignTabItem(
467 const std::string& session_tag,
468 const SessionTab& tab) {
Torne (Richard Coles)c2e0dbd2013-05-09 18:35:53 +0100469 const sessions::SerializedNavigationEntry& current_navigation =
Torne (Richard Coles)2a99a7e2013-03-28 15:31:22 +0000470 tab.navigations.at(tab.normalized_navigation_index());
Torne (Richard Coles)c2e0dbd2013-05-09 18:35:53 +0100471 TabNavigationItem item(session_tag, tab.tab_id.id(),
Ben Murdocheb525c52013-07-10 11:40:50 +0100472 current_navigation.title(),
Torne (Richard Coles)c2e0dbd2013-05-09 18:35:53 +0100473 current_navigation.virtual_url());
474 int command_id = TabModelIndexToCommandId(tab_navigation_items_.size());
Torne (Richard Coles)2a99a7e2013-03-28 15:31:22 +0000475 // There may be no tab title, in which case, use the url as tab title.
476 AddItem(command_id,
477 current_navigation.title().empty() ?
478 UTF8ToUTF16(item.url.spec()) : current_navigation.title());
Torne (Richard Coles)c2e0dbd2013-05-09 18:35:53 +0100479 AddTabFavicon(tab_navigation_items_.size(), command_id, item.url);
480 tab_navigation_items_.push_back(item);
481}
482
483void RecentTabsSubMenuModel::BuildWindowItem(
484 const SessionID::id_type& window_id,
485 int num_tabs) {
486 int command_id = WindowModelIndexToCommandId(window_items_.size());
487 if (num_tabs == 1) {
488 AddItemWithStringId(command_id, IDS_NEW_TAB_RECENTLY_CLOSED_WINDOW_SINGLE);
489 } else {
490 AddItem(command_id, l10n_util::GetStringFUTF16(
491 IDS_NEW_TAB_RECENTLY_CLOSED_WINDOW_MULTIPLE,
492 base::IntToString16(num_tabs)));
493 }
494 ResourceBundle& rb = ResourceBundle::GetSharedInstance();
495 SetIcon(GetItemCount() - 1,
496 rb.GetNativeImageNamed(IDR_RECENTLY_CLOSED_WINDOW));
497 window_items_.push_back(window_id);
Torne (Richard Coles)2a99a7e2013-03-28 15:31:22 +0000498}
499
500void RecentTabsSubMenuModel::AddDeviceFavicon(
501 int index_in_menu,
502 browser_sync::SyncedSession::DeviceType device_type) {
503 int favicon_id = -1;
504 switch (device_type) {
505 case browser_sync::SyncedSession::TYPE_PHONE:
506 favicon_id = IDR_PHONE_FAVICON;
507 break;
508
509 case browser_sync::SyncedSession::TYPE_TABLET:
510 favicon_id = IDR_TABLET_FAVICON;
511 break;
512
513 case browser_sync::SyncedSession::TYPE_CHROMEOS:
514 case browser_sync::SyncedSession::TYPE_WIN:
515 case browser_sync::SyncedSession::TYPE_MACOSX:
516 case browser_sync::SyncedSession::TYPE_LINUX:
517 case browser_sync::SyncedSession::TYPE_OTHER:
518 case browser_sync::SyncedSession::TYPE_UNSET:
519 favicon_id = IDR_LAPTOP_FAVICON;
520 break;
521 };
522
523 ResourceBundle& rb = ResourceBundle::GetSharedInstance();
524 SetIcon(index_in_menu, rb.GetNativeImageNamed(favicon_id));
525}
526
527void RecentTabsSubMenuModel::AddTabFavicon(int model_index,
528 int command_id,
529 const GURL& url) {
530 int index_in_menu = GetIndexOfCommandId(command_id);
531
532 // If tab has synced favicon, use it.
533 // Note that currently, foreign tab only has favicon if --sync-tab-favicons
534 // switch is on; according to zea@, this flag is now automatically enabled for
535 // iOS and android, and they're looking into enabling it for other platforms.
536 browser_sync::SessionModelAssociator* associator = GetModelAssociator();
537 scoped_refptr<base::RefCountedMemory> favicon_png;
538 if (associator &&
539 associator->GetSyncedFaviconForPageURL(url.spec(), &favicon_png)) {
540 gfx::Image image = gfx::Image::CreateFrom1xPNGBytes(
541 favicon_png->front(),
542 favicon_png->size());
543 SetIcon(index_in_menu, image);
544 return;
545 }
546
547 // Otherwise, start to fetch the favicon from local history asynchronously.
548 // Set default icon first.
549 SetIcon(index_in_menu, default_favicon_);
550 // Start request to fetch actual icon if possible.
551 FaviconService* favicon_service = FaviconServiceFactory::GetForProfile(
552 browser_->profile(), Profile::EXPLICIT_ACCESS);
553 if (!favicon_service)
554 return;
555
556 favicon_service->GetFaviconImageForURL(
557 FaviconService::FaviconForURLParams(browser_->profile(),
558 url,
Torne (Richard Coles)90dce4d2013-05-29 14:40:03 +0100559 chrome::FAVICON,
Torne (Richard Coles)2a99a7e2013-03-28 15:31:22 +0000560 gfx::kFaviconSize),
561 base::Bind(&RecentTabsSubMenuModel::OnFaviconDataAvailable,
562 weak_ptr_factory_.GetWeakPtr(),
563 command_id),
564 &cancelable_task_tracker_);
565}
566
567void RecentTabsSubMenuModel::OnFaviconDataAvailable(
568 int command_id,
Torne (Richard Coles)90dce4d2013-05-29 14:40:03 +0100569 const chrome::FaviconImageResult& image_result) {
Torne (Richard Coles)2a99a7e2013-03-28 15:31:22 +0000570 if (image_result.image.IsEmpty())
571 return;
Torne (Richard Coles)c2e0dbd2013-05-09 18:35:53 +0100572 DCHECK(!tab_navigation_items_.empty());
Torne (Richard Coles)2a99a7e2013-03-28 15:31:22 +0000573 int index_in_menu = GetIndexOfCommandId(command_id);
574 DCHECK(index_in_menu != -1);
575 SetIcon(index_in_menu, image_result.image);
576 if (GetMenuModelDelegate())
577 GetMenuModelDelegate()->OnIconChanged(index_in_menu);
578}
579
580browser_sync::SessionModelAssociator*
581 RecentTabsSubMenuModel::GetModelAssociator() {
582 if (!associator_) {
583 ProfileSyncService* service = ProfileSyncServiceFactory::GetInstance()->
584 GetForProfile(browser_->profile());
585 // Only return the associator if it exists and it is done syncing sessions.
586 if (service && service->ShouldPushChanges())
587 associator_ = service->GetSessionModelAssociator();
588 }
589 return associator_;
590}