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"
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/profile_sync_service.h"
21 #include "chrome/browser/sync/profile_sync_service_factory.h"
22 #include "chrome/browser/ui/browser.h"
23 #include "chrome/browser/ui/browser_commands.h"
24 #include "chrome/browser/ui/tabs/tab_strip_model.h"
25 #include "chrome/browser/ui/toolbar/wrench_menu_model.h"
26 #include "chrome/grit/generated_resources.h"
27 #include "components/favicon_base/favicon_types.h"
28 #include "components/sync_driver/glue/synced_session.h"
29 #include "components/sync_driver/open_tabs_ui_delegate.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(OS_MACOSX)
39 #include "ui/gfx/paint_vector_icon.h"
40 #include "ui/gfx/vector_icons_public.h"
41 #include "ui/native_theme/common_theme.h"
42 #include "ui/native_theme/native_theme.h"
46 #include "ash/accelerators/accelerator_table.h"
47 #endif // defined(USE_ASH)
51 // Initial comamnd ID's for navigatable (and hence executable) tab/window menu
52 // items. The menumodel and storage structures are not 1-1:
53 // - menumodel has "Recently closed" header, "No tabs from other devices",
54 // device section headers, separators, local and other devices' tab items, and
55 // local window items.
56 // - |local_tab_navigation_items_| and |other_devices_tab_navigation_items_|
57 // only have navigatabale/executable tab items.
58 // - |local_window_items_| only has executable open window items.
59 // Using initial command IDs for local tab, local window and other devices' tab
60 // items makes it easier and less error-prone to manipulate the menumodel and
61 // storage structures. These ids must be bigger than the maximum possible
62 // number of items in the menumodel, so that index of the last menu item doesn't
63 // clash with these values when menu items are retrieved via
64 // GetIndexOfCommandId().
65 // The range of all command ID's used in RecentTabsSubMenuModel, including the
66 // "Recently closed" headers, must be between
67 // |WrenchMenuModel::kMinRecentTabsCommandId| i.e. 1001 and 1200
68 // (|WrenchMenuModel::kMaxRecentTabsCommandId|) inclusively.
69 const int kFirstLocalTabCommandId
= WrenchMenuModel::kMinRecentTabsCommandId
;
70 const int kFirstLocalWindowCommandId
= 1031;
71 const int kFirstOtherDevicesTabCommandId
= 1051;
72 const int kMinDeviceNameCommandId
= 1100;
73 const int kMaxDeviceNameCommandId
= 1110;
75 // The maximum number of local recently closed entries (tab or window) to be
77 const int kMaxLocalEntries
= 8;
79 // Index of the separator that follows the history menu item. Used as a
80 // reference position for inserting local entries.
81 const int kHistorySeparatorIndex
= 1;
83 // Comparator function for use with std::sort that will sort sessions by
84 // descending modified_time (i.e., most recent first).
85 bool SortSessionsByRecency(const sync_driver::SyncedSession
* s1
,
86 const sync_driver::SyncedSession
* s2
) {
87 return s1
->modified_time
> s2
->modified_time
;
90 // Returns true if the command id identifies a tab menu item.
91 bool IsTabModelCommandId(int command_id
) {
92 return ((command_id
>= kFirstLocalTabCommandId
&&
93 command_id
< kFirstLocalWindowCommandId
) ||
94 (command_id
>= kFirstOtherDevicesTabCommandId
&&
95 command_id
< kMinDeviceNameCommandId
));
98 // Returns true if the command id identifies a window menu item.
99 bool IsWindowModelCommandId(int command_id
) {
100 return command_id
>= kFirstLocalWindowCommandId
&&
101 command_id
< kFirstOtherDevicesTabCommandId
;
104 bool IsDeviceNameCommandId(int command_id
) {
105 return command_id
>= kMinDeviceNameCommandId
&&
106 command_id
<= kMaxDeviceNameCommandId
;
109 // Convert |tab_vector_index| to command id of menu item, with
110 // |first_command_id| as the base command id.
111 int TabVectorIndexToCommandId(int tab_vector_index
, int first_command_id
) {
112 int command_id
= tab_vector_index
+ first_command_id
;
113 DCHECK(IsTabModelCommandId(command_id
));
117 // Convert |window_vector_index| to command id of menu item.
118 int WindowVectorIndexToCommandId(int window_vector_index
) {
119 int command_id
= window_vector_index
+ kFirstLocalWindowCommandId
;
120 DCHECK(IsWindowModelCommandId(command_id
));
124 // Convert |command_id| of menu item to index in |local_window_items_|.
125 int CommandIdToWindowVectorIndex(int command_id
) {
126 DCHECK(IsWindowModelCommandId(command_id
));
127 return command_id
- kFirstLocalWindowCommandId
;
130 #if !defined(OS_MACOSX)
131 gfx::Image
CreateFavicon(gfx::VectorIconId id
) {
133 ui::CommonThemeGetSystemColor(ui::NativeTheme::kColorId_ChromeIconGrey
,
135 return gfx::Image(gfx::CreateVectorIcon(id
, 16, grey
));
141 enum RecentTabAction
{
142 LOCAL_SESSION_TAB
= 0,
146 LIMIT_RECENT_TAB_ACTION
149 // An element in |RecentTabsSubMenuModel::local_tab_navigation_items_| or
150 // |RecentTabsSubMenuModel::other_devices_tab_navigation_items_| that stores
151 // the navigation information of a local or other devices' tab required to
153 struct RecentTabsSubMenuModel::TabNavigationItem
{
154 TabNavigationItem() : tab_id(-1) {}
156 TabNavigationItem(const std::string
& session_tag
,
157 const SessionID::id_type
& tab_id
,
158 const base::string16
& title
,
160 : session_tag(session_tag
),
165 // For use by std::set for sorting.
166 bool operator<(const TabNavigationItem
& other
) const {
167 return url
< other
.url
;
170 // Empty for local tabs, non-empty for other devices' tabs.
171 std::string session_tag
;
172 SessionID::id_type tab_id
; // -1 for invalid, >= 0 otherwise.
173 base::string16 title
;
177 const int RecentTabsSubMenuModel::kRecentlyClosedHeaderCommandId
= 1120;
178 const int RecentTabsSubMenuModel::kDisabledRecentlyClosedHeaderCommandId
= 1121;
180 RecentTabsSubMenuModel::RecentTabsSubMenuModel(
181 ui::AcceleratorProvider
* accelerator_provider
,
183 sync_driver::OpenTabsUIDelegate
* open_tabs_delegate
)
184 : ui::SimpleMenuModel(this),
186 open_tabs_delegate_(open_tabs_delegate
),
187 last_local_model_index_(kHistorySeparatorIndex
),
188 default_favicon_(ui::ResourceBundle::GetSharedInstance().
189 GetNativeImageNamed(IDR_DEFAULT_FAVICON
)),
190 weak_ptr_factory_(this) {
191 // Invoke asynchronous call to load tabs from local last session, which does
192 // nothing if the tabs have already been loaded or they shouldn't be loaded.
193 // TabRestoreServiceChanged() will be called after the tabs are loaded.
194 TabRestoreService
* service
=
195 TabRestoreServiceFactory::GetForProfile(browser_
->profile());
197 service
->LoadTabsFromLastSession();
199 // TODO(sail): enable this when mac implements the dynamic menu, together with
200 // MenuModelDelegate::MenuStructureChanged().
201 #if !defined(OS_MACOSX)
202 service
->AddObserver(this);
208 // Retrieve accelerator key for IDC_RESTORE_TAB now, because on ASH, it's not
209 // defined in |accelerator_provider|, but in shell, so simply retrieve it now
210 // for all ASH and non-ASH for use in |GetAcceleratorForCommandId|.
212 for (size_t i
= 0; i
< ash::kAcceleratorDataLength
; ++i
) {
213 const ash::AcceleratorData
& accel_data
= ash::kAcceleratorData
[i
];
214 if (accel_data
.action
== ash::RESTORE_TAB
) {
215 reopen_closed_tab_accelerator_
= ui::Accelerator(accel_data
.keycode
,
216 accel_data
.modifiers
);
221 if (accelerator_provider
) {
222 accelerator_provider
->GetAcceleratorForCommandId(
223 IDC_RESTORE_TAB
, &reopen_closed_tab_accelerator_
);
225 #endif // defined(USE_ASH)
227 if (accelerator_provider
) {
228 accelerator_provider
->GetAcceleratorForCommandId(
229 IDC_SHOW_HISTORY
, &show_history_accelerator_
);
233 RecentTabsSubMenuModel::~RecentTabsSubMenuModel() {
234 TabRestoreService
* service
=
235 TabRestoreServiceFactory::GetForProfile(browser_
->profile());
237 service
->RemoveObserver(this);
240 bool RecentTabsSubMenuModel::IsCommandIdChecked(int command_id
) const {
244 bool RecentTabsSubMenuModel::IsCommandIdEnabled(int command_id
) const {
245 if (command_id
== kRecentlyClosedHeaderCommandId
||
246 command_id
== kDisabledRecentlyClosedHeaderCommandId
||
247 command_id
== IDC_RECENT_TABS_NO_DEVICE_TABS
||
248 IsDeviceNameCommandId(command_id
)) {
254 bool RecentTabsSubMenuModel::GetAcceleratorForCommandId(
255 int command_id
, ui::Accelerator
* accelerator
) {
256 // If there are no recently closed items, we show the accelerator beside
257 // the header, otherwise, we show it beside the first item underneath it.
258 int index_in_menu
= GetIndexOfCommandId(command_id
);
259 int header_index
= GetIndexOfCommandId(kRecentlyClosedHeaderCommandId
);
260 if ((command_id
== kDisabledRecentlyClosedHeaderCommandId
||
261 (header_index
!= -1 && index_in_menu
== header_index
+ 1)) &&
262 reopen_closed_tab_accelerator_
.key_code() != ui::VKEY_UNKNOWN
) {
263 *accelerator
= reopen_closed_tab_accelerator_
;
267 if (command_id
== IDC_SHOW_HISTORY
) {
268 *accelerator
= show_history_accelerator_
;
275 void RecentTabsSubMenuModel::ExecuteCommand(int command_id
, int event_flags
) {
276 if (command_id
== IDC_SHOW_HISTORY
) {
277 UMA_HISTOGRAM_ENUMERATION("WrenchMenu.RecentTabsSubMenu", SHOW_MORE
,
278 LIMIT_RECENT_TAB_ACTION
);
279 // We show all "other devices" on the history page.
280 chrome::ExecuteCommandWithDisposition(browser_
, IDC_SHOW_HISTORY
,
281 ui::DispositionFromEventFlags(event_flags
));
285 DCHECK_NE(IDC_RECENT_TABS_NO_DEVICE_TABS
, command_id
);
286 DCHECK(!IsDeviceNameCommandId(command_id
));
288 WindowOpenDisposition disposition
=
289 ui::DispositionFromEventFlags(event_flags
);
290 if (disposition
== CURRENT_TAB
) // Force to open a new foreground tab.
291 disposition
= NEW_FOREGROUND_TAB
;
293 TabRestoreService
* service
=
294 TabRestoreServiceFactory::GetForProfile(browser_
->profile());
295 TabRestoreServiceDelegate
* delegate
=
296 TabRestoreServiceDelegate::FindDelegateForWebContents(
297 browser_
->tab_strip_model()->GetActiveWebContents());
298 if (IsTabModelCommandId(command_id
)) {
299 TabNavigationItems
* tab_items
= NULL
;
300 int tab_items_idx
= CommandIdToTabVectorIndex(command_id
, &tab_items
);
301 const TabNavigationItem
& item
= (*tab_items
)[tab_items_idx
];
302 DCHECK(item
.tab_id
> -1 && item
.url
.is_valid());
304 if (item
.session_tag
.empty()) { // Restore tab of local session.
305 if (service
&& delegate
) {
306 content::RecordAction(
307 base::UserMetricsAction("WrenchMenu_OpenRecentTabFromLocal"));
308 UMA_HISTOGRAM_ENUMERATION("WrenchMenu.RecentTabsSubMenu",
309 LOCAL_SESSION_TAB
, LIMIT_RECENT_TAB_ACTION
);
310 service
->RestoreEntryById(delegate
, item
.tab_id
,
311 browser_
->host_desktop_type(), disposition
);
313 } else { // Restore tab of session from other devices.
314 sync_driver::OpenTabsUIDelegate
* open_tabs
= GetOpenTabsUIDelegate();
317 const sessions::SessionTab
* tab
;
318 if (!open_tabs
->GetForeignTab(item
.session_tag
, item
.tab_id
, &tab
))
320 if (tab
->navigations
.empty())
322 content::RecordAction(
323 base::UserMetricsAction("WrenchMenu_OpenRecentTabFromDevice"));
324 UMA_HISTOGRAM_ENUMERATION("WrenchMenu.RecentTabsSubMenu",
325 OTHER_DEVICE_TAB
, LIMIT_RECENT_TAB_ACTION
);
326 SessionRestore::RestoreForeignSessionTab(
327 browser_
->tab_strip_model()->GetActiveWebContents(),
331 DCHECK(IsWindowModelCommandId(command_id
));
332 if (service
&& delegate
) {
333 int window_items_idx
= CommandIdToWindowVectorIndex(command_id
);
334 DCHECK(window_items_idx
>= 0 &&
335 window_items_idx
< static_cast<int>(local_window_items_
.size()));
336 content::RecordAction(
337 base::UserMetricsAction("WrenchMenu_OpenRecentWindow"));
338 UMA_HISTOGRAM_ENUMERATION("WrenchMenu.RecentTabsSubMenu", RESTORE_WINDOW
,
339 LIMIT_RECENT_TAB_ACTION
);
340 service
->RestoreEntryById(delegate
, local_window_items_
[window_items_idx
],
341 browser_
->host_desktop_type(), disposition
);
344 UMA_HISTOGRAM_MEDIUM_TIMES("WrenchMenu.TimeToAction.OpenRecentTab",
345 menu_opened_timer_
.Elapsed());
346 UMA_HISTOGRAM_ENUMERATION("WrenchMenu.MenuAction", MENU_ACTION_RECENT_TAB
,
350 int RecentTabsSubMenuModel::GetFirstRecentTabsCommandId() {
351 return WindowVectorIndexToCommandId(0);
354 const gfx::FontList
* RecentTabsSubMenuModel::GetLabelFontListAt(
356 int command_id
= GetCommandIdAt(index
);
357 if (command_id
== kRecentlyClosedHeaderCommandId
||
358 IsDeviceNameCommandId(command_id
)) {
359 return &ui::ResourceBundle::GetSharedInstance().GetFontList(
360 ui::ResourceBundle::BoldFont
);
365 int RecentTabsSubMenuModel::GetMaxWidthForItemAtIndex(int item_index
) const {
366 int command_id
= GetCommandIdAt(item_index
);
367 if (command_id
== IDC_RECENT_TABS_NO_DEVICE_TABS
||
368 command_id
== kRecentlyClosedHeaderCommandId
||
369 command_id
== kDisabledRecentlyClosedHeaderCommandId
) {
375 bool RecentTabsSubMenuModel::GetURLAndTitleForItemAtIndex(
378 base::string16
* title
) {
379 int command_id
= GetCommandIdAt(index
);
380 if (IsTabModelCommandId(command_id
)) {
381 TabNavigationItems
* tab_items
= NULL
;
382 int tab_items_idx
= CommandIdToTabVectorIndex(command_id
, &tab_items
);
383 const TabNavigationItem
& item
= (*tab_items
)[tab_items_idx
];
384 *url
= item
.url
.possibly_invalid_spec();
391 void RecentTabsSubMenuModel::Build() {
392 // The menu contains:
393 // - History to open the full history tab.
395 // - Recently closed header, then list of local recently closed tabs/windows,
397 // - device 1 section header, then list of tabs from device, then separator
398 // - device 2 section header, then list of tabs from device, then separator
399 // - device 3 section header, then list of tabs from device, then separator
400 // |local_tab_navigation_items_| and |other_devices_tab_navigation_items_|
401 // only contain navigatable (and hence executable) tab items for local
402 // recently closed tabs and tabs from other devices respectively.
403 // |local_window_items_| contains the local recently closed windows.
404 InsertItemWithStringIdAt(0, IDC_SHOW_HISTORY
, IDS_SHOW_HISTORY
);
405 InsertSeparatorAt(1, ui::NORMAL_SEPARATOR
);
407 BuildTabsFromOtherDevices();
410 void RecentTabsSubMenuModel::BuildLocalEntries() {
411 last_local_model_index_
= kHistorySeparatorIndex
;
413 // All local items use InsertItem*At() to append or insert a menu item.
414 // We're appending if building the entries for the first time i.e. invoked
415 // from Constructor(), inserting when local entries change subsequently i.e.
416 // invoked from TabRestoreServiceChanged().
417 TabRestoreService
* service
=
418 TabRestoreServiceFactory::GetForProfile(browser_
->profile());
420 if (!service
|| service
->entries().size() == 0) {
421 // This is to show a disabled restore tab entry with the accelerator to
422 // teach users about this command.
423 InsertItemWithStringIdAt(++last_local_model_index_
,
424 kDisabledRecentlyClosedHeaderCommandId
,
425 IDS_RECENTLY_CLOSED
);
427 InsertItemWithStringIdAt(++last_local_model_index_
,
428 kRecentlyClosedHeaderCommandId
,
429 IDS_RECENTLY_CLOSED
);
430 #if defined(OS_MACOSX)
431 ui::ResourceBundle
& rb
= ui::ResourceBundle::GetSharedInstance();
432 SetIcon(last_local_model_index_
,
433 rb
.GetNativeImageNamed(IDR_RECENTLY_CLOSED_WINDOW
));
435 SetIcon(last_local_model_index_
, CreateFavicon(gfx::VectorIconId::TAB
));
439 TabRestoreService::Entries entries
= service
->entries();
440 for (TabRestoreService::Entries::const_iterator it
= entries
.begin();
441 it
!= entries
.end() && added_count
< kMaxLocalEntries
; ++it
) {
442 TabRestoreService::Entry
* entry
= *it
;
443 if (entry
->type
== TabRestoreService::TAB
) {
444 TabRestoreService::Tab
* tab
=
445 static_cast<TabRestoreService::Tab
*>(entry
);
446 const sessions::SerializedNavigationEntry
& current_navigation
=
447 tab
->navigations
.at(tab
->current_navigation_index
);
450 current_navigation
.title(),
451 current_navigation
.virtual_url(),
452 ++last_local_model_index_
);
454 DCHECK_EQ(entry
->type
, TabRestoreService::WINDOW
);
455 BuildLocalWindowItem(
457 static_cast<TabRestoreService::Window
*>(entry
)->tabs
.size(),
458 ++last_local_model_index_
);
463 DCHECK_GE(last_local_model_index_
, 0);
466 void RecentTabsSubMenuModel::BuildTabsFromOtherDevices() {
467 // All other devices' items (device headers or tabs) use AddItem*() to append
468 // a menu item, because they are always only built once (i.e. invoked from
469 // Constructor()) and don't change after that.
471 sync_driver::OpenTabsUIDelegate
* open_tabs
= GetOpenTabsUIDelegate();
472 std::vector
<const sync_driver::SyncedSession
*> sessions
;
473 if (!open_tabs
|| !open_tabs
->GetAllForeignSessions(&sessions
)) {
474 AddSeparator(ui::NORMAL_SEPARATOR
);
475 AddItemWithStringId(IDC_RECENT_TABS_NO_DEVICE_TABS
,
476 IDS_RECENT_TABS_NO_DEVICE_TABS
);
480 // Sort sessions from most recent to least recent.
481 std::sort(sessions
.begin(), sessions
.end(), SortSessionsByRecency
);
483 const size_t kMaxSessionsToShow
= 3;
484 size_t num_sessions_added
= 0;
486 i
< sessions
.size() && num_sessions_added
< kMaxSessionsToShow
; ++i
) {
487 const sync_driver::SyncedSession
* session
= sessions
[i
];
488 const std::string
& session_tag
= session
->session_tag
;
490 // Collect tabs from all windows of the session, ordered by recency.
491 std::vector
<const sessions::SessionTab
*> tabs_in_session
;
492 if (!open_tabs
->GetForeignSessionTabs(session_tag
, &tabs_in_session
) ||
493 tabs_in_session
.empty())
496 // Add the header for the device session.
497 DCHECK(!session
->session_name
.empty());
498 AddSeparator(ui::NORMAL_SEPARATOR
);
499 int command_id
= kMinDeviceNameCommandId
+ i
;
500 DCHECK_LE(command_id
, kMaxDeviceNameCommandId
);
501 AddItem(command_id
, base::UTF8ToUTF16(session
->session_name
));
502 AddDeviceFavicon(GetItemCount() - 1, session
->device_type
);
504 // Build tab menu items from sorted session tabs.
505 const size_t kMaxTabsPerSessionToShow
= 4;
507 k
< std::min(tabs_in_session
.size(), kMaxTabsPerSessionToShow
);
509 BuildOtherDevicesTabItem(session_tag
, *tabs_in_session
[k
]);
510 } // for all tabs in one session
512 ++num_sessions_added
;
513 } // for all sessions
515 // We are not supposed to get here unless at least some items were added.
516 DCHECK_GT(GetItemCount(), 0);
519 void RecentTabsSubMenuModel::BuildLocalTabItem(int session_id
,
520 const base::string16
& title
,
522 int curr_model_index
) {
523 TabNavigationItem
item(std::string(), session_id
, title
, url
);
524 int command_id
= TabVectorIndexToCommandId(
525 local_tab_navigation_items_
.size(), kFirstLocalTabCommandId
);
526 // See comments in BuildLocalEntries() about usage of InsertItem*At().
527 // There may be no tab title, in which case, use the url as tab title.
528 InsertItemAt(curr_model_index
, command_id
,
529 title
.empty() ? base::UTF8ToUTF16(item
.url
.spec()) : title
);
530 AddTabFavicon(command_id
, item
.url
);
531 local_tab_navigation_items_
.push_back(item
);
534 void RecentTabsSubMenuModel::BuildLocalWindowItem(
535 const SessionID::id_type
& window_id
,
537 int curr_model_index
) {
538 int command_id
= WindowVectorIndexToCommandId(local_window_items_
.size());
539 // See comments in BuildLocalEntries() about usage of InsertItem*At().
540 InsertItemAt(curr_model_index
, command_id
, l10n_util::GetPluralStringFUTF16(
541 IDS_RECENTLY_CLOSED_WINDOW
, num_tabs
));
542 #if defined(OS_MACOSX)
543 ui::ResourceBundle
& rb
= ui::ResourceBundle::GetSharedInstance();
544 SetIcon(curr_model_index
, rb
.GetNativeImageNamed(IDR_RECENTLY_CLOSED_WINDOW
));
546 SetIcon(curr_model_index
, CreateFavicon(gfx::VectorIconId::TAB
));
548 local_window_items_
.push_back(window_id
);
551 void RecentTabsSubMenuModel::BuildOtherDevicesTabItem(
552 const std::string
& session_tag
,
553 const sessions::SessionTab
& tab
) {
554 const sessions::SerializedNavigationEntry
& current_navigation
=
555 tab
.navigations
.at(tab
.normalized_navigation_index());
556 TabNavigationItem
item(session_tag
, tab
.tab_id
.id(),
557 current_navigation
.title(),
558 current_navigation
.virtual_url());
559 int command_id
= TabVectorIndexToCommandId(
560 other_devices_tab_navigation_items_
.size(),
561 kFirstOtherDevicesTabCommandId
);
562 // See comments in BuildTabsFromOtherDevices() about usage of AddItem*().
563 // There may be no tab title, in which case, use the url as tab title.
565 current_navigation
.title().empty() ?
566 base::UTF8ToUTF16(item
.url
.spec()) : current_navigation
.title());
567 AddTabFavicon(command_id
, item
.url
);
568 other_devices_tab_navigation_items_
.push_back(item
);
571 void RecentTabsSubMenuModel::AddDeviceFavicon(
573 sync_driver::SyncedSession::DeviceType device_type
) {
574 #if defined(OS_MACOSX)
576 switch (device_type
) {
577 case sync_driver::SyncedSession::TYPE_PHONE
:
578 favicon_id
= IDR_PHONE_FAVICON
;
581 case sync_driver::SyncedSession::TYPE_TABLET
:
582 favicon_id
= IDR_TABLET_FAVICON
;
585 case sync_driver::SyncedSession::TYPE_CHROMEOS
:
586 case sync_driver::SyncedSession::TYPE_WIN
:
587 case sync_driver::SyncedSession::TYPE_MACOSX
:
588 case sync_driver::SyncedSession::TYPE_LINUX
:
589 case sync_driver::SyncedSession::TYPE_OTHER
:
590 case sync_driver::SyncedSession::TYPE_UNSET
:
591 favicon_id
= IDR_LAPTOP_FAVICON
;
595 ui::ResourceBundle
& rb
= ui::ResourceBundle::GetSharedInstance();
596 SetIcon(index_in_menu
, rb
.GetNativeImageNamed(favicon_id
));
598 gfx::VectorIconId favicon_id
= gfx::VectorIconId::VECTOR_ICON_NONE
;
599 switch (device_type
) {
600 case sync_driver::SyncedSession::TYPE_PHONE
:
601 favicon_id
= gfx::VectorIconId::SMARTPHONE
;
604 case sync_driver::SyncedSession::TYPE_TABLET
:
605 favicon_id
= gfx::VectorIconId::TABLET
;
608 case sync_driver::SyncedSession::TYPE_CHROMEOS
:
609 case sync_driver::SyncedSession::TYPE_WIN
:
610 case sync_driver::SyncedSession::TYPE_MACOSX
:
611 case sync_driver::SyncedSession::TYPE_LINUX
:
612 case sync_driver::SyncedSession::TYPE_OTHER
:
613 case sync_driver::SyncedSession::TYPE_UNSET
:
614 favicon_id
= gfx::VectorIconId::LAPTOP
;
618 SetIcon(index_in_menu
, CreateFavicon(favicon_id
));
622 void RecentTabsSubMenuModel::AddTabFavicon(int command_id
, const GURL
& url
) {
623 bool is_local_tab
= command_id
< kFirstOtherDevicesTabCommandId
;
624 int index_in_menu
= GetIndexOfCommandId(command_id
);
627 // If tab has synced favicon, use it.
628 // Note that currently, other devices' tabs only have favicons if
629 // --sync-tab-favicons switch is on; according to zea@, this flag is now
630 // automatically enabled for iOS and android, and they're looking into
631 // enabling it for other platforms.
632 sync_driver::OpenTabsUIDelegate
* open_tabs
= GetOpenTabsUIDelegate();
633 scoped_refptr
<base::RefCountedMemory
> favicon_png
;
635 open_tabs
->GetSyncedFaviconForPageURL(url
.spec(), &favicon_png
)) {
636 gfx::Image image
= gfx::Image::CreateFrom1xPNGBytes(favicon_png
);
637 SetIcon(index_in_menu
, image
);
642 // Otherwise, start to fetch the favicon from local history asynchronously.
643 // Set default icon first.
644 SetIcon(index_in_menu
, default_favicon_
);
645 // Start request to fetch actual icon if possible.
646 favicon::FaviconService
* favicon_service
=
647 FaviconServiceFactory::GetForProfile(browser_
->profile(),
648 ServiceAccessType::EXPLICIT_ACCESS
);
649 if (!favicon_service
)
652 favicon_service
->GetFaviconImageForPageURL(
654 base::Bind(&RecentTabsSubMenuModel::OnFaviconDataAvailable
,
655 weak_ptr_factory_
.GetWeakPtr(),
657 is_local_tab
? &local_tab_cancelable_task_tracker_
658 : &other_devices_tab_cancelable_task_tracker_
);
661 void RecentTabsSubMenuModel::OnFaviconDataAvailable(
663 const favicon_base::FaviconImageResult
& image_result
) {
664 if (image_result
.image
.IsEmpty())
666 int index_in_menu
= GetIndexOfCommandId(command_id
);
667 DCHECK_GT(index_in_menu
, -1);
668 SetIcon(index_in_menu
, image_result
.image
);
669 ui::MenuModelDelegate
* menu_model_delegate
= GetMenuModelDelegate();
670 if (menu_model_delegate
)
671 menu_model_delegate
->OnIconChanged(index_in_menu
);
674 int RecentTabsSubMenuModel::CommandIdToTabVectorIndex(
676 TabNavigationItems
** tab_items
) {
677 DCHECK(IsTabModelCommandId(command_id
));
678 if (command_id
>= kFirstOtherDevicesTabCommandId
) {
679 *tab_items
= &other_devices_tab_navigation_items_
;
680 return command_id
- kFirstOtherDevicesTabCommandId
;
682 *tab_items
= &local_tab_navigation_items_
;
683 return command_id
- kFirstLocalTabCommandId
;
686 void RecentTabsSubMenuModel::ClearLocalEntries() {
687 // Remove local items (recently closed tabs and windows) from menumodel.
688 while (last_local_model_index_
> kHistorySeparatorIndex
)
689 RemoveItemAt(last_local_model_index_
--);
691 // Cancel asynchronous FaviconService::GetFaviconImageForPageURL() tasks of
693 local_tab_cancelable_task_tracker_
.TryCancelAll();
695 // Remove all local tab navigation items.
696 local_tab_navigation_items_
.clear();
698 // Remove all local window items.
699 local_window_items_
.clear();
702 sync_driver::OpenTabsUIDelegate
*
703 RecentTabsSubMenuModel::GetOpenTabsUIDelegate() {
704 if (!open_tabs_delegate_
) {
705 ProfileSyncService
* service
= ProfileSyncServiceFactory::GetInstance()->
706 GetForProfile(browser_
->profile());
707 // Only return the delegate if it exists and it is done syncing sessions.
708 if (service
&& service
->IsSyncActive())
709 open_tabs_delegate_
= service
->GetOpenTabsUIDelegate();
711 return open_tabs_delegate_
;
714 void RecentTabsSubMenuModel::TabRestoreServiceChanged(
715 TabRestoreService
* service
) {
720 ui::MenuModelDelegate
* menu_model_delegate
= GetMenuModelDelegate();
721 if (menu_model_delegate
)
722 menu_model_delegate
->OnMenuStructureChanged();
725 void RecentTabsSubMenuModel::TabRestoreServiceDestroyed(
726 TabRestoreService
* service
) {
727 TabRestoreServiceChanged(service
);