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. 4 5#include "chrome/browser/ui/toolbar/recent_tabs_sub_menu_model.h" 6 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/common/pref_names.h" 29#include "chrome/grit/generated_resources.h" 30#include "components/favicon_base/favicon_types.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" 37 38#if defined(USE_ASH) 39#include "ash/accelerators/accelerator_table.h" 40#endif // defined(USE_ASH) 41 42namespace { 43 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. 62const int kFirstLocalTabCommandId = WrenchMenuModel::kMinRecentTabsCommandId; 63const int kFirstLocalWindowCommandId = 1031; 64const int kFirstOtherDevicesTabCommandId = 1051; 65const int kMinDeviceNameCommandId = 1100; 66const int kMaxDeviceNameCommandId = 1110; 67 68// The maximum number of local recently closed entries (tab or window) to be 69// shown in the menu. 70const int kMaxLocalEntries = 8; 71 72// Comparator function for use with std::sort that will sort sessions by 73// descending modified_time (i.e., most recent first). 74bool SortSessionsByRecency(const browser_sync::SyncedSession* s1, 75 const browser_sync::SyncedSession* s2) { 76 return s1->modified_time > s2->modified_time; 77} 78 79// Comparator function for use with std::sort that will sort tabs by 80// descending timestamp (i.e., most recent first). 81bool SortTabsByRecency(const SessionTab* t1, const SessionTab* t2) { 82 return t1->timestamp > t2->timestamp; 83} 84 85// Returns true if the command id identifies a tab menu item. 86bool IsTabModelCommandId(int command_id) { 87 return ((command_id >= kFirstLocalTabCommandId && 88 command_id < kFirstLocalWindowCommandId) || 89 (command_id >= kFirstOtherDevicesTabCommandId && 90 command_id < kMinDeviceNameCommandId)); 91} 92 93// Returns true if the command id identifies a window menu item. 94bool IsWindowModelCommandId(int command_id) { 95 return command_id >= kFirstLocalWindowCommandId && 96 command_id < kFirstOtherDevicesTabCommandId; 97} 98 99bool IsDeviceNameCommandId(int command_id) { 100 return command_id >= kMinDeviceNameCommandId && 101 command_id <= kMaxDeviceNameCommandId; 102} 103 104// Convert |tab_vector_index| to command id of menu item, with 105// |first_command_id| as the base command id. 106int 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; 110} 111 112// Convert |window_vector_index| to command id of menu item. 113int WindowVectorIndexToCommandId(int window_vector_index) { 114 int command_id = window_vector_index + kFirstLocalWindowCommandId; 115 DCHECK(IsWindowModelCommandId(command_id)); 116 return command_id; 117} 118 119// Convert |command_id| of menu item to index in |local_window_items_|. 120int CommandIdToWindowVectorIndex(int command_id) { 121 DCHECK(IsWindowModelCommandId(command_id)); 122 return command_id - kFirstLocalWindowCommandId; 123} 124 125} // namespace 126 127enum RecentTabAction { 128 LOCAL_SESSION_TAB = 0, 129 OTHER_DEVICE_TAB, 130 RESTORE_WINDOW, 131 SHOW_MORE, 132 LIMIT_RECENT_TAB_ACTION 133}; 134 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. 139struct RecentTabsSubMenuModel::TabNavigationItem { 140 TabNavigationItem() : tab_id(-1) {} 141 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) {} 150 151 // For use by std::set for sorting. 152 bool operator<(const TabNavigationItem& other) const { 153 return url < other.url; 154 } 155 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; 161}; 162 163const int RecentTabsSubMenuModel::kRecentlyClosedHeaderCommandId = 1120; 164const int RecentTabsSubMenuModel::kDisabledRecentlyClosedHeaderCommandId = 1121; 165 166RecentTabsSubMenuModel::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(); 184 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 190 } 191 192 Build(); 193 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; 204 } 205 } 206#else 207 if (accelerator_provider) { 208 accelerator_provider->GetAcceleratorForCommandId( 209 IDC_RESTORE_TAB, &reopen_closed_tab_accelerator_); 210 } 211#endif // defined(USE_ASH) 212} 213 214RecentTabsSubMenuModel::~RecentTabsSubMenuModel() { 215 TabRestoreService* service = 216 TabRestoreServiceFactory::GetForProfile(browser_->profile()); 217 if (service) 218 service->RemoveObserver(this); 219} 220 221bool RecentTabsSubMenuModel::IsCommandIdChecked(int command_id) const { 222 return false; 223} 224 225bool 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; 231 } 232 return true; 233} 234 235bool 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; 246 } 247 return false; 248} 249 250void 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; 258 } 259 260 DCHECK_NE(IDC_RECENT_TABS_NO_DEVICE_TABS, command_id); 261 DCHECK(!IsDeviceNameCommandId(command_id)); 262 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; 267 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()); 278 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); 285 } 286 } else { // Restore tab of session from other devices. 287 browser_sync::OpenTabsUIDelegate* open_tabs = GetOpenTabsUIDelegate(); 288 if (!open_tabs) 289 return; 290 const 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); 300 } 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); 311 } 312 } 313} 314 315int RecentTabsSubMenuModel::GetFirstRecentTabsCommandId() { 316 return WindowVectorIndexToCommandId(0); 317} 318 319const gfx::FontList* RecentTabsSubMenuModel::GetLabelFontListAt( 320 int index) const { 321 int command_id = GetCommandIdAt(index); 322 if (command_id == kRecentlyClosedHeaderCommandId || 323 IsDeviceNameCommandId(command_id)) { 324 return &ui::ResourceBundle::GetSharedInstance().GetFontList( 325 ui::ResourceBundle::BoldFont); 326 } 327 return NULL; 328} 329 330int RecentTabsSubMenuModel::GetMaxWidthForItemAtIndex(int item_index) const { 331 int command_id = GetCommandIdAt(item_index); 332 if (command_id == IDC_RECENT_TABS_NO_DEVICE_TABS || 333 command_id == kRecentlyClosedHeaderCommandId || 334 command_id == kDisabledRecentlyClosedHeaderCommandId) { 335 return -1; 336 } 337 return 320; 338} 339 340bool RecentTabsSubMenuModel::GetURLAndTitleForItemAtIndex( 341 int index, 342 std::string* url, 343 base::string16* title) { 344 int command_id = GetCommandIdAt(index); 345 if (IsTabModelCommandId(command_id)) { 346 TabNavigationItems* tab_items = NULL; 347 int tab_items_idx = CommandIdToTabVectorIndex(command_id, &tab_items); 348 const TabNavigationItem& item = (*tab_items)[tab_items_idx]; 349 *url = item.url.possibly_invalid_spec(); 350 *title = item.title; 351 return true; 352 } 353 return false; 354} 355 356void RecentTabsSubMenuModel::Build() { 357 // The menu contains: 358 // - Recently closed header, then list of local recently closed tabs/windows, 359 // then separator 360 // - device 1 section header, then list of tabs from device, then separator 361 // - device 2 section header, then list of tabs from device, then separator 362 // - device 3 section header, then list of tabs from device, then separator 363 // - More... to open the history tab to get more other devices. 364 // |local_tab_navigation_items_| and |other_devices_tab_navigation_items_| 365 // only contain navigatable (and hence executable) tab items for local 366 // recently closed tabs and tabs from other devices respectively. 367 // |local_window_items_| contains the local recently closed windows. 368 BuildLocalEntries(); 369 BuildTabsFromOtherDevices(); 370} 371 372void RecentTabsSubMenuModel::BuildLocalEntries() { 373 // All local items use InsertItem*At() to append or insert a menu item. 374 // We're appending if building the entries for the first time i.e. invoked 375 // from Constructor(), inserting when local entries change subsequently i.e. 376 // invoked from TabRestoreServiceChanged(). 377 378 DCHECK_EQ(last_local_model_index_, -1); 379 380 TabRestoreService* service = 381 TabRestoreServiceFactory::GetForProfile(browser_->profile()); 382 if (!service || service->entries().size() == 0) { 383 // This is to show a disabled restore tab entry with the accelerator to 384 // teach users about this command. 385 InsertItemWithStringIdAt(++last_local_model_index_, 386 kDisabledRecentlyClosedHeaderCommandId, 387 IDS_NEW_TAB_RECENTLY_CLOSED); 388 } else { 389 InsertItemWithStringIdAt(++last_local_model_index_, 390 kRecentlyClosedHeaderCommandId, 391 IDS_NEW_TAB_RECENTLY_CLOSED); 392 ui::ResourceBundle& rb = ui::ResourceBundle::GetSharedInstance(); 393 SetIcon(last_local_model_index_, 394 rb.GetNativeImageNamed(IDR_RECENTLY_CLOSED_WINDOW)); 395 396 int added_count = 0; 397 TabRestoreService::Entries entries = service->entries(); 398 for (TabRestoreService::Entries::const_iterator it = entries.begin(); 399 it != entries.end() && added_count < kMaxLocalEntries; ++it) { 400 TabRestoreService::Entry* entry = *it; 401 if (entry->type == TabRestoreService::TAB) { 402 TabRestoreService::Tab* tab = 403 static_cast<TabRestoreService::Tab*>(entry); 404 const sessions::SerializedNavigationEntry& current_navigation = 405 tab->navigations.at(tab->current_navigation_index); 406 BuildLocalTabItem( 407 entry->id, 408 current_navigation.title(), 409 current_navigation.virtual_url(), 410 ++last_local_model_index_); 411 } else { 412 DCHECK_EQ(entry->type, TabRestoreService::WINDOW); 413 BuildLocalWindowItem( 414 entry->id, 415 static_cast<TabRestoreService::Window*>(entry)->tabs.size(), 416 ++last_local_model_index_); 417 } 418 ++added_count; 419 } 420 } 421 422 DCHECK_GE(last_local_model_index_, 0); 423} 424 425void RecentTabsSubMenuModel::BuildTabsFromOtherDevices() { 426 // All other devices' items (device headers or tabs) use AddItem*() to append 427 // a menu item, because they are always only built once (i.e. invoked from 428 // Constructor()) and don't change after that. 429 430 browser_sync::OpenTabsUIDelegate* open_tabs = GetOpenTabsUIDelegate(); 431 std::vector<const browser_sync::SyncedSession*> sessions; 432 if (!open_tabs || !open_tabs->GetAllForeignSessions(&sessions)) { 433 AddSeparator(ui::NORMAL_SEPARATOR); 434 AddItemWithStringId(IDC_RECENT_TABS_NO_DEVICE_TABS, 435 IDS_RECENT_TABS_NO_DEVICE_TABS); 436 return; 437 } 438 439 // Sort sessions from most recent to least recent. 440 std::sort(sessions.begin(), sessions.end(), SortSessionsByRecency); 441 442 const size_t kMaxSessionsToShow = 3; 443 size_t num_sessions_added = 0; 444 for (size_t i = 0; 445 i < sessions.size() && num_sessions_added < kMaxSessionsToShow; ++i) { 446 const browser_sync::SyncedSession* session = sessions[i]; 447 const std::string& session_tag = session->session_tag; 448 449 // Get windows of session. 450 std::vector<const SessionWindow*> windows; 451 if (!open_tabs->GetForeignSession(session_tag, &windows) || 452 windows.empty()) { 453 continue; 454 } 455 456 // Collect tabs from all windows of session, pruning those that are not 457 // syncable or are NewTabPage, then sort them from most recent to least 458 // recent, independent of which window the tabs were from. 459 std::vector<const SessionTab*> tabs_in_session; 460 for (size_t j = 0; j < windows.size(); ++j) { 461 const SessionWindow* window = windows[j]; 462 for (size_t t = 0; t < window->tabs.size(); ++t) { 463 const SessionTab* tab = window->tabs[t]; 464 if (tab->navigations.empty()) 465 continue; 466 const sessions::SerializedNavigationEntry& current_navigation = 467 tab->navigations.at(tab->normalized_navigation_index()); 468 if (chrome::IsNTPURL(current_navigation.virtual_url(), 469 browser_->profile())) { 470 continue; 471 } 472 tabs_in_session.push_back(tab); 473 } 474 } 475 if (tabs_in_session.empty()) 476 continue; 477 std::sort(tabs_in_session.begin(), tabs_in_session.end(), 478 SortTabsByRecency); 479 480 // Add the header for the device session. 481 DCHECK(!session->session_name.empty()); 482 AddSeparator(ui::NORMAL_SEPARATOR); 483 int command_id = kMinDeviceNameCommandId + i; 484 DCHECK_LE(command_id, kMaxDeviceNameCommandId); 485 AddItem(command_id, base::UTF8ToUTF16(session->session_name)); 486 AddDeviceFavicon(GetItemCount() - 1, session->device_type); 487 488 // Build tab menu items from sorted session tabs. 489 const size_t kMaxTabsPerSessionToShow = 4; 490 for (size_t k = 0; 491 k < std::min(tabs_in_session.size(), kMaxTabsPerSessionToShow); 492 ++k) { 493 BuildOtherDevicesTabItem(session_tag, *tabs_in_session[k]); 494 } // for all tabs in one session 495 496 ++num_sessions_added; 497 } // for all sessions 498 499 // We are not supposed to get here unless at least some items were added. 500 DCHECK_GT(GetItemCount(), 0); 501 AddSeparator(ui::NORMAL_SEPARATOR); 502 AddItemWithStringId(IDC_SHOW_HISTORY, IDS_RECENT_TABS_MORE); 503} 504 505void RecentTabsSubMenuModel::BuildLocalTabItem(int session_id, 506 const base::string16& title, 507 const GURL& url, 508 int curr_model_index) { 509 TabNavigationItem item(std::string(), session_id, title, url); 510 int command_id = TabVectorIndexToCommandId( 511 local_tab_navigation_items_.size(), kFirstLocalTabCommandId); 512 // See comments in BuildLocalEntries() about usage of InsertItem*At(). 513 // There may be no tab title, in which case, use the url as tab title. 514 InsertItemAt(curr_model_index, command_id, 515 title.empty() ? base::UTF8ToUTF16(item.url.spec()) : title); 516 AddTabFavicon(command_id, item.url); 517 local_tab_navigation_items_.push_back(item); 518} 519 520void RecentTabsSubMenuModel::BuildLocalWindowItem( 521 const SessionID::id_type& window_id, 522 int num_tabs, 523 int curr_model_index) { 524 int command_id = WindowVectorIndexToCommandId(local_window_items_.size()); 525 // See comments in BuildLocalEntries() about usage of InsertItem*At(). 526 if (num_tabs == 1) { 527 InsertItemWithStringIdAt(curr_model_index, command_id, 528 IDS_NEW_TAB_RECENTLY_CLOSED_WINDOW_SINGLE); 529 } else { 530 InsertItemAt(curr_model_index, command_id, l10n_util::GetStringFUTF16( 531 IDS_NEW_TAB_RECENTLY_CLOSED_WINDOW_MULTIPLE, 532 base::IntToString16(num_tabs))); 533 } 534 ui::ResourceBundle& rb = ui::ResourceBundle::GetSharedInstance(); 535 SetIcon(curr_model_index, rb.GetNativeImageNamed(IDR_RECENTLY_CLOSED_WINDOW)); 536 local_window_items_.push_back(window_id); 537} 538 539void RecentTabsSubMenuModel::BuildOtherDevicesTabItem( 540 const std::string& session_tag, 541 const SessionTab& tab) { 542 const sessions::SerializedNavigationEntry& current_navigation = 543 tab.navigations.at(tab.normalized_navigation_index()); 544 TabNavigationItem item(session_tag, tab.tab_id.id(), 545 current_navigation.title(), 546 current_navigation.virtual_url()); 547 int command_id = TabVectorIndexToCommandId( 548 other_devices_tab_navigation_items_.size(), 549 kFirstOtherDevicesTabCommandId); 550 // See comments in BuildTabsFromOtherDevices() about usage of AddItem*(). 551 // There may be no tab title, in which case, use the url as tab title. 552 AddItem(command_id, 553 current_navigation.title().empty() ? 554 base::UTF8ToUTF16(item.url.spec()) : current_navigation.title()); 555 AddTabFavicon(command_id, item.url); 556 other_devices_tab_navigation_items_.push_back(item); 557} 558 559void RecentTabsSubMenuModel::AddDeviceFavicon( 560 int index_in_menu, 561 browser_sync::SyncedSession::DeviceType device_type) { 562 int favicon_id = -1; 563 switch (device_type) { 564 case browser_sync::SyncedSession::TYPE_PHONE: 565 favicon_id = IDR_PHONE_FAVICON; 566 break; 567 568 case browser_sync::SyncedSession::TYPE_TABLET: 569 favicon_id = IDR_TABLET_FAVICON; 570 break; 571 572 case browser_sync::SyncedSession::TYPE_CHROMEOS: 573 case browser_sync::SyncedSession::TYPE_WIN: 574 case browser_sync::SyncedSession::TYPE_MACOSX: 575 case browser_sync::SyncedSession::TYPE_LINUX: 576 case browser_sync::SyncedSession::TYPE_OTHER: 577 case browser_sync::SyncedSession::TYPE_UNSET: 578 favicon_id = IDR_LAPTOP_FAVICON; 579 break; 580 } 581 582 ui::ResourceBundle& rb = ui::ResourceBundle::GetSharedInstance(); 583 SetIcon(index_in_menu, rb.GetNativeImageNamed(favicon_id)); 584} 585 586void RecentTabsSubMenuModel::AddTabFavicon(int command_id, const GURL& url) { 587 bool is_local_tab = command_id < kFirstOtherDevicesTabCommandId; 588 int index_in_menu = GetIndexOfCommandId(command_id); 589 590 if (!is_local_tab) { 591 // If tab has synced favicon, use it. 592 // Note that currently, other devices' tabs only have favicons if 593 // --sync-tab-favicons switch is on; according to zea@, this flag is now 594 // automatically enabled for iOS and android, and they're looking into 595 // enabling it for other platforms. 596 browser_sync::OpenTabsUIDelegate* open_tabs = GetOpenTabsUIDelegate(); 597 scoped_refptr<base::RefCountedMemory> favicon_png; 598 if (open_tabs && 599 open_tabs->GetSyncedFaviconForPageURL(url.spec(), &favicon_png)) { 600 gfx::Image image = gfx::Image::CreateFrom1xPNGBytes(favicon_png); 601 SetIcon(index_in_menu, image); 602 return; 603 } 604 } 605 606 // Otherwise, start to fetch the favicon from local history asynchronously. 607 // Set default icon first. 608 SetIcon(index_in_menu, default_favicon_); 609 // Start request to fetch actual icon if possible. 610 FaviconService* favicon_service = FaviconServiceFactory::GetForProfile( 611 browser_->profile(), Profile::EXPLICIT_ACCESS); 612 if (!favicon_service) 613 return; 614 615 favicon_service->GetFaviconImageForPageURL( 616 url, 617 base::Bind(&RecentTabsSubMenuModel::OnFaviconDataAvailable, 618 weak_ptr_factory_.GetWeakPtr(), 619 command_id), 620 is_local_tab ? &local_tab_cancelable_task_tracker_ 621 : &other_devices_tab_cancelable_task_tracker_); 622} 623 624void RecentTabsSubMenuModel::OnFaviconDataAvailable( 625 int command_id, 626 const favicon_base::FaviconImageResult& image_result) { 627 if (image_result.image.IsEmpty()) 628 return; 629 int index_in_menu = GetIndexOfCommandId(command_id); 630 DCHECK_GT(index_in_menu, -1); 631 SetIcon(index_in_menu, image_result.image); 632 ui::MenuModelDelegate* menu_model_delegate = GetMenuModelDelegate(); 633 if (menu_model_delegate) 634 menu_model_delegate->OnIconChanged(index_in_menu); 635} 636 637int RecentTabsSubMenuModel::CommandIdToTabVectorIndex( 638 int command_id, 639 TabNavigationItems** tab_items) { 640 DCHECK(IsTabModelCommandId(command_id)); 641 if (command_id >= kFirstOtherDevicesTabCommandId) { 642 *tab_items = &other_devices_tab_navigation_items_; 643 return command_id - kFirstOtherDevicesTabCommandId; 644 } 645 *tab_items = &local_tab_navigation_items_; 646 return command_id - kFirstLocalTabCommandId; 647} 648 649void RecentTabsSubMenuModel::ClearLocalEntries() { 650 // Remove local items (recently closed tabs and windows) from menumodel. 651 while (last_local_model_index_ >= 0) 652 RemoveItemAt(last_local_model_index_--); 653 654 // Cancel asynchronous FaviconService::GetFaviconImageForPageURL() tasks of 655 // all 656 // local tabs. 657 local_tab_cancelable_task_tracker_.TryCancelAll(); 658 659 // Remove all local tab navigation items. 660 local_tab_navigation_items_.clear(); 661 662 // Remove all local window items. 663 local_window_items_.clear(); 664} 665 666browser_sync::OpenTabsUIDelegate* 667 RecentTabsSubMenuModel::GetOpenTabsUIDelegate() { 668 if (!open_tabs_delegate_) { 669 ProfileSyncService* service = ProfileSyncServiceFactory::GetInstance()-> 670 GetForProfile(browser_->profile()); 671 // Only return the delegate if it exists and it is done syncing sessions. 672 if (service && service->ShouldPushChanges()) 673 open_tabs_delegate_ = service->GetOpenTabsUIDelegate(); 674 } 675 return open_tabs_delegate_; 676} 677 678void RecentTabsSubMenuModel::TabRestoreServiceChanged( 679 TabRestoreService* service) { 680 ClearLocalEntries(); 681 682 BuildLocalEntries(); 683 684 ui::MenuModelDelegate* menu_model_delegate = GetMenuModelDelegate(); 685 if (menu_model_delegate) 686 menu_model_delegate->OnMenuStructureChanged(); 687} 688 689void RecentTabsSubMenuModel::TabRestoreServiceDestroyed( 690 TabRestoreService* service) { 691 TabRestoreServiceChanged(service); 692} 693