Revert of Remove OneClickSigninHelper since it is no longer used. (patchset #5 id...
[chromium-blink-merge.git] / chrome / browser / ui / toolbar / recent_tabs_sub_menu_model.cc
blobc8e3d39c56db580c2ff303e023c4203962418cf9
1 // 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.
5 #include "chrome/browser/ui/toolbar/recent_tabs_sub_menu_model.h"
7 #include "base/bind.h"
8 #include "base/metrics/histogram.h"
9 #include "base/prefs/scoped_user_pref_update.h"
10 #include "base/strings/string_number_conversions.h"
11 #include "base/strings/utf_string_conversions.h"
12 #include "chrome/app/chrome_command_ids.h"
13 #include "chrome/browser/favicon/favicon_service_factory.h"
14 #include "chrome/browser/profiles/profile.h"
15 #include "chrome/browser/search/search.h"
16 #include "chrome/browser/sessions/session_restore.h"
17 #include "chrome/browser/sessions/tab_restore_service.h"
18 #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/synced_session.h"
21 #include "chrome/browser/sync/open_tabs_ui_delegate.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"
27 #include "chrome/browser/ui/toolbar/wrench_menu_model.h"
28 #include "chrome/grit/generated_resources.h"
29 #include "components/favicon_base/favicon_types.h"
30 #include "grit/browser_resources.h"
31 #include "grit/theme_resources.h"
32 #include "ui/base/accelerators/accelerator.h"
33 #include "ui/base/l10n/l10n_util.h"
34 #include "ui/base/resource/resource_bundle.h"
35 #include "ui/resources/grit/ui_resources.h"
37 #if defined(USE_ASH)
38 #include "ash/accelerators/accelerator_table.h"
39 #endif // defined(USE_ASH)
41 namespace {
43 // Initial comamnd ID's for navigatable (and hence executable) tab/window menu
44 // items. The menumodel and storage structures are not 1-1:
45 // - menumodel has "Recently closed" header, "No tabs from other devices",
46 // device section headers, separators, local and other devices' tab items, and
47 // local window items.
48 // - |local_tab_navigation_items_| and |other_devices_tab_navigation_items_|
49 // only have navigatabale/executable tab items.
50 // - |local_window_items_| only has executable open window items.
51 // Using initial command IDs for local tab, local window and other devices' tab
52 // items makes it easier and less error-prone to manipulate the menumodel and
53 // storage structures. These ids must be bigger than the maximum possible
54 // number of items in the menumodel, so that index of the last menu item doesn't
55 // clash with these values when menu items are retrieved via
56 // GetIndexOfCommandId().
57 // The range of all command ID's used in RecentTabsSubMenuModel, including the
58 // "Recently closed" headers, must be between
59 // |WrenchMenuModel::kMinRecentTabsCommandId| i.e. 1001 and 1200
60 // (|WrenchMenuModel::kMaxRecentTabsCommandId|) inclusively.
61 const int kFirstLocalTabCommandId = WrenchMenuModel::kMinRecentTabsCommandId;
62 const int kFirstLocalWindowCommandId = 1031;
63 const int kFirstOtherDevicesTabCommandId = 1051;
64 const int kMinDeviceNameCommandId = 1100;
65 const int kMaxDeviceNameCommandId = 1110;
67 // The maximum number of local recently closed entries (tab or window) to be
68 // shown in the menu.
69 const int kMaxLocalEntries = 8;
71 // Comparator function for use with std::sort that will sort sessions by
72 // descending modified_time (i.e., most recent first).
73 bool SortSessionsByRecency(const browser_sync::SyncedSession* s1,
74 const browser_sync::SyncedSession* s2) {
75 return s1->modified_time > s2->modified_time;
78 // Comparator function for use with std::sort that will sort tabs by
79 // descending timestamp (i.e., most recent first).
80 bool SortTabsByRecency(const sessions::SessionTab* t1,
81 const sessions::SessionTab* t2) {
82 return t1->timestamp > t2->timestamp;
85 // Returns true if the command id identifies a tab menu item.
86 bool IsTabModelCommandId(int command_id) {
87 return ((command_id >= kFirstLocalTabCommandId &&
88 command_id < kFirstLocalWindowCommandId) ||
89 (command_id >= kFirstOtherDevicesTabCommandId &&
90 command_id < kMinDeviceNameCommandId));
93 // Returns true if the command id identifies a window menu item.
94 bool IsWindowModelCommandId(int command_id) {
95 return command_id >= kFirstLocalWindowCommandId &&
96 command_id < kFirstOtherDevicesTabCommandId;
99 bool IsDeviceNameCommandId(int command_id) {
100 return command_id >= kMinDeviceNameCommandId &&
101 command_id <= kMaxDeviceNameCommandId;
104 // Convert |tab_vector_index| to command id of menu item, with
105 // |first_command_id| as the base command id.
106 int TabVectorIndexToCommandId(int tab_vector_index, int first_command_id) {
107 int command_id = tab_vector_index + first_command_id;
108 DCHECK(IsTabModelCommandId(command_id));
109 return command_id;
112 // Convert |window_vector_index| to command id of menu item.
113 int WindowVectorIndexToCommandId(int window_vector_index) {
114 int command_id = window_vector_index + kFirstLocalWindowCommandId;
115 DCHECK(IsWindowModelCommandId(command_id));
116 return command_id;
119 // Convert |command_id| of menu item to index in |local_window_items_|.
120 int CommandIdToWindowVectorIndex(int command_id) {
121 DCHECK(IsWindowModelCommandId(command_id));
122 return command_id - kFirstLocalWindowCommandId;
125 } // namespace
127 enum RecentTabAction {
128 LOCAL_SESSION_TAB = 0,
129 OTHER_DEVICE_TAB,
130 RESTORE_WINDOW,
131 SHOW_MORE,
132 LIMIT_RECENT_TAB_ACTION
135 // An element in |RecentTabsSubMenuModel::local_tab_navigation_items_| or
136 // |RecentTabsSubMenuModel::other_devices_tab_navigation_items_| that stores
137 // the navigation information of a local or other devices' tab required to
138 // restore the tab.
139 struct RecentTabsSubMenuModel::TabNavigationItem {
140 TabNavigationItem() : tab_id(-1) {}
142 TabNavigationItem(const std::string& session_tag,
143 const SessionID::id_type& tab_id,
144 const base::string16& title,
145 const GURL& url)
146 : session_tag(session_tag),
147 tab_id(tab_id),
148 title(title),
149 url(url) {}
151 // For use by std::set for sorting.
152 bool operator<(const TabNavigationItem& other) const {
153 return url < other.url;
156 // Empty for local tabs, non-empty for other devices' tabs.
157 std::string session_tag;
158 SessionID::id_type tab_id; // -1 for invalid, >= 0 otherwise.
159 base::string16 title;
160 GURL url;
163 const int RecentTabsSubMenuModel::kRecentlyClosedHeaderCommandId = 1120;
164 const int RecentTabsSubMenuModel::kDisabledRecentlyClosedHeaderCommandId = 1121;
166 RecentTabsSubMenuModel::RecentTabsSubMenuModel(
167 ui::AcceleratorProvider* accelerator_provider,
168 Browser* browser,
169 browser_sync::OpenTabsUIDelegate* open_tabs_delegate)
170 : ui::SimpleMenuModel(this),
171 browser_(browser),
172 open_tabs_delegate_(open_tabs_delegate),
173 last_local_model_index_(-1),
174 default_favicon_(ui::ResourceBundle::GetSharedInstance().
175 GetNativeImageNamed(IDR_DEFAULT_FAVICON)),
176 weak_ptr_factory_(this) {
177 // Invoke asynchronous call to load tabs from local last session, which does
178 // nothing if the tabs have already been loaded or they shouldn't be loaded.
179 // TabRestoreServiceChanged() will be called after the tabs are loaded.
180 TabRestoreService* service =
181 TabRestoreServiceFactory::GetForProfile(browser_->profile());
182 if (service) {
183 service->LoadTabsFromLastSession();
185 // TODO(sail): enable this when mac implements the dynamic menu, together with
186 // MenuModelDelegate::MenuStructureChanged().
187 #if !defined(OS_MACOSX)
188 service->AddObserver(this);
189 #endif
192 Build();
194 // Retrieve accelerator key for IDC_RESTORE_TAB now, because on ASH, it's not
195 // defined in |accelerator_provider|, but in shell, so simply retrieve it now
196 // for all ASH and non-ASH for use in |GetAcceleratorForCommandId|.
197 #if defined(USE_ASH)
198 for (size_t i = 0; i < ash::kAcceleratorDataLength; ++i) {
199 const ash::AcceleratorData& accel_data = ash::kAcceleratorData[i];
200 if (accel_data.action == ash::RESTORE_TAB) {
201 reopen_closed_tab_accelerator_ = ui::Accelerator(accel_data.keycode,
202 accel_data.modifiers);
203 break;
206 #else
207 if (accelerator_provider) {
208 accelerator_provider->GetAcceleratorForCommandId(
209 IDC_RESTORE_TAB, &reopen_closed_tab_accelerator_);
211 #endif // defined(USE_ASH)
214 RecentTabsSubMenuModel::~RecentTabsSubMenuModel() {
215 TabRestoreService* service =
216 TabRestoreServiceFactory::GetForProfile(browser_->profile());
217 if (service)
218 service->RemoveObserver(this);
221 bool RecentTabsSubMenuModel::IsCommandIdChecked(int command_id) const {
222 return false;
225 bool RecentTabsSubMenuModel::IsCommandIdEnabled(int command_id) const {
226 if (command_id == kRecentlyClosedHeaderCommandId ||
227 command_id == kDisabledRecentlyClosedHeaderCommandId ||
228 command_id == IDC_RECENT_TABS_NO_DEVICE_TABS ||
229 IsDeviceNameCommandId(command_id)) {
230 return false;
232 return true;
235 bool RecentTabsSubMenuModel::GetAcceleratorForCommandId(
236 int command_id, ui::Accelerator* accelerator) {
237 // If there are no recently closed items, we show the accelerator beside
238 // the header, otherwise, we show it beside the first item underneath it.
239 int index_in_menu = GetIndexOfCommandId(command_id);
240 int header_index = GetIndexOfCommandId(kRecentlyClosedHeaderCommandId);
241 if ((command_id == kDisabledRecentlyClosedHeaderCommandId ||
242 (header_index != -1 && index_in_menu == header_index + 1)) &&
243 reopen_closed_tab_accelerator_.key_code() != ui::VKEY_UNKNOWN) {
244 *accelerator = reopen_closed_tab_accelerator_;
245 return true;
247 return false;
250 void RecentTabsSubMenuModel::ExecuteCommand(int command_id, int event_flags) {
251 if (command_id == IDC_SHOW_HISTORY) {
252 UMA_HISTOGRAM_ENUMERATION("WrenchMenu.RecentTabsSubMenu", SHOW_MORE,
253 LIMIT_RECENT_TAB_ACTION);
254 // We show all "other devices" on the history page.
255 chrome::ExecuteCommandWithDisposition(browser_, IDC_SHOW_HISTORY,
256 ui::DispositionFromEventFlags(event_flags));
257 return;
260 DCHECK_NE(IDC_RECENT_TABS_NO_DEVICE_TABS, command_id);
261 DCHECK(!IsDeviceNameCommandId(command_id));
263 WindowOpenDisposition disposition =
264 ui::DispositionFromEventFlags(event_flags);
265 if (disposition == CURRENT_TAB) // Force to open a new foreground tab.
266 disposition = NEW_FOREGROUND_TAB;
268 TabRestoreService* service =
269 TabRestoreServiceFactory::GetForProfile(browser_->profile());
270 TabRestoreServiceDelegate* delegate =
271 TabRestoreServiceDelegate::FindDelegateForWebContents(
272 browser_->tab_strip_model()->GetActiveWebContents());
273 if (IsTabModelCommandId(command_id)) {
274 TabNavigationItems* tab_items = NULL;
275 int tab_items_idx = CommandIdToTabVectorIndex(command_id, &tab_items);
276 const TabNavigationItem& item = (*tab_items)[tab_items_idx];
277 DCHECK(item.tab_id > -1 && item.url.is_valid());
279 if (item.session_tag.empty()) { // Restore tab of local session.
280 if (service && delegate) {
281 UMA_HISTOGRAM_ENUMERATION("WrenchMenu.RecentTabsSubMenu",
282 LOCAL_SESSION_TAB, LIMIT_RECENT_TAB_ACTION);
283 service->RestoreEntryById(delegate, item.tab_id,
284 browser_->host_desktop_type(), disposition);
286 } else { // Restore tab of session from other devices.
287 browser_sync::OpenTabsUIDelegate* open_tabs = GetOpenTabsUIDelegate();
288 if (!open_tabs)
289 return;
290 const sessions::SessionTab* tab;
291 if (!open_tabs->GetForeignTab(item.session_tag, item.tab_id, &tab))
292 return;
293 if (tab->navigations.empty())
294 return;
295 UMA_HISTOGRAM_ENUMERATION("WrenchMenu.RecentTabsSubMenu",
296 OTHER_DEVICE_TAB, LIMIT_RECENT_TAB_ACTION);
297 SessionRestore::RestoreForeignSessionTab(
298 browser_->tab_strip_model()->GetActiveWebContents(),
299 *tab, disposition);
301 } else {
302 DCHECK(IsWindowModelCommandId(command_id));
303 if (service && delegate) {
304 int window_items_idx = CommandIdToWindowVectorIndex(command_id);
305 DCHECK(window_items_idx >= 0 &&
306 window_items_idx < static_cast<int>(local_window_items_.size()));
307 UMA_HISTOGRAM_ENUMERATION("WrenchMenu.RecentTabsSubMenu", RESTORE_WINDOW,
308 LIMIT_RECENT_TAB_ACTION);
309 service->RestoreEntryById(delegate, local_window_items_[window_items_idx],
310 browser_->host_desktop_type(), disposition);
313 UMA_HISTOGRAM_MEDIUM_TIMES("WrenchMenu.TimeToAction.OpenRecentTab",
314 menu_opened_timer_.Elapsed());
315 UMA_HISTOGRAM_ENUMERATION("WrenchMenu.MenuAction", MENU_ACTION_RECENT_TAB,
316 LIMIT_MENU_ACTION);
319 int RecentTabsSubMenuModel::GetFirstRecentTabsCommandId() {
320 return WindowVectorIndexToCommandId(0);
323 const gfx::FontList* RecentTabsSubMenuModel::GetLabelFontListAt(
324 int index) const {
325 int command_id = GetCommandIdAt(index);
326 if (command_id == kRecentlyClosedHeaderCommandId ||
327 IsDeviceNameCommandId(command_id)) {
328 return &ui::ResourceBundle::GetSharedInstance().GetFontList(
329 ui::ResourceBundle::BoldFont);
331 return NULL;
334 int RecentTabsSubMenuModel::GetMaxWidthForItemAtIndex(int item_index) const {
335 int command_id = GetCommandIdAt(item_index);
336 if (command_id == IDC_RECENT_TABS_NO_DEVICE_TABS ||
337 command_id == kRecentlyClosedHeaderCommandId ||
338 command_id == kDisabledRecentlyClosedHeaderCommandId) {
339 return -1;
341 return 320;
344 bool RecentTabsSubMenuModel::GetURLAndTitleForItemAtIndex(
345 int index,
346 std::string* url,
347 base::string16* title) {
348 int command_id = GetCommandIdAt(index);
349 if (IsTabModelCommandId(command_id)) {
350 TabNavigationItems* tab_items = NULL;
351 int tab_items_idx = CommandIdToTabVectorIndex(command_id, &tab_items);
352 const TabNavigationItem& item = (*tab_items)[tab_items_idx];
353 *url = item.url.possibly_invalid_spec();
354 *title = item.title;
355 return true;
357 return false;
360 void RecentTabsSubMenuModel::Build() {
361 // The menu contains:
362 // - Recently closed header, then list of local recently closed tabs/windows,
363 // then separator
364 // - device 1 section header, then list of tabs from device, then separator
365 // - device 2 section header, then list of tabs from device, then separator
366 // - device 3 section header, then list of tabs from device, then separator
367 // - More... to open the history tab to get more other devices.
368 // |local_tab_navigation_items_| and |other_devices_tab_navigation_items_|
369 // only contain navigatable (and hence executable) tab items for local
370 // recently closed tabs and tabs from other devices respectively.
371 // |local_window_items_| contains the local recently closed windows.
372 BuildLocalEntries();
373 BuildTabsFromOtherDevices();
376 void RecentTabsSubMenuModel::BuildLocalEntries() {
377 // All local items use InsertItem*At() to append or insert a menu item.
378 // We're appending if building the entries for the first time i.e. invoked
379 // from Constructor(), inserting when local entries change subsequently i.e.
380 // invoked from TabRestoreServiceChanged().
382 DCHECK_EQ(last_local_model_index_, -1);
384 TabRestoreService* service =
385 TabRestoreServiceFactory::GetForProfile(browser_->profile());
386 if (!service || service->entries().size() == 0) {
387 // This is to show a disabled restore tab entry with the accelerator to
388 // teach users about this command.
389 InsertItemWithStringIdAt(++last_local_model_index_,
390 kDisabledRecentlyClosedHeaderCommandId,
391 IDS_NEW_TAB_RECENTLY_CLOSED);
392 } else {
393 InsertItemWithStringIdAt(++last_local_model_index_,
394 kRecentlyClosedHeaderCommandId,
395 IDS_NEW_TAB_RECENTLY_CLOSED);
396 ui::ResourceBundle& rb = ui::ResourceBundle::GetSharedInstance();
397 SetIcon(last_local_model_index_,
398 rb.GetNativeImageNamed(IDR_RECENTLY_CLOSED_WINDOW));
400 int added_count = 0;
401 TabRestoreService::Entries entries = service->entries();
402 for (TabRestoreService::Entries::const_iterator it = entries.begin();
403 it != entries.end() && added_count < kMaxLocalEntries; ++it) {
404 TabRestoreService::Entry* entry = *it;
405 if (entry->type == TabRestoreService::TAB) {
406 TabRestoreService::Tab* tab =
407 static_cast<TabRestoreService::Tab*>(entry);
408 const sessions::SerializedNavigationEntry& current_navigation =
409 tab->navigations.at(tab->current_navigation_index);
410 BuildLocalTabItem(
411 entry->id,
412 current_navigation.title(),
413 current_navigation.virtual_url(),
414 ++last_local_model_index_);
415 } else {
416 DCHECK_EQ(entry->type, TabRestoreService::WINDOW);
417 BuildLocalWindowItem(
418 entry->id,
419 static_cast<TabRestoreService::Window*>(entry)->tabs.size(),
420 ++last_local_model_index_);
422 ++added_count;
426 DCHECK_GE(last_local_model_index_, 0);
429 void RecentTabsSubMenuModel::BuildTabsFromOtherDevices() {
430 // All other devices' items (device headers or tabs) use AddItem*() to append
431 // a menu item, because they are always only built once (i.e. invoked from
432 // Constructor()) and don't change after that.
434 browser_sync::OpenTabsUIDelegate* open_tabs = GetOpenTabsUIDelegate();
435 std::vector<const browser_sync::SyncedSession*> sessions;
436 if (!open_tabs || !open_tabs->GetAllForeignSessions(&sessions)) {
437 AddSeparator(ui::NORMAL_SEPARATOR);
438 AddItemWithStringId(IDC_RECENT_TABS_NO_DEVICE_TABS,
439 IDS_RECENT_TABS_NO_DEVICE_TABS);
440 return;
443 // Sort sessions from most recent to least recent.
444 std::sort(sessions.begin(), sessions.end(), SortSessionsByRecency);
446 const size_t kMaxSessionsToShow = 3;
447 size_t num_sessions_added = 0;
448 for (size_t i = 0;
449 i < sessions.size() && num_sessions_added < kMaxSessionsToShow; ++i) {
450 const browser_sync::SyncedSession* session = sessions[i];
451 const std::string& session_tag = session->session_tag;
453 // Get windows of session.
454 std::vector<const sessions::SessionWindow*> windows;
455 if (!open_tabs->GetForeignSession(session_tag, &windows) ||
456 windows.empty()) {
457 continue;
460 // Collect tabs from all windows of session, pruning those that are not
461 // syncable or are NewTabPage, then sort them from most recent to least
462 // recent, independent of which window the tabs were from.
463 std::vector<const sessions::SessionTab*> tabs_in_session;
464 for (size_t j = 0; j < windows.size(); ++j) {
465 const sessions::SessionWindow* window = windows[j];
466 for (size_t t = 0; t < window->tabs.size(); ++t) {
467 const sessions::SessionTab* tab = window->tabs[t];
468 if (tab->navigations.empty())
469 continue;
470 const sessions::SerializedNavigationEntry& current_navigation =
471 tab->navigations.at(tab->normalized_navigation_index());
472 if (chrome::IsNTPURL(current_navigation.virtual_url(),
473 browser_->profile())) {
474 continue;
476 tabs_in_session.push_back(tab);
479 if (tabs_in_session.empty())
480 continue;
481 std::sort(tabs_in_session.begin(), tabs_in_session.end(),
482 SortTabsByRecency);
484 // Add the header for the device session.
485 DCHECK(!session->session_name.empty());
486 AddSeparator(ui::NORMAL_SEPARATOR);
487 int command_id = kMinDeviceNameCommandId + i;
488 DCHECK_LE(command_id, kMaxDeviceNameCommandId);
489 AddItem(command_id, base::UTF8ToUTF16(session->session_name));
490 AddDeviceFavicon(GetItemCount() - 1, session->device_type);
492 // Build tab menu items from sorted session tabs.
493 const size_t kMaxTabsPerSessionToShow = 4;
494 for (size_t k = 0;
495 k < std::min(tabs_in_session.size(), kMaxTabsPerSessionToShow);
496 ++k) {
497 BuildOtherDevicesTabItem(session_tag, *tabs_in_session[k]);
498 } // for all tabs in one session
500 ++num_sessions_added;
501 } // for all sessions
503 // We are not supposed to get here unless at least some items were added.
504 DCHECK_GT(GetItemCount(), 0);
505 AddSeparator(ui::NORMAL_SEPARATOR);
506 AddItemWithStringId(IDC_SHOW_HISTORY, IDS_RECENT_TABS_MORE);
509 void RecentTabsSubMenuModel::BuildLocalTabItem(int session_id,
510 const base::string16& title,
511 const GURL& url,
512 int curr_model_index) {
513 TabNavigationItem item(std::string(), session_id, title, url);
514 int command_id = TabVectorIndexToCommandId(
515 local_tab_navigation_items_.size(), kFirstLocalTabCommandId);
516 // See comments in BuildLocalEntries() about usage of InsertItem*At().
517 // There may be no tab title, in which case, use the url as tab title.
518 InsertItemAt(curr_model_index, command_id,
519 title.empty() ? base::UTF8ToUTF16(item.url.spec()) : title);
520 AddTabFavicon(command_id, item.url);
521 local_tab_navigation_items_.push_back(item);
524 void RecentTabsSubMenuModel::BuildLocalWindowItem(
525 const SessionID::id_type& window_id,
526 int num_tabs,
527 int curr_model_index) {
528 int command_id = WindowVectorIndexToCommandId(local_window_items_.size());
529 // See comments in BuildLocalEntries() about usage of InsertItem*At().
530 if (num_tabs == 1) {
531 InsertItemWithStringIdAt(curr_model_index, command_id,
532 IDS_NEW_TAB_RECENTLY_CLOSED_WINDOW_SINGLE);
533 } else {
534 InsertItemAt(curr_model_index, command_id, l10n_util::GetStringFUTF16(
535 IDS_NEW_TAB_RECENTLY_CLOSED_WINDOW_MULTIPLE,
536 base::IntToString16(num_tabs)));
538 ui::ResourceBundle& rb = ui::ResourceBundle::GetSharedInstance();
539 SetIcon(curr_model_index, rb.GetNativeImageNamed(IDR_RECENTLY_CLOSED_WINDOW));
540 local_window_items_.push_back(window_id);
543 void RecentTabsSubMenuModel::BuildOtherDevicesTabItem(
544 const std::string& session_tag,
545 const sessions::SessionTab& tab) {
546 const sessions::SerializedNavigationEntry& current_navigation =
547 tab.navigations.at(tab.normalized_navigation_index());
548 TabNavigationItem item(session_tag, tab.tab_id.id(),
549 current_navigation.title(),
550 current_navigation.virtual_url());
551 int command_id = TabVectorIndexToCommandId(
552 other_devices_tab_navigation_items_.size(),
553 kFirstOtherDevicesTabCommandId);
554 // See comments in BuildTabsFromOtherDevices() about usage of AddItem*().
555 // There may be no tab title, in which case, use the url as tab title.
556 AddItem(command_id,
557 current_navigation.title().empty() ?
558 base::UTF8ToUTF16(item.url.spec()) : current_navigation.title());
559 AddTabFavicon(command_id, item.url);
560 other_devices_tab_navigation_items_.push_back(item);
563 void RecentTabsSubMenuModel::AddDeviceFavicon(
564 int index_in_menu,
565 browser_sync::SyncedSession::DeviceType device_type) {
566 int favicon_id = -1;
567 switch (device_type) {
568 case browser_sync::SyncedSession::TYPE_PHONE:
569 favicon_id = IDR_PHONE_FAVICON;
570 break;
572 case browser_sync::SyncedSession::TYPE_TABLET:
573 favicon_id = IDR_TABLET_FAVICON;
574 break;
576 case browser_sync::SyncedSession::TYPE_CHROMEOS:
577 case browser_sync::SyncedSession::TYPE_WIN:
578 case browser_sync::SyncedSession::TYPE_MACOSX:
579 case browser_sync::SyncedSession::TYPE_LINUX:
580 case browser_sync::SyncedSession::TYPE_OTHER:
581 case browser_sync::SyncedSession::TYPE_UNSET:
582 favicon_id = IDR_LAPTOP_FAVICON;
583 break;
586 ui::ResourceBundle& rb = ui::ResourceBundle::GetSharedInstance();
587 SetIcon(index_in_menu, rb.GetNativeImageNamed(favicon_id));
590 void RecentTabsSubMenuModel::AddTabFavicon(int command_id, const GURL& url) {
591 bool is_local_tab = command_id < kFirstOtherDevicesTabCommandId;
592 int index_in_menu = GetIndexOfCommandId(command_id);
594 if (!is_local_tab) {
595 // If tab has synced favicon, use it.
596 // Note that currently, other devices' tabs only have favicons if
597 // --sync-tab-favicons switch is on; according to zea@, this flag is now
598 // automatically enabled for iOS and android, and they're looking into
599 // enabling it for other platforms.
600 browser_sync::OpenTabsUIDelegate* open_tabs = GetOpenTabsUIDelegate();
601 scoped_refptr<base::RefCountedMemory> favicon_png;
602 if (open_tabs &&
603 open_tabs->GetSyncedFaviconForPageURL(url.spec(), &favicon_png)) {
604 gfx::Image image = gfx::Image::CreateFrom1xPNGBytes(favicon_png);
605 SetIcon(index_in_menu, image);
606 return;
610 // Otherwise, start to fetch the favicon from local history asynchronously.
611 // Set default icon first.
612 SetIcon(index_in_menu, default_favicon_);
613 // Start request to fetch actual icon if possible.
614 FaviconService* favicon_service = FaviconServiceFactory::GetForProfile(
615 browser_->profile(), ServiceAccessType::EXPLICIT_ACCESS);
616 if (!favicon_service)
617 return;
619 favicon_service->GetFaviconImageForPageURL(
620 url,
621 base::Bind(&RecentTabsSubMenuModel::OnFaviconDataAvailable,
622 weak_ptr_factory_.GetWeakPtr(),
623 command_id),
624 is_local_tab ? &local_tab_cancelable_task_tracker_
625 : &other_devices_tab_cancelable_task_tracker_);
628 void RecentTabsSubMenuModel::OnFaviconDataAvailable(
629 int command_id,
630 const favicon_base::FaviconImageResult& image_result) {
631 if (image_result.image.IsEmpty())
632 return;
633 int index_in_menu = GetIndexOfCommandId(command_id);
634 DCHECK_GT(index_in_menu, -1);
635 SetIcon(index_in_menu, image_result.image);
636 ui::MenuModelDelegate* menu_model_delegate = GetMenuModelDelegate();
637 if (menu_model_delegate)
638 menu_model_delegate->OnIconChanged(index_in_menu);
641 int RecentTabsSubMenuModel::CommandIdToTabVectorIndex(
642 int command_id,
643 TabNavigationItems** tab_items) {
644 DCHECK(IsTabModelCommandId(command_id));
645 if (command_id >= kFirstOtherDevicesTabCommandId) {
646 *tab_items = &other_devices_tab_navigation_items_;
647 return command_id - kFirstOtherDevicesTabCommandId;
649 *tab_items = &local_tab_navigation_items_;
650 return command_id - kFirstLocalTabCommandId;
653 void RecentTabsSubMenuModel::ClearLocalEntries() {
654 // Remove local items (recently closed tabs and windows) from menumodel.
655 while (last_local_model_index_ >= 0)
656 RemoveItemAt(last_local_model_index_--);
658 // Cancel asynchronous FaviconService::GetFaviconImageForPageURL() tasks of
659 // all
660 // local tabs.
661 local_tab_cancelable_task_tracker_.TryCancelAll();
663 // Remove all local tab navigation items.
664 local_tab_navigation_items_.clear();
666 // Remove all local window items.
667 local_window_items_.clear();
670 browser_sync::OpenTabsUIDelegate*
671 RecentTabsSubMenuModel::GetOpenTabsUIDelegate() {
672 if (!open_tabs_delegate_) {
673 ProfileSyncService* service = ProfileSyncServiceFactory::GetInstance()->
674 GetForProfile(browser_->profile());
675 // Only return the delegate if it exists and it is done syncing sessions.
676 if (service && service->SyncActive())
677 open_tabs_delegate_ = service->GetOpenTabsUIDelegate();
679 return open_tabs_delegate_;
682 void RecentTabsSubMenuModel::TabRestoreServiceChanged(
683 TabRestoreService* service) {
684 ClearLocalEntries();
686 BuildLocalEntries();
688 ui::MenuModelDelegate* menu_model_delegate = GetMenuModelDelegate();
689 if (menu_model_delegate)
690 menu_model_delegate->OnMenuStructureChanged();
693 void RecentTabsSubMenuModel::TabRestoreServiceDestroyed(
694 TabRestoreService* service) {
695 TabRestoreServiceChanged(service);