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/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 
     42 namespace {
     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.
     62 const int kFirstLocalTabCommandId = WrenchMenuModel::kMinRecentTabsCommandId;
     63 const int kFirstLocalWindowCommandId = 1031;
     64 const int kFirstOtherDevicesTabCommandId = 1051;
     65 const int kMinDeviceNameCommandId = 1100;
     66 const int kMaxDeviceNameCommandId = 1110;
     67 
     68 // The maximum number of local recently closed entries (tab or window) to be
     69 // shown in the menu.
     70 const 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).
     74 bool 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).
     81 bool 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.
     86 bool 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.
     94 bool IsWindowModelCommandId(int command_id) {
     95   return command_id >= kFirstLocalWindowCommandId &&
     96          command_id < kFirstOtherDevicesTabCommandId;
     97 }
     98 
     99 bool 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.
    106 int 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.
    113 int 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_|.
    120 int CommandIdToWindowVectorIndex(int command_id) {
    121   DCHECK(IsWindowModelCommandId(command_id));
    122   return command_id - kFirstLocalWindowCommandId;
    123 }
    124 
    125 }  // namespace
    126 
    127 enum 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.
    139 struct 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 
    163 const int RecentTabsSubMenuModel::kRecentlyClosedHeaderCommandId = 1120;
    164 const int RecentTabsSubMenuModel::kDisabledRecentlyClosedHeaderCommandId = 1121;
    165 
    166 RecentTabsSubMenuModel::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 
    214 RecentTabsSubMenuModel::~RecentTabsSubMenuModel() {
    215   TabRestoreService* service =
    216       TabRestoreServiceFactory::GetForProfile(browser_->profile());
    217   if (service)
    218     service->RemoveObserver(this);
    219 }
    220 
    221 bool RecentTabsSubMenuModel::IsCommandIdChecked(int command_id) const {
    222   return false;
    223 }
    224 
    225 bool 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 
    235 bool 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 
    250 void 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 
    315 int RecentTabsSubMenuModel::GetFirstRecentTabsCommandId() {
    316   return WindowVectorIndexToCommandId(0);
    317 }
    318 
    319 const 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 
    330 int 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 
    340 bool 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 
    356 void 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 
    372 void 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 
    425 void 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 
    505 void 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 
    520 void 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 
    539 void 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 
    559 void 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 
    586 void 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 
    624 void 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 
    637 int 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 
    649 void 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 
    666 browser_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 
    678 void 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 
    689 void RecentTabsSubMenuModel::TabRestoreServiceDestroyed(
    690     TabRestoreService* service) {
    691   TabRestoreServiceChanged(service);
    692 }
    693