Don't show supervised user as "already on this device" while they're being imported.
[chromium-blink-merge.git] / chrome / browser / ui / toolbar / recent_tabs_sub_menu_model.cc
blob8b4a2f5759953e39851e8a4d03bf48a97dc59c9e
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 "content/public/browser/user_metrics.h"
31 #include "grit/browser_resources.h"
32 #include "grit/theme_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/resources/grit/ui_resources.h"
38 #if defined(USE_ASH)
39 #include "ash/accelerators/accelerator_table.h"
40 #endif // defined(USE_ASH)
42 namespace {
44 // Initial comamnd ID's for navigatable (and hence executable) tab/window menu
45 // items. The menumodel and storage structures are not 1-1:
46 // - menumodel has "Recently closed" header, "No tabs from other devices",
47 // device section headers, separators, local and other devices' tab items, and
48 // local window items.
49 // - |local_tab_navigation_items_| and |other_devices_tab_navigation_items_|
50 // only have navigatabale/executable tab items.
51 // - |local_window_items_| only has executable open window items.
52 // Using initial command IDs for local tab, local window and other devices' tab
53 // items makes it easier and less error-prone to manipulate the menumodel and
54 // storage structures. These ids must be bigger than the maximum possible
55 // number of items in the menumodel, so that index of the last menu item doesn't
56 // clash with these values when menu items are retrieved via
57 // GetIndexOfCommandId().
58 // The range of all command ID's used in RecentTabsSubMenuModel, including the
59 // "Recently closed" headers, must be between
60 // |WrenchMenuModel::kMinRecentTabsCommandId| i.e. 1001 and 1200
61 // (|WrenchMenuModel::kMaxRecentTabsCommandId|) inclusively.
62 const int kFirstLocalTabCommandId = WrenchMenuModel::kMinRecentTabsCommandId;
63 const int kFirstLocalWindowCommandId = 1031;
64 const int kFirstOtherDevicesTabCommandId = 1051;
65 const int kMinDeviceNameCommandId = 1100;
66 const int kMaxDeviceNameCommandId = 1110;
68 // The maximum number of local recently closed entries (tab or window) to be
69 // shown in the menu.
70 const int kMaxLocalEntries = 8;
72 // Comparator function for use with std::sort that will sort sessions by
73 // descending modified_time (i.e., most recent first).
74 bool SortSessionsByRecency(const browser_sync::SyncedSession* s1,
75 const browser_sync::SyncedSession* s2) {
76 return s1->modified_time > s2->modified_time;
79 // Comparator function for use with std::sort that will sort tabs by
80 // descending timestamp (i.e., most recent first).
81 bool SortTabsByRecency(const sessions::SessionTab* t1,
82 const sessions::SessionTab* t2) {
83 return t1->timestamp > t2->timestamp;
86 // Returns true if the command id identifies a tab menu item.
87 bool IsTabModelCommandId(int command_id) {
88 return ((command_id >= kFirstLocalTabCommandId &&
89 command_id < kFirstLocalWindowCommandId) ||
90 (command_id >= kFirstOtherDevicesTabCommandId &&
91 command_id < kMinDeviceNameCommandId));
94 // Returns true if the command id identifies a window menu item.
95 bool IsWindowModelCommandId(int command_id) {
96 return command_id >= kFirstLocalWindowCommandId &&
97 command_id < kFirstOtherDevicesTabCommandId;
100 bool IsDeviceNameCommandId(int command_id) {
101 return command_id >= kMinDeviceNameCommandId &&
102 command_id <= kMaxDeviceNameCommandId;
105 // Convert |tab_vector_index| to command id of menu item, with
106 // |first_command_id| as the base command id.
107 int TabVectorIndexToCommandId(int tab_vector_index, int first_command_id) {
108 int command_id = tab_vector_index + first_command_id;
109 DCHECK(IsTabModelCommandId(command_id));
110 return command_id;
113 // Convert |window_vector_index| to command id of menu item.
114 int WindowVectorIndexToCommandId(int window_vector_index) {
115 int command_id = window_vector_index + kFirstLocalWindowCommandId;
116 DCHECK(IsWindowModelCommandId(command_id));
117 return command_id;
120 // Convert |command_id| of menu item to index in |local_window_items_|.
121 int CommandIdToWindowVectorIndex(int command_id) {
122 DCHECK(IsWindowModelCommandId(command_id));
123 return command_id - kFirstLocalWindowCommandId;
126 } // namespace
128 enum RecentTabAction {
129 LOCAL_SESSION_TAB = 0,
130 OTHER_DEVICE_TAB,
131 RESTORE_WINDOW,
132 SHOW_MORE,
133 LIMIT_RECENT_TAB_ACTION
136 // An element in |RecentTabsSubMenuModel::local_tab_navigation_items_| or
137 // |RecentTabsSubMenuModel::other_devices_tab_navigation_items_| that stores
138 // the navigation information of a local or other devices' tab required to
139 // restore the tab.
140 struct RecentTabsSubMenuModel::TabNavigationItem {
141 TabNavigationItem() : tab_id(-1) {}
143 TabNavigationItem(const std::string& session_tag,
144 const SessionID::id_type& tab_id,
145 const base::string16& title,
146 const GURL& url)
147 : session_tag(session_tag),
148 tab_id(tab_id),
149 title(title),
150 url(url) {}
152 // For use by std::set for sorting.
153 bool operator<(const TabNavigationItem& other) const {
154 return url < other.url;
157 // Empty for local tabs, non-empty for other devices' tabs.
158 std::string session_tag;
159 SessionID::id_type tab_id; // -1 for invalid, >= 0 otherwise.
160 base::string16 title;
161 GURL url;
164 const int RecentTabsSubMenuModel::kRecentlyClosedHeaderCommandId = 1120;
165 const int RecentTabsSubMenuModel::kDisabledRecentlyClosedHeaderCommandId = 1121;
167 RecentTabsSubMenuModel::RecentTabsSubMenuModel(
168 ui::AcceleratorProvider* accelerator_provider,
169 Browser* browser,
170 browser_sync::OpenTabsUIDelegate* open_tabs_delegate)
171 : ui::SimpleMenuModel(this),
172 browser_(browser),
173 open_tabs_delegate_(open_tabs_delegate),
174 last_local_model_index_(-1),
175 default_favicon_(ui::ResourceBundle::GetSharedInstance().
176 GetNativeImageNamed(IDR_DEFAULT_FAVICON)),
177 weak_ptr_factory_(this) {
178 // Invoke asynchronous call to load tabs from local last session, which does
179 // nothing if the tabs have already been loaded or they shouldn't be loaded.
180 // TabRestoreServiceChanged() will be called after the tabs are loaded.
181 TabRestoreService* service =
182 TabRestoreServiceFactory::GetForProfile(browser_->profile());
183 if (service) {
184 service->LoadTabsFromLastSession();
186 // TODO(sail): enable this when mac implements the dynamic menu, together with
187 // MenuModelDelegate::MenuStructureChanged().
188 #if !defined(OS_MACOSX)
189 service->AddObserver(this);
190 #endif
193 Build();
195 // Retrieve accelerator key for IDC_RESTORE_TAB now, because on ASH, it's not
196 // defined in |accelerator_provider|, but in shell, so simply retrieve it now
197 // for all ASH and non-ASH for use in |GetAcceleratorForCommandId|.
198 #if defined(USE_ASH)
199 for (size_t i = 0; i < ash::kAcceleratorDataLength; ++i) {
200 const ash::AcceleratorData& accel_data = ash::kAcceleratorData[i];
201 if (accel_data.action == ash::RESTORE_TAB) {
202 reopen_closed_tab_accelerator_ = ui::Accelerator(accel_data.keycode,
203 accel_data.modifiers);
204 break;
207 #else
208 if (accelerator_provider) {
209 accelerator_provider->GetAcceleratorForCommandId(
210 IDC_RESTORE_TAB, &reopen_closed_tab_accelerator_);
212 #endif // defined(USE_ASH)
215 RecentTabsSubMenuModel::~RecentTabsSubMenuModel() {
216 TabRestoreService* service =
217 TabRestoreServiceFactory::GetForProfile(browser_->profile());
218 if (service)
219 service->RemoveObserver(this);
222 bool RecentTabsSubMenuModel::IsCommandIdChecked(int command_id) const {
223 return false;
226 bool RecentTabsSubMenuModel::IsCommandIdEnabled(int command_id) const {
227 if (command_id == kRecentlyClosedHeaderCommandId ||
228 command_id == kDisabledRecentlyClosedHeaderCommandId ||
229 command_id == IDC_RECENT_TABS_NO_DEVICE_TABS ||
230 IsDeviceNameCommandId(command_id)) {
231 return false;
233 return true;
236 bool RecentTabsSubMenuModel::GetAcceleratorForCommandId(
237 int command_id, ui::Accelerator* accelerator) {
238 // If there are no recently closed items, we show the accelerator beside
239 // the header, otherwise, we show it beside the first item underneath it.
240 int index_in_menu = GetIndexOfCommandId(command_id);
241 int header_index = GetIndexOfCommandId(kRecentlyClosedHeaderCommandId);
242 if ((command_id == kDisabledRecentlyClosedHeaderCommandId ||
243 (header_index != -1 && index_in_menu == header_index + 1)) &&
244 reopen_closed_tab_accelerator_.key_code() != ui::VKEY_UNKNOWN) {
245 *accelerator = reopen_closed_tab_accelerator_;
246 return true;
248 return false;
251 void RecentTabsSubMenuModel::ExecuteCommand(int command_id, int event_flags) {
252 if (command_id == IDC_SHOW_HISTORY) {
253 UMA_HISTOGRAM_ENUMERATION("WrenchMenu.RecentTabsSubMenu", SHOW_MORE,
254 LIMIT_RECENT_TAB_ACTION);
255 // We show all "other devices" on the history page.
256 chrome::ExecuteCommandWithDisposition(browser_, IDC_SHOW_HISTORY,
257 ui::DispositionFromEventFlags(event_flags));
258 return;
261 DCHECK_NE(IDC_RECENT_TABS_NO_DEVICE_TABS, command_id);
262 DCHECK(!IsDeviceNameCommandId(command_id));
264 WindowOpenDisposition disposition =
265 ui::DispositionFromEventFlags(event_flags);
266 if (disposition == CURRENT_TAB) // Force to open a new foreground tab.
267 disposition = NEW_FOREGROUND_TAB;
269 TabRestoreService* service =
270 TabRestoreServiceFactory::GetForProfile(browser_->profile());
271 TabRestoreServiceDelegate* delegate =
272 TabRestoreServiceDelegate::FindDelegateForWebContents(
273 browser_->tab_strip_model()->GetActiveWebContents());
274 if (IsTabModelCommandId(command_id)) {
275 TabNavigationItems* tab_items = NULL;
276 int tab_items_idx = CommandIdToTabVectorIndex(command_id, &tab_items);
277 const TabNavigationItem& item = (*tab_items)[tab_items_idx];
278 DCHECK(item.tab_id > -1 && item.url.is_valid());
280 if (item.session_tag.empty()) { // Restore tab of local session.
281 if (service && delegate) {
282 content::RecordAction(
283 base::UserMetricsAction("WrenchMenu_OpenRecentTabFromLocal"));
284 UMA_HISTOGRAM_ENUMERATION("WrenchMenu.RecentTabsSubMenu",
285 LOCAL_SESSION_TAB, LIMIT_RECENT_TAB_ACTION);
286 service->RestoreEntryById(delegate, item.tab_id,
287 browser_->host_desktop_type(), disposition);
289 } else { // Restore tab of session from other devices.
290 browser_sync::OpenTabsUIDelegate* open_tabs = GetOpenTabsUIDelegate();
291 if (!open_tabs)
292 return;
293 const sessions::SessionTab* tab;
294 if (!open_tabs->GetForeignTab(item.session_tag, item.tab_id, &tab))
295 return;
296 if (tab->navigations.empty())
297 return;
298 content::RecordAction(
299 base::UserMetricsAction("WrenchMenu_OpenRecentTabFromDevice"));
300 UMA_HISTOGRAM_ENUMERATION("WrenchMenu.RecentTabsSubMenu",
301 OTHER_DEVICE_TAB, LIMIT_RECENT_TAB_ACTION);
302 SessionRestore::RestoreForeignSessionTab(
303 browser_->tab_strip_model()->GetActiveWebContents(),
304 *tab, disposition);
306 } else {
307 DCHECK(IsWindowModelCommandId(command_id));
308 if (service && delegate) {
309 int window_items_idx = CommandIdToWindowVectorIndex(command_id);
310 DCHECK(window_items_idx >= 0 &&
311 window_items_idx < static_cast<int>(local_window_items_.size()));
312 content::RecordAction(
313 base::UserMetricsAction("WrenchMenu_OpenRecentWindow"));
314 UMA_HISTOGRAM_ENUMERATION("WrenchMenu.RecentTabsSubMenu", RESTORE_WINDOW,
315 LIMIT_RECENT_TAB_ACTION);
316 service->RestoreEntryById(delegate, local_window_items_[window_items_idx],
317 browser_->host_desktop_type(), disposition);
320 UMA_HISTOGRAM_MEDIUM_TIMES("WrenchMenu.TimeToAction.OpenRecentTab",
321 menu_opened_timer_.Elapsed());
322 UMA_HISTOGRAM_ENUMERATION("WrenchMenu.MenuAction", MENU_ACTION_RECENT_TAB,
323 LIMIT_MENU_ACTION);
326 int RecentTabsSubMenuModel::GetFirstRecentTabsCommandId() {
327 return WindowVectorIndexToCommandId(0);
330 const gfx::FontList* RecentTabsSubMenuModel::GetLabelFontListAt(
331 int index) const {
332 int command_id = GetCommandIdAt(index);
333 if (command_id == kRecentlyClosedHeaderCommandId ||
334 IsDeviceNameCommandId(command_id)) {
335 return &ui::ResourceBundle::GetSharedInstance().GetFontList(
336 ui::ResourceBundle::BoldFont);
338 return NULL;
341 int RecentTabsSubMenuModel::GetMaxWidthForItemAtIndex(int item_index) const {
342 int command_id = GetCommandIdAt(item_index);
343 if (command_id == IDC_RECENT_TABS_NO_DEVICE_TABS ||
344 command_id == kRecentlyClosedHeaderCommandId ||
345 command_id == kDisabledRecentlyClosedHeaderCommandId) {
346 return -1;
348 return 320;
351 bool RecentTabsSubMenuModel::GetURLAndTitleForItemAtIndex(
352 int index,
353 std::string* url,
354 base::string16* title) {
355 int command_id = GetCommandIdAt(index);
356 if (IsTabModelCommandId(command_id)) {
357 TabNavigationItems* tab_items = NULL;
358 int tab_items_idx = CommandIdToTabVectorIndex(command_id, &tab_items);
359 const TabNavigationItem& item = (*tab_items)[tab_items_idx];
360 *url = item.url.possibly_invalid_spec();
361 *title = item.title;
362 return true;
364 return false;
367 void RecentTabsSubMenuModel::Build() {
368 // The menu contains:
369 // - Recently closed header, then list of local recently closed tabs/windows,
370 // then separator
371 // - device 1 section header, then list of tabs from device, then separator
372 // - device 2 section header, then list of tabs from device, then separator
373 // - device 3 section header, then list of tabs from device, then separator
374 // - More... to open the history tab to get more other devices.
375 // |local_tab_navigation_items_| and |other_devices_tab_navigation_items_|
376 // only contain navigatable (and hence executable) tab items for local
377 // recently closed tabs and tabs from other devices respectively.
378 // |local_window_items_| contains the local recently closed windows.
379 BuildLocalEntries();
380 BuildTabsFromOtherDevices();
383 void RecentTabsSubMenuModel::BuildLocalEntries() {
384 // All local items use InsertItem*At() to append or insert a menu item.
385 // We're appending if building the entries for the first time i.e. invoked
386 // from Constructor(), inserting when local entries change subsequently i.e.
387 // invoked from TabRestoreServiceChanged().
389 DCHECK_EQ(last_local_model_index_, -1);
391 TabRestoreService* service =
392 TabRestoreServiceFactory::GetForProfile(browser_->profile());
393 if (!service || service->entries().size() == 0) {
394 // This is to show a disabled restore tab entry with the accelerator to
395 // teach users about this command.
396 InsertItemWithStringIdAt(++last_local_model_index_,
397 kDisabledRecentlyClosedHeaderCommandId,
398 IDS_RECENTLY_CLOSED);
399 } else {
400 InsertItemWithStringIdAt(++last_local_model_index_,
401 kRecentlyClosedHeaderCommandId,
402 IDS_RECENTLY_CLOSED);
403 ui::ResourceBundle& rb = ui::ResourceBundle::GetSharedInstance();
404 SetIcon(last_local_model_index_,
405 rb.GetNativeImageNamed(IDR_RECENTLY_CLOSED_WINDOW));
407 int added_count = 0;
408 TabRestoreService::Entries entries = service->entries();
409 for (TabRestoreService::Entries::const_iterator it = entries.begin();
410 it != entries.end() && added_count < kMaxLocalEntries; ++it) {
411 TabRestoreService::Entry* entry = *it;
412 if (entry->type == TabRestoreService::TAB) {
413 TabRestoreService::Tab* tab =
414 static_cast<TabRestoreService::Tab*>(entry);
415 const sessions::SerializedNavigationEntry& current_navigation =
416 tab->navigations.at(tab->current_navigation_index);
417 BuildLocalTabItem(
418 entry->id,
419 current_navigation.title(),
420 current_navigation.virtual_url(),
421 ++last_local_model_index_);
422 } else {
423 DCHECK_EQ(entry->type, TabRestoreService::WINDOW);
424 BuildLocalWindowItem(
425 entry->id,
426 static_cast<TabRestoreService::Window*>(entry)->tabs.size(),
427 ++last_local_model_index_);
429 ++added_count;
433 DCHECK_GE(last_local_model_index_, 0);
436 void RecentTabsSubMenuModel::BuildTabsFromOtherDevices() {
437 // All other devices' items (device headers or tabs) use AddItem*() to append
438 // a menu item, because they are always only built once (i.e. invoked from
439 // Constructor()) and don't change after that.
441 browser_sync::OpenTabsUIDelegate* open_tabs = GetOpenTabsUIDelegate();
442 std::vector<const browser_sync::SyncedSession*> sessions;
443 if (!open_tabs || !open_tabs->GetAllForeignSessions(&sessions)) {
444 AddSeparator(ui::NORMAL_SEPARATOR);
445 AddItemWithStringId(IDC_RECENT_TABS_NO_DEVICE_TABS,
446 IDS_RECENT_TABS_NO_DEVICE_TABS);
447 return;
450 // Sort sessions from most recent to least recent.
451 std::sort(sessions.begin(), sessions.end(), SortSessionsByRecency);
453 const size_t kMaxSessionsToShow = 3;
454 size_t num_sessions_added = 0;
455 for (size_t i = 0;
456 i < sessions.size() && num_sessions_added < kMaxSessionsToShow; ++i) {
457 const browser_sync::SyncedSession* session = sessions[i];
458 const std::string& session_tag = session->session_tag;
460 // Get windows of session.
461 std::vector<const sessions::SessionWindow*> windows;
462 if (!open_tabs->GetForeignSession(session_tag, &windows) ||
463 windows.empty()) {
464 continue;
467 // Collect tabs from all windows of session, pruning those that are not
468 // syncable or are NewTabPage, then sort them from most recent to least
469 // recent, independent of which window the tabs were from.
470 std::vector<const sessions::SessionTab*> tabs_in_session;
471 for (size_t j = 0; j < windows.size(); ++j) {
472 const sessions::SessionWindow* window = windows[j];
473 for (size_t t = 0; t < window->tabs.size(); ++t) {
474 const sessions::SessionTab* tab = window->tabs[t];
475 if (tab->navigations.empty())
476 continue;
477 const sessions::SerializedNavigationEntry& current_navigation =
478 tab->navigations.at(tab->normalized_navigation_index());
479 if (chrome::IsNTPURL(current_navigation.virtual_url(),
480 browser_->profile())) {
481 continue;
483 tabs_in_session.push_back(tab);
486 if (tabs_in_session.empty())
487 continue;
488 std::sort(tabs_in_session.begin(), tabs_in_session.end(),
489 SortTabsByRecency);
491 // Add the header for the device session.
492 DCHECK(!session->session_name.empty());
493 AddSeparator(ui::NORMAL_SEPARATOR);
494 int command_id = kMinDeviceNameCommandId + i;
495 DCHECK_LE(command_id, kMaxDeviceNameCommandId);
496 AddItem(command_id, base::UTF8ToUTF16(session->session_name));
497 AddDeviceFavicon(GetItemCount() - 1, session->device_type);
499 // Build tab menu items from sorted session tabs.
500 const size_t kMaxTabsPerSessionToShow = 4;
501 for (size_t k = 0;
502 k < std::min(tabs_in_session.size(), kMaxTabsPerSessionToShow);
503 ++k) {
504 BuildOtherDevicesTabItem(session_tag, *tabs_in_session[k]);
505 } // for all tabs in one session
507 ++num_sessions_added;
508 } // for all sessions
510 // We are not supposed to get here unless at least some items were added.
511 DCHECK_GT(GetItemCount(), 0);
512 AddSeparator(ui::NORMAL_SEPARATOR);
513 AddItemWithStringId(IDC_SHOW_HISTORY, IDS_RECENT_TABS_MORE);
516 void RecentTabsSubMenuModel::BuildLocalTabItem(int session_id,
517 const base::string16& title,
518 const GURL& url,
519 int curr_model_index) {
520 TabNavigationItem item(std::string(), session_id, title, url);
521 int command_id = TabVectorIndexToCommandId(
522 local_tab_navigation_items_.size(), kFirstLocalTabCommandId);
523 // See comments in BuildLocalEntries() about usage of InsertItem*At().
524 // There may be no tab title, in which case, use the url as tab title.
525 InsertItemAt(curr_model_index, command_id,
526 title.empty() ? base::UTF8ToUTF16(item.url.spec()) : title);
527 AddTabFavicon(command_id, item.url);
528 local_tab_navigation_items_.push_back(item);
531 void RecentTabsSubMenuModel::BuildLocalWindowItem(
532 const SessionID::id_type& window_id,
533 int num_tabs,
534 int curr_model_index) {
535 int command_id = WindowVectorIndexToCommandId(local_window_items_.size());
536 // See comments in BuildLocalEntries() about usage of InsertItem*At().
537 InsertItemAt(curr_model_index, command_id, l10n_util::GetPluralStringFUTF16(
538 IDS_RECENTLY_CLOSED_WINDOW, num_tabs));
539 ui::ResourceBundle& rb = ui::ResourceBundle::GetSharedInstance();
540 SetIcon(curr_model_index, rb.GetNativeImageNamed(IDR_RECENTLY_CLOSED_WINDOW));
541 local_window_items_.push_back(window_id);
544 void RecentTabsSubMenuModel::BuildOtherDevicesTabItem(
545 const std::string& session_tag,
546 const sessions::SessionTab& tab) {
547 const sessions::SerializedNavigationEntry& current_navigation =
548 tab.navigations.at(tab.normalized_navigation_index());
549 TabNavigationItem item(session_tag, tab.tab_id.id(),
550 current_navigation.title(),
551 current_navigation.virtual_url());
552 int command_id = TabVectorIndexToCommandId(
553 other_devices_tab_navigation_items_.size(),
554 kFirstOtherDevicesTabCommandId);
555 // See comments in BuildTabsFromOtherDevices() about usage of AddItem*().
556 // There may be no tab title, in which case, use the url as tab title.
557 AddItem(command_id,
558 current_navigation.title().empty() ?
559 base::UTF8ToUTF16(item.url.spec()) : current_navigation.title());
560 AddTabFavicon(command_id, item.url);
561 other_devices_tab_navigation_items_.push_back(item);
564 void RecentTabsSubMenuModel::AddDeviceFavicon(
565 int index_in_menu,
566 browser_sync::SyncedSession::DeviceType device_type) {
567 int favicon_id = -1;
568 switch (device_type) {
569 case browser_sync::SyncedSession::TYPE_PHONE:
570 favicon_id = IDR_PHONE_FAVICON;
571 break;
573 case browser_sync::SyncedSession::TYPE_TABLET:
574 favicon_id = IDR_TABLET_FAVICON;
575 break;
577 case browser_sync::SyncedSession::TYPE_CHROMEOS:
578 case browser_sync::SyncedSession::TYPE_WIN:
579 case browser_sync::SyncedSession::TYPE_MACOSX:
580 case browser_sync::SyncedSession::TYPE_LINUX:
581 case browser_sync::SyncedSession::TYPE_OTHER:
582 case browser_sync::SyncedSession::TYPE_UNSET:
583 favicon_id = IDR_LAPTOP_FAVICON;
584 break;
587 ui::ResourceBundle& rb = ui::ResourceBundle::GetSharedInstance();
588 SetIcon(index_in_menu, rb.GetNativeImageNamed(favicon_id));
591 void RecentTabsSubMenuModel::AddTabFavicon(int command_id, const GURL& url) {
592 bool is_local_tab = command_id < kFirstOtherDevicesTabCommandId;
593 int index_in_menu = GetIndexOfCommandId(command_id);
595 if (!is_local_tab) {
596 // If tab has synced favicon, use it.
597 // Note that currently, other devices' tabs only have favicons if
598 // --sync-tab-favicons switch is on; according to zea@, this flag is now
599 // automatically enabled for iOS and android, and they're looking into
600 // enabling it for other platforms.
601 browser_sync::OpenTabsUIDelegate* open_tabs = GetOpenTabsUIDelegate();
602 scoped_refptr<base::RefCountedMemory> favicon_png;
603 if (open_tabs &&
604 open_tabs->GetSyncedFaviconForPageURL(url.spec(), &favicon_png)) {
605 gfx::Image image = gfx::Image::CreateFrom1xPNGBytes(favicon_png);
606 SetIcon(index_in_menu, image);
607 return;
611 // Otherwise, start to fetch the favicon from local history asynchronously.
612 // Set default icon first.
613 SetIcon(index_in_menu, default_favicon_);
614 // Start request to fetch actual icon if possible.
615 favicon::FaviconService* favicon_service =
616 FaviconServiceFactory::GetForProfile(browser_->profile(),
617 ServiceAccessType::EXPLICIT_ACCESS);
618 if (!favicon_service)
619 return;
621 favicon_service->GetFaviconImageForPageURL(
622 url,
623 base::Bind(&RecentTabsSubMenuModel::OnFaviconDataAvailable,
624 weak_ptr_factory_.GetWeakPtr(),
625 command_id),
626 is_local_tab ? &local_tab_cancelable_task_tracker_
627 : &other_devices_tab_cancelable_task_tracker_);
630 void RecentTabsSubMenuModel::OnFaviconDataAvailable(
631 int command_id,
632 const favicon_base::FaviconImageResult& image_result) {
633 if (image_result.image.IsEmpty())
634 return;
635 int index_in_menu = GetIndexOfCommandId(command_id);
636 DCHECK_GT(index_in_menu, -1);
637 SetIcon(index_in_menu, image_result.image);
638 ui::MenuModelDelegate* menu_model_delegate = GetMenuModelDelegate();
639 if (menu_model_delegate)
640 menu_model_delegate->OnIconChanged(index_in_menu);
643 int RecentTabsSubMenuModel::CommandIdToTabVectorIndex(
644 int command_id,
645 TabNavigationItems** tab_items) {
646 DCHECK(IsTabModelCommandId(command_id));
647 if (command_id >= kFirstOtherDevicesTabCommandId) {
648 *tab_items = &other_devices_tab_navigation_items_;
649 return command_id - kFirstOtherDevicesTabCommandId;
651 *tab_items = &local_tab_navigation_items_;
652 return command_id - kFirstLocalTabCommandId;
655 void RecentTabsSubMenuModel::ClearLocalEntries() {
656 // Remove local items (recently closed tabs and windows) from menumodel.
657 while (last_local_model_index_ >= 0)
658 RemoveItemAt(last_local_model_index_--);
660 // Cancel asynchronous FaviconService::GetFaviconImageForPageURL() tasks of
661 // all
662 // local tabs.
663 local_tab_cancelable_task_tracker_.TryCancelAll();
665 // Remove all local tab navigation items.
666 local_tab_navigation_items_.clear();
668 // Remove all local window items.
669 local_window_items_.clear();
672 browser_sync::OpenTabsUIDelegate*
673 RecentTabsSubMenuModel::GetOpenTabsUIDelegate() {
674 if (!open_tabs_delegate_) {
675 ProfileSyncService* service = ProfileSyncServiceFactory::GetInstance()->
676 GetForProfile(browser_->profile());
677 // Only return the delegate if it exists and it is done syncing sessions.
678 if (service && service->SyncActive())
679 open_tabs_delegate_ = service->GetOpenTabsUIDelegate();
681 return open_tabs_delegate_;
684 void RecentTabsSubMenuModel::TabRestoreServiceChanged(
685 TabRestoreService* service) {
686 ClearLocalEntries();
688 BuildLocalEntries();
690 ui::MenuModelDelegate* menu_model_delegate = GetMenuModelDelegate();
691 if (menu_model_delegate)
692 menu_model_delegate->OnMenuStructureChanged();
695 void RecentTabsSubMenuModel::TabRestoreServiceDestroyed(
696 TabRestoreService* service) {
697 TabRestoreServiceChanged(service);