Home | History | Annotate | Download | only in toolbar
      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/strings/string_number_conversions.h"
     10 #include "base/strings/utf_string_conversions.h"
     11 #include "chrome/app/chrome_command_ids.h"
     12 #include "chrome/browser/favicon/favicon_service_factory.h"
     13 #include "chrome/browser/prefs/scoped_user_pref_update.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/session_model_associator.h"
     21 #include "chrome/browser/sync/glue/synced_session.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/common/favicon/favicon_types.h"
     28 #include "chrome/common/pref_names.h"
     29 #include "grit/browser_resources.h"
     30 #include "grit/generated_resources.h"
     31 #include "grit/theme_resources.h"
     32 #include "grit/ui_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/gfx/favicon_size.h"
     37 
     38 #if defined(USE_ASH)
     39 #include "ash/accelerators/accelerator_table.h"
     40 #endif  // defined(USE_ASH)
     41 
     42 namespace {
     43 
     44 // First comamnd id for navigatable (and hence executable) tab menu item.
     45 // The models and menu are not 1-1:
     46 // - menu has "Reopen closed tab", "No tabs from other devices", device section
     47 //   headers, separators and executable tab items.
     48 // - |tab_navigation_items_| only has navigatabale/executable tab items.
     49 // - |window_items_| only has executable open window items.
     50 // Using an initial command ids for tab/window items makes it easier and less
     51 // error-prone to manipulate the models and menu.
     52 // These values must be bigger than the maximum possible number of items in
     53 // menu, so that index of last menu item doesn't clash with this value when menu
     54 // items are retrieved via GetIndexOfCommandId.
     55 const int kFirstTabCommandId = 100;
     56 const int kFirstWindowCommandId = 200;
     57 
     58 // The maximum number of recently closed entries to be shown in the menu.
     59 const int kMaxRecentlyClosedEntries = 8;
     60 
     61 // Comparator function for use with std::sort that will sort sessions by
     62 // descending modified_time (i.e., most recent first).
     63 bool SortSessionsByRecency(const browser_sync::SyncedSession* s1,
     64                            const browser_sync::SyncedSession* s2) {
     65   return s1->modified_time > s2->modified_time;
     66 }
     67 
     68 // Comparator function for use with std::sort that will sort tabs by
     69 // descending timestamp (i.e., most recent first).
     70 bool SortTabsByRecency(const SessionTab* t1, const SessionTab* t2) {
     71   return t1->timestamp > t2->timestamp;
     72 }
     73 
     74 // Returns true if the command id is related to a tab model index.
     75 bool IsTabModelCommandId(int command_id) {
     76   return command_id >= kFirstTabCommandId && command_id < kFirstWindowCommandId;
     77 }
     78 
     79 // Returns true if the command id is related to a window model index.
     80 bool IsWindowModelCommandId(int command_id) {
     81   return command_id >= kFirstWindowCommandId &&
     82          command_id < RecentTabsSubMenuModel::kRecentlyClosedHeaderCommandId;
     83 }
     84 
     85 // Convert |tab_model_index| to command id of menu item.
     86 int TabModelIndexToCommandId(int tab_model_index) {
     87   int command_id = tab_model_index + kFirstTabCommandId;
     88   DCHECK_LT(command_id, kFirstWindowCommandId);
     89   return command_id;
     90 }
     91 
     92 // Convert |command_id| of menu item to index in tab model.
     93 int CommandIdToTabModelIndex(int command_id) {
     94   DCHECK_GE(command_id, kFirstTabCommandId);
     95   DCHECK_LT(command_id, kFirstWindowCommandId);
     96   return command_id - kFirstTabCommandId;
     97 }
     98 
     99 // Convert |window_model_index| to command id of menu item.
    100 int WindowModelIndexToCommandId(int window_model_index) {
    101   int command_id = window_model_index + kFirstWindowCommandId;
    102   DCHECK_LT(command_id, RecentTabsSubMenuModel::kRecentlyClosedHeaderCommandId);
    103   return command_id;
    104 }
    105 
    106 // Convert |command_id| of menu item to index in window model.
    107 int CommandIdToWindowModelIndex(int command_id) {
    108   DCHECK_GE(command_id, kFirstWindowCommandId);
    109   DCHECK_LT(command_id, RecentTabsSubMenuModel::kRecentlyClosedHeaderCommandId);
    110   return command_id - kFirstWindowCommandId;
    111 }
    112 
    113 }  // namespace
    114 
    115 enum RecentTabAction {
    116   LOCAL_SESSION_TAB = 0,
    117   OTHER_DEVICE_TAB,
    118   RESTORE_WINDOW,
    119   SHOW_MORE,
    120   LIMIT_RECENT_TAB_ACTION
    121 };
    122 
    123 // An element in |RecentTabsSubMenuModel::tab_navigation_items_| that stores
    124 // the navigation information of a local or foreign tab required to restore the
    125 // tab.
    126 struct RecentTabsSubMenuModel::TabNavigationItem {
    127   TabNavigationItem() : tab_id(-1) {}
    128 
    129   TabNavigationItem(const std::string& session_tag,
    130                     const SessionID::id_type& tab_id,
    131                     const string16& title,
    132                     const GURL& url)
    133       : session_tag(session_tag),
    134         tab_id(tab_id),
    135         title(title),
    136         url(url) {}
    137 
    138   // For use by std::set for sorting.
    139   bool operator<(const TabNavigationItem& other) const {
    140     return url < other.url;
    141   }
    142 
    143   std::string session_tag;  // Empty for local tabs, non-empty for foreign tabs.
    144   SessionID::id_type tab_id;  // -1 for invalid, >= 0 otherwise.
    145   string16 title;
    146   GURL url;
    147 };
    148 
    149 const int RecentTabsSubMenuModel::kRecentlyClosedHeaderCommandId = 500;
    150 const int RecentTabsSubMenuModel::kDisabledRecentlyClosedHeaderCommandId = 501;
    151 const int RecentTabsSubMenuModel::kDeviceNameCommandId = 1000;
    152 
    153 RecentTabsSubMenuModel::RecentTabsSubMenuModel(
    154     ui::AcceleratorProvider* accelerator_provider,
    155     Browser* browser,
    156     browser_sync::SessionModelAssociator* associator)
    157     : ui::SimpleMenuModel(this),
    158       browser_(browser),
    159       associator_(associator),
    160       default_favicon_(ResourceBundle::GetSharedInstance().
    161           GetNativeImageNamed(IDR_DEFAULT_FAVICON)),
    162       weak_ptr_factory_(this) {
    163   Build();
    164 
    165   // Retrieve accelerator key for IDC_RESTORE_TAB now, because on ASH, it's not
    166   // defined in |accelerator_provider|, but in shell, so simply retrieve it now
    167   // for all ASH and non-ASH for use in |GetAcceleratorForCommandId|.
    168 #if defined(USE_ASH)
    169   for (size_t i = 0; i < ash::kAcceleratorDataLength; ++i) {
    170     const ash::AcceleratorData& accel_data = ash::kAcceleratorData[i];
    171     if (accel_data.action == ash::RESTORE_TAB) {
    172       reopen_closed_tab_accelerator_ = ui::Accelerator(accel_data.keycode,
    173                                                        accel_data.modifiers);
    174       break;
    175     }
    176   }
    177 #else
    178   if (accelerator_provider) {
    179     accelerator_provider->GetAcceleratorForCommandId(
    180         IDC_RESTORE_TAB, &reopen_closed_tab_accelerator_);
    181   }
    182 #endif  // defined(USE_ASH)
    183 }
    184 
    185 RecentTabsSubMenuModel::~RecentTabsSubMenuModel() {
    186 }
    187 
    188 bool RecentTabsSubMenuModel::IsCommandIdChecked(int command_id) const {
    189   return false;
    190 }
    191 
    192 bool RecentTabsSubMenuModel::IsCommandIdEnabled(int command_id) const {
    193   if (command_id == kRecentlyClosedHeaderCommandId ||
    194       command_id == kDisabledRecentlyClosedHeaderCommandId ||
    195       command_id == kDeviceNameCommandId ||
    196       command_id == IDC_RECENT_TABS_NO_DEVICE_TABS) {
    197     return false;
    198   }
    199   return true;
    200 }
    201 
    202 bool RecentTabsSubMenuModel::GetAcceleratorForCommandId(
    203     int command_id, ui::Accelerator* accelerator) {
    204   // If there are no recently closed items, we show the accelerator beside
    205   // the header, otherwise, we show it beside the first item underneath it.
    206   int index_in_menu = GetIndexOfCommandId(command_id);
    207   int header_index = GetIndexOfCommandId(kRecentlyClosedHeaderCommandId);
    208   if ((command_id == kDisabledRecentlyClosedHeaderCommandId ||
    209        (header_index != -1 && index_in_menu == header_index + 1)) &&
    210       reopen_closed_tab_accelerator_.key_code() != ui::VKEY_UNKNOWN) {
    211     *accelerator = reopen_closed_tab_accelerator_;
    212     return true;
    213   }
    214   return false;
    215 }
    216 
    217 void RecentTabsSubMenuModel::ExecuteCommand(int command_id, int event_flags) {
    218   if (command_id == IDC_SHOW_HISTORY) {
    219     UMA_HISTOGRAM_ENUMERATION("WrenchMenu.RecentTabsSubMenu", SHOW_MORE,
    220                               LIMIT_RECENT_TAB_ACTION);
    221     // We show all "other devices" on the history page.
    222     chrome::ExecuteCommandWithDisposition(browser_, IDC_SHOW_HISTORY,
    223         ui::DispositionFromEventFlags(event_flags));
    224     return;
    225   }
    226 
    227   DCHECK_NE(kDeviceNameCommandId, command_id);
    228   DCHECK_NE(IDC_RECENT_TABS_NO_DEVICE_TABS, command_id);
    229 
    230   WindowOpenDisposition disposition =
    231       ui::DispositionFromEventFlags(event_flags);
    232   if (disposition == CURRENT_TAB)  // Force to open a new foreground tab.
    233     disposition = NEW_FOREGROUND_TAB;
    234 
    235   TabRestoreService* service =
    236       TabRestoreServiceFactory::GetForProfile(browser_->profile());
    237   TabRestoreServiceDelegate* delegate =
    238       TabRestoreServiceDelegate::FindDelegateForWebContents(
    239           browser_->tab_strip_model()->GetActiveWebContents());
    240   if (IsTabModelCommandId(command_id)) {
    241     int model_idx = CommandIdToTabModelIndex(command_id);
    242     DCHECK(model_idx >= 0 &&
    243            model_idx < static_cast<int>(tab_navigation_items_.size()));
    244     const TabNavigationItem& item = tab_navigation_items_[model_idx];
    245     DCHECK(item.tab_id > -1 && item.url.is_valid());
    246 
    247     if (item.session_tag.empty()) {  // Restore tab of local session.
    248       if (service && delegate) {
    249         UMA_HISTOGRAM_ENUMERATION("WrenchMenu.RecentTabsSubMenu",
    250                                   LOCAL_SESSION_TAB, LIMIT_RECENT_TAB_ACTION);
    251         service->RestoreEntryById(delegate, item.tab_id,
    252                                   browser_->host_desktop_type(), disposition);
    253       }
    254     } else {  // Restore tab of foreign session.
    255       browser_sync::SessionModelAssociator* associator = GetModelAssociator();
    256       if (!associator)
    257         return;
    258       const SessionTab* tab;
    259       if (!associator->GetForeignTab(item.session_tag, item.tab_id, &tab))
    260         return;
    261       if (tab->navigations.empty())
    262         return;
    263       UMA_HISTOGRAM_ENUMERATION("WrenchMenu.RecentTabsSubMenu",
    264                                 OTHER_DEVICE_TAB, LIMIT_RECENT_TAB_ACTION);
    265       SessionRestore::RestoreForeignSessionTab(
    266           browser_->tab_strip_model()->GetActiveWebContents(),
    267           *tab, disposition);
    268     }
    269   } else {
    270     DCHECK(IsWindowModelCommandId(command_id));
    271     if (service && delegate) {
    272       int model_idx = CommandIdToWindowModelIndex(command_id);
    273       DCHECK(model_idx >= 0 &&
    274              model_idx < static_cast<int>(window_items_.size()));
    275       UMA_HISTOGRAM_ENUMERATION("WrenchMenu.RecentTabsSubMenu", RESTORE_WINDOW,
    276                                 LIMIT_RECENT_TAB_ACTION);
    277       service->RestoreEntryById(delegate, window_items_[model_idx],
    278                                 browser_->host_desktop_type(), disposition);
    279     }
    280   }
    281 }
    282 
    283 const gfx::Font* RecentTabsSubMenuModel::GetLabelFontAt(int index) const {
    284   int command_id = GetCommandIdAt(index);
    285   if (command_id == kDeviceNameCommandId ||
    286       command_id == kRecentlyClosedHeaderCommandId) {
    287     return &ResourceBundle::GetSharedInstance().GetFont(
    288         ResourceBundle::BoldFont);
    289   }
    290   return NULL;
    291 }
    292 
    293 int RecentTabsSubMenuModel::GetMaxWidthForItemAtIndex(int item_index) const {
    294   int command_id = GetCommandIdAt(item_index);
    295   if (command_id == IDC_RECENT_TABS_NO_DEVICE_TABS ||
    296       command_id == kRecentlyClosedHeaderCommandId ||
    297       command_id == kDisabledRecentlyClosedHeaderCommandId) {
    298     return -1;
    299   }
    300   return 320;
    301 }
    302 
    303 bool RecentTabsSubMenuModel::GetURLAndTitleForItemAtIndex(
    304     int index,
    305     std::string* url,
    306     string16* title) const {
    307   int command_id = GetCommandIdAt(index);
    308   if (IsTabModelCommandId(command_id)) {
    309     int model_idx = CommandIdToTabModelIndex(command_id);
    310     DCHECK(model_idx >= 0 &&
    311            model_idx < static_cast<int>(tab_navigation_items_.size()));
    312     *url = tab_navigation_items_[model_idx].url.possibly_invalid_spec();
    313     *title = tab_navigation_items_[model_idx].title;
    314     return true;
    315   }
    316   return false;
    317 }
    318 
    319 void RecentTabsSubMenuModel::Build() {
    320   // The menu contains:
    321   // - Recently closed tabs header, then list of tabs, then separator
    322   // - device 1 section header, then list of tabs from device, then separator
    323   // - device 2 section header, then list of tabs from device, then separator
    324   // - device 3 section header, then list of tabs from device, then separator
    325   // - More... to open the history tab to get more other devices.
    326   // |tab_navigation_items_| only contains navigatable (and hence executable)
    327   // tab items for other devices, and |window_items_| contains the recently
    328   // closed windows.
    329   BuildRecentTabs();
    330   BuildDevices();
    331 }
    332 
    333 void RecentTabsSubMenuModel::BuildRecentTabs() {
    334   ListValue recently_closed_list;
    335   TabRestoreService* service =
    336       TabRestoreServiceFactory::GetForProfile(browser_->profile());
    337   if (service) {
    338     // This does nothing if the tabs have already been loaded or they
    339     // shouldn't be loaded.
    340     service->LoadTabsFromLastSession();
    341   }
    342 
    343   if (!service || service->entries().size() == 0) {
    344     // This is to show a disabled restore tab entry with the accelerator to
    345     // teach users about this command.
    346     AddItemWithStringId(kDisabledRecentlyClosedHeaderCommandId,
    347                         IDS_NEW_TAB_RECENTLY_CLOSED);
    348     return;
    349   }
    350 
    351   AddItemWithStringId(kRecentlyClosedHeaderCommandId,
    352                       IDS_NEW_TAB_RECENTLY_CLOSED);
    353   ResourceBundle& rb = ResourceBundle::GetSharedInstance();
    354   SetIcon(GetItemCount() - 1,
    355           rb.GetNativeImageNamed(IDR_RECENTLY_CLOSED_WINDOW));
    356 
    357   int added_count = 0;
    358   TabRestoreService::Entries entries = service->entries();
    359   for (TabRestoreService::Entries::const_iterator it = entries.begin();
    360        it != entries.end() && added_count < kMaxRecentlyClosedEntries; ++it) {
    361     TabRestoreService::Entry* entry = *it;
    362     if (entry->type == TabRestoreService::TAB) {
    363       TabRestoreService::Tab* tab = static_cast<TabRestoreService::Tab*>(entry);
    364       const sessions::SerializedNavigationEntry& current_navigation =
    365           tab->navigations.at(tab->current_navigation_index);
    366       BuildLocalTabItem(
    367           entry->id,
    368           current_navigation.title(),
    369           current_navigation.virtual_url());
    370     } else  {
    371       DCHECK_EQ(entry->type, TabRestoreService::WINDOW);
    372       BuildWindowItem(
    373           entry->id,
    374           static_cast<TabRestoreService::Window*>(entry)->tabs.size());
    375     }
    376     ++added_count;
    377   }
    378 }
    379 
    380 void RecentTabsSubMenuModel::BuildDevices() {
    381   browser_sync::SessionModelAssociator* associator = GetModelAssociator();
    382   std::vector<const browser_sync::SyncedSession*> sessions;
    383   if (!associator || !associator->GetAllForeignSessions(&sessions)) {
    384     AddSeparator(ui::NORMAL_SEPARATOR);
    385     AddItemWithStringId(IDC_RECENT_TABS_NO_DEVICE_TABS,
    386                         IDS_RECENT_TABS_NO_DEVICE_TABS);
    387     return;
    388   }
    389 
    390   // Sort sessions from most recent to least recent.
    391   std::sort(sessions.begin(), sessions.end(), SortSessionsByRecency);
    392 
    393   const size_t kMaxSessionsToShow = 3;
    394   size_t num_sessions_added = 0;
    395   for (size_t i = 0;
    396        i < sessions.size() && num_sessions_added < kMaxSessionsToShow; ++i) {
    397     const browser_sync::SyncedSession* session = sessions[i];
    398     const std::string& session_tag = session->session_tag;
    399 
    400     // Get windows of session.
    401     std::vector<const SessionWindow*> windows;
    402     if (!associator->GetForeignSession(session_tag, &windows) ||
    403         windows.empty()) {
    404       continue;
    405     }
    406 
    407     // Collect tabs from all windows of session, pruning those that are not
    408     // syncable or are NewTabPage, then sort them from most recent to least
    409     // recent, independent of which window the tabs were from.
    410     std::vector<const SessionTab*> tabs_in_session;
    411     for (size_t j = 0; j < windows.size(); ++j) {
    412       const SessionWindow* window = windows[j];
    413       for (size_t t = 0; t < window->tabs.size(); ++t) {
    414         const SessionTab* tab = window->tabs[t];
    415         if (tab->navigations.empty())
    416           continue;
    417         const sessions::SerializedNavigationEntry& current_navigation =
    418             tab->navigations.at(tab->normalized_navigation_index());
    419         if (chrome::IsNTPURL(current_navigation.virtual_url(),
    420                              browser_->profile())) {
    421           continue;
    422         }
    423         tabs_in_session.push_back(tab);
    424       }
    425     }
    426     if (tabs_in_session.empty())
    427       continue;
    428     std::sort(tabs_in_session.begin(), tabs_in_session.end(),
    429               SortTabsByRecency);
    430 
    431     // Add the header for the device session.
    432     DCHECK(!session->session_name.empty());
    433     AddSeparator(ui::NORMAL_SEPARATOR);
    434     AddItem(kDeviceNameCommandId, UTF8ToUTF16(session->session_name));
    435     AddDeviceFavicon(GetItemCount() - 1, session->device_type);
    436 
    437     // Build tab menu items from sorted session tabs.
    438     const size_t kMaxTabsPerSessionToShow = 4;
    439     for (size_t k = 0;
    440          k < std::min(tabs_in_session.size(), kMaxTabsPerSessionToShow);
    441          ++k) {
    442       BuildForeignTabItem(session_tag, *tabs_in_session[k]);
    443     }  // for all tabs in one session
    444 
    445     ++num_sessions_added;
    446   }  // for all sessions
    447 
    448   // We are not supposed to get here unless at least some items were added.
    449   DCHECK_GT(GetItemCount(), 0);
    450   AddSeparator(ui::NORMAL_SEPARATOR);
    451   AddItemWithStringId(IDC_SHOW_HISTORY, IDS_RECENT_TABS_MORE);
    452 }
    453 
    454 void RecentTabsSubMenuModel::BuildLocalTabItem(
    455     int session_id,
    456     const string16& title,
    457     const GURL& url) {
    458   TabNavigationItem item("", session_id, title, url);
    459   int command_id = TabModelIndexToCommandId(tab_navigation_items_.size());
    460   // There may be no tab title, in which case, use the url as tab title.
    461   AddItem(command_id, title.empty() ? UTF8ToUTF16(item.url.spec()) : title);
    462   AddTabFavicon(tab_navigation_items_.size(), command_id, item.url);
    463   tab_navigation_items_.push_back(item);
    464 }
    465 
    466 void RecentTabsSubMenuModel::BuildForeignTabItem(
    467     const std::string& session_tag,
    468     const SessionTab& tab) {
    469   const sessions::SerializedNavigationEntry& current_navigation =
    470       tab.navigations.at(tab.normalized_navigation_index());
    471   TabNavigationItem item(session_tag, tab.tab_id.id(),
    472                          current_navigation.title(),
    473                          current_navigation.virtual_url());
    474   int command_id = TabModelIndexToCommandId(tab_navigation_items_.size());
    475   // There may be no tab title, in which case, use the url as tab title.
    476   AddItem(command_id,
    477           current_navigation.title().empty() ?
    478               UTF8ToUTF16(item.url.spec()) : current_navigation.title());
    479   AddTabFavicon(tab_navigation_items_.size(), command_id, item.url);
    480   tab_navigation_items_.push_back(item);
    481 }
    482 
    483 void RecentTabsSubMenuModel::BuildWindowItem(
    484     const SessionID::id_type& window_id,
    485     int num_tabs) {
    486   int command_id = WindowModelIndexToCommandId(window_items_.size());
    487   if (num_tabs == 1) {
    488     AddItemWithStringId(command_id, IDS_NEW_TAB_RECENTLY_CLOSED_WINDOW_SINGLE);
    489   } else {
    490     AddItem(command_id, l10n_util::GetStringFUTF16(
    491         IDS_NEW_TAB_RECENTLY_CLOSED_WINDOW_MULTIPLE,
    492         base::IntToString16(num_tabs)));
    493   }
    494   ResourceBundle& rb = ResourceBundle::GetSharedInstance();
    495   SetIcon(GetItemCount() - 1,
    496           rb.GetNativeImageNamed(IDR_RECENTLY_CLOSED_WINDOW));
    497   window_items_.push_back(window_id);
    498 }
    499 
    500 void RecentTabsSubMenuModel::AddDeviceFavicon(
    501     int index_in_menu,
    502     browser_sync::SyncedSession::DeviceType device_type) {
    503   int favicon_id = -1;
    504   switch (device_type) {
    505     case browser_sync::SyncedSession::TYPE_PHONE:
    506       favicon_id = IDR_PHONE_FAVICON;
    507       break;
    508 
    509     case browser_sync::SyncedSession::TYPE_TABLET:
    510       favicon_id = IDR_TABLET_FAVICON;
    511       break;
    512 
    513     case browser_sync::SyncedSession::TYPE_CHROMEOS:
    514     case browser_sync::SyncedSession::TYPE_WIN:
    515     case browser_sync::SyncedSession::TYPE_MACOSX:
    516     case browser_sync::SyncedSession::TYPE_LINUX:
    517     case browser_sync::SyncedSession::TYPE_OTHER:
    518     case browser_sync::SyncedSession::TYPE_UNSET:
    519       favicon_id = IDR_LAPTOP_FAVICON;
    520       break;
    521   };
    522 
    523   ResourceBundle& rb = ResourceBundle::GetSharedInstance();
    524   SetIcon(index_in_menu, rb.GetNativeImageNamed(favicon_id));
    525 }
    526 
    527 void RecentTabsSubMenuModel::AddTabFavicon(int model_index,
    528                                            int command_id,
    529                                            const GURL& url) {
    530   int index_in_menu = GetIndexOfCommandId(command_id);
    531 
    532   // If tab has synced favicon, use it.
    533   // Note that currently, foreign tab only has favicon if --sync-tab-favicons
    534   // switch is on; according to zea@, this flag is now automatically enabled for
    535   // iOS and android, and they're looking into enabling it for other platforms.
    536   browser_sync::SessionModelAssociator* associator = GetModelAssociator();
    537   scoped_refptr<base::RefCountedMemory> favicon_png;
    538   if (associator &&
    539       associator->GetSyncedFaviconForPageURL(url.spec(), &favicon_png)) {
    540     gfx::Image image = gfx::Image::CreateFrom1xPNGBytes(
    541         favicon_png->front(),
    542         favicon_png->size());
    543     SetIcon(index_in_menu, image);
    544     return;
    545   }
    546 
    547   // Otherwise, start to fetch the favicon from local history asynchronously.
    548   // Set default icon first.
    549   SetIcon(index_in_menu, default_favicon_);
    550   // Start request to fetch actual icon if possible.
    551   FaviconService* favicon_service = FaviconServiceFactory::GetForProfile(
    552       browser_->profile(), Profile::EXPLICIT_ACCESS);
    553   if (!favicon_service)
    554     return;
    555 
    556   favicon_service->GetFaviconImageForURL(
    557       FaviconService::FaviconForURLParams(browser_->profile(),
    558                                           url,
    559                                           chrome::FAVICON,
    560                                           gfx::kFaviconSize),
    561       base::Bind(&RecentTabsSubMenuModel::OnFaviconDataAvailable,
    562                  weak_ptr_factory_.GetWeakPtr(),
    563                  command_id),
    564       &cancelable_task_tracker_);
    565 }
    566 
    567 void RecentTabsSubMenuModel::OnFaviconDataAvailable(
    568     int command_id,
    569     const chrome::FaviconImageResult& image_result) {
    570   if (image_result.image.IsEmpty())
    571     return;
    572   DCHECK(!tab_navigation_items_.empty());
    573   int index_in_menu = GetIndexOfCommandId(command_id);
    574   DCHECK(index_in_menu != -1);
    575   SetIcon(index_in_menu, image_result.image);
    576   if (GetMenuModelDelegate())
    577     GetMenuModelDelegate()->OnIconChanged(index_in_menu);
    578 }
    579 
    580 browser_sync::SessionModelAssociator*
    581     RecentTabsSubMenuModel::GetModelAssociator() {
    582   if (!associator_) {
    583     ProfileSyncService* service = ProfileSyncServiceFactory::GetInstance()->
    584         GetForProfile(browser_->profile());
    585     // Only return the associator if it exists and it is done syncing sessions.
    586     if (service && service->ShouldPushChanges())
    587       associator_ = service->GetSessionModelAssociator();
    588   }
    589   return associator_;
    590 }
    591