ExtensionSyncService: listen for relevant changes instead of being explicitly called...
[chromium-blink-merge.git] / chrome / browser / ui / toolbar / recent_tabs_sub_menu_model.cc
blobf285ee8da4a5e81f41dba21551260c1fadec4299
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/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_public2.h"
41 #include "ui/native_theme/common_theme.h"
42 #include "ui/native_theme/native_theme.h"
43 #endif
45 #if defined(USE_ASH)
46 #include "ash/accelerators/accelerator_table.h"
47 #endif // defined(USE_ASH)
49 namespace {
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
76 // shown in the menu.
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));
114 return 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));
121 return 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) {
132 SkColor grey;
133 ui::CommonThemeGetSystemColor(ui::NativeTheme::kColorId_ChromeIconGrey,
134 &grey);
135 return gfx::Image(gfx::CreateVectorIcon(id, 16, grey));
137 #endif
139 } // namespace
141 enum RecentTabAction {
142 LOCAL_SESSION_TAB = 0,
143 OTHER_DEVICE_TAB,
144 RESTORE_WINDOW,
145 SHOW_MORE,
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
152 // restore the tab.
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,
159 const GURL& url)
160 : session_tag(session_tag),
161 tab_id(tab_id),
162 title(title),
163 url(url) {}
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;
174 GURL url;
177 const int RecentTabsSubMenuModel::kRecentlyClosedHeaderCommandId = 1120;
178 const int RecentTabsSubMenuModel::kDisabledRecentlyClosedHeaderCommandId = 1121;
180 RecentTabsSubMenuModel::RecentTabsSubMenuModel(
181 ui::AcceleratorProvider* accelerator_provider,
182 Browser* browser,
183 sync_driver::OpenTabsUIDelegate* open_tabs_delegate)
184 : ui::SimpleMenuModel(this),
185 browser_(browser),
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());
196 if (service) {
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);
203 #endif
206 Build();
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|.
211 #if defined(USE_ASH)
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);
217 break;
220 #else
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());
236 if (service)
237 service->RemoveObserver(this);
240 bool RecentTabsSubMenuModel::IsCommandIdChecked(int command_id) const {
241 return false;
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)) {
249 return false;
251 return true;
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_;
264 return true;
267 if (command_id == IDC_SHOW_HISTORY) {
268 *accelerator = show_history_accelerator_;
269 return true;
272 return false;
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));
282 return;
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();
315 if (!open_tabs)
316 return;
317 const sessions::SessionTab* tab;
318 if (!open_tabs->GetForeignTab(item.session_tag, item.tab_id, &tab))
319 return;
320 if (tab->navigations.empty())
321 return;
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(),
328 *tab, disposition);
330 } else {
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,
347 LIMIT_MENU_ACTION);
350 int RecentTabsSubMenuModel::GetFirstRecentTabsCommandId() {
351 return WindowVectorIndexToCommandId(0);
354 const gfx::FontList* RecentTabsSubMenuModel::GetLabelFontListAt(
355 int index) const {
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);
362 return NULL;
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) {
370 return -1;
372 return 320;
375 bool RecentTabsSubMenuModel::GetURLAndTitleForItemAtIndex(
376 int index,
377 std::string* url,
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();
385 *title = item.title;
386 return true;
388 return false;
391 void RecentTabsSubMenuModel::Build() {
392 // The menu contains:
393 // - History to open the full history tab.
394 // - Separator
395 // - Recently closed header, then list of local recently closed tabs/windows,
396 // then separator
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);
406 BuildLocalEntries();
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);
426 } else {
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));
434 #else
435 SetIcon(last_local_model_index_, CreateFavicon(gfx::VectorIconId::TAB));
436 #endif
438 int added_count = 0;
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);
448 BuildLocalTabItem(
449 entry->id,
450 current_navigation.title(),
451 current_navigation.virtual_url(),
452 ++last_local_model_index_);
453 } else {
454 DCHECK_EQ(entry->type, TabRestoreService::WINDOW);
455 BuildLocalWindowItem(
456 entry->id,
457 static_cast<TabRestoreService::Window*>(entry)->tabs.size(),
458 ++last_local_model_index_);
460 ++added_count;
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);
477 return;
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;
485 for (size_t i = 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())
494 continue;
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;
506 for (size_t k = 0;
507 k < std::min(tabs_in_session.size(), kMaxTabsPerSessionToShow);
508 ++k) {
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,
521 const GURL& url,
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,
536 int num_tabs,
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));
545 #else
546 SetIcon(curr_model_index, CreateFavicon(gfx::VectorIconId::TAB));
547 #endif
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.
564 AddItem(command_id,
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(
572 int index_in_menu,
573 sync_driver::SyncedSession::DeviceType device_type) {
574 #if defined(OS_MACOSX)
575 int favicon_id = -1;
576 switch (device_type) {
577 case sync_driver::SyncedSession::TYPE_PHONE:
578 favicon_id = IDR_PHONE_FAVICON;
579 break;
581 case sync_driver::SyncedSession::TYPE_TABLET:
582 favicon_id = IDR_TABLET_FAVICON;
583 break;
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;
592 break;
595 ui::ResourceBundle& rb = ui::ResourceBundle::GetSharedInstance();
596 SetIcon(index_in_menu, rb.GetNativeImageNamed(favicon_id));
597 #else
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;
602 break;
604 case sync_driver::SyncedSession::TYPE_TABLET:
605 favicon_id = gfx::VectorIconId::TABLET;
606 break;
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;
615 break;
618 SetIcon(index_in_menu, CreateFavicon(favicon_id));
619 #endif
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);
626 if (!is_local_tab) {
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;
634 if (open_tabs &&
635 open_tabs->GetSyncedFaviconForPageURL(url.spec(), &favicon_png)) {
636 gfx::Image image = gfx::Image::CreateFrom1xPNGBytes(favicon_png);
637 SetIcon(index_in_menu, image);
638 return;
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)
650 return;
652 favicon_service->GetFaviconImageForPageURL(
653 url,
654 base::Bind(&RecentTabsSubMenuModel::OnFaviconDataAvailable,
655 weak_ptr_factory_.GetWeakPtr(),
656 command_id),
657 is_local_tab ? &local_tab_cancelable_task_tracker_
658 : &other_devices_tab_cancelable_task_tracker_);
661 void RecentTabsSubMenuModel::OnFaviconDataAvailable(
662 int command_id,
663 const favicon_base::FaviconImageResult& image_result) {
664 if (image_result.image.IsEmpty())
665 return;
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(
675 int command_id,
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
692 // all local tabs.
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) {
716 ClearLocalEntries();
718 BuildLocalEntries();
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);