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 <stddef.h>
8 
9 #include <algorithm>
10 
11 #include "base/bind.h"
12 #include "base/metrics/histogram_macros.h"
13 #include "base/metrics/user_metrics.h"
14 #include "base/strings/string_number_conversions.h"
15 #include "base/strings/utf_string_conversions.h"
16 #include "build/build_config.h"
17 #include "chrome/app/chrome_command_ids.h"
18 #include "chrome/app/vector_icons/vector_icons.h"
19 #include "chrome/browser/favicon/favicon_service_factory.h"
20 #include "chrome/browser/favicon/favicon_utils.h"
21 #include "chrome/browser/favicon/history_ui_favicon_request_handler_factory.h"
22 #include "chrome/browser/profiles/profile.h"
23 #include "chrome/browser/search/search.h"
24 #include "chrome/browser/sessions/session_restore.h"
25 #include "chrome/browser/sessions/tab_restore_service_factory.h"
26 #include "chrome/browser/sync/session_sync_service_factory.h"
27 #include "chrome/browser/ui/browser.h"
28 #include "chrome/browser/ui/browser_commands.h"
29 #include "chrome/browser/ui/browser_live_tab_context.h"
30 #include "chrome/browser/ui/browser_window.h"
31 #include "chrome/browser/ui/tabs/tab_strip_model.h"
32 #include "chrome/browser/ui/toolbar/app_menu_model.h"
33 #include "chrome/grit/generated_resources.h"
34 #include "components/favicon/core/history_ui_favicon_request_handler.h"
35 #include "components/favicon_base/favicon_types.h"
36 #include "components/prefs/scoped_user_pref_update.h"
37 #include "components/sessions/core/tab_restore_service.h"
38 #include "components/strings/grit/components_strings.h"
39 #include "components/sync_sessions/open_tabs_ui_delegate.h"
40 #include "components/sync_sessions/session_sync_service.h"
41 #include "components/sync_sessions/synced_session.h"
42 #include "ui/base/accelerators/accelerator.h"
43 #include "ui/base/l10n/l10n_util.h"
44 #include "ui/base/models/image_model.h"
45 #include "ui/base/resource/resource_bundle.h"
46 #include "ui/gfx/color_palette.h"
47 #include "ui/gfx/paint_vector_icon.h"
48 #include "ui/native_theme/native_theme.h"
49 #include "ui/resources/grit/ui_resources.h"
50 
51 namespace {
52 
53 // Initial comamnd ID's for navigatable (and hence executable) tab/window menu
54 // items.  The menumodel and storage structures are not 1-1:
55 // - menumodel has "Recently closed" header, "No tabs from other devices",
56 //   device section headers, separators, local and other devices' tab items, and
57 //   local window items.
58 // - |local_tab_navigation_items_| and |other_devices_tab_navigation_items_|
59 // only have navigatabale/executable tab items.
60 // - |local_window_items_| only has executable open window items.
61 // Using initial command IDs for local tab, local window and other devices' tab
62 // items makes it easier and less error-prone to manipulate the menumodel and
63 // storage structures.  These ids must be bigger than the maximum possible
64 // number of items in the menumodel, so that index of the last menu item doesn't
65 // clash with these values when menu items are retrieved via
66 // GetIndexOfCommandId().
67 // The range of all command ID's used in RecentTabsSubMenuModel, including the
68 // "Recently closed" headers, must be between
69 // |AppMenuModel::kMinRecentTabsCommandId| i.e. 1001 and 1200
70 // (|AppMenuModel::kMaxRecentTabsCommandId|) inclusively.
71 const int kFirstLocalTabCommandId = AppMenuModel::kMinRecentTabsCommandId;
72 const int kFirstLocalWindowCommandId = 1031;
73 const int kFirstOtherDevicesTabCommandId = 1051;
74 const int kMinDeviceNameCommandId = 1100;
75 const int kMaxDeviceNameCommandId = 1110;
76 
77 // The maximum number of local recently closed entries (tab or window) to be
78 // shown in the menu.
79 const int kMaxLocalEntries = 8;
80 
81 // Comparator function for use with std::sort that will sort sessions by
82 // descending modified_time (i.e., most recent first).
SortSessionsByRecency(const sync_sessions::SyncedSession * s1,const sync_sessions::SyncedSession * s2)83 bool SortSessionsByRecency(const sync_sessions::SyncedSession* s1,
84                            const sync_sessions::SyncedSession* s2) {
85   return s1->modified_time > s2->modified_time;
86 }
87 
88 // Returns true if the command id identifies a tab menu item.
IsTabModelCommandId(int command_id)89 bool IsTabModelCommandId(int command_id) {
90   return ((command_id >= kFirstLocalTabCommandId &&
91            command_id < kFirstLocalWindowCommandId) ||
92           (command_id >= kFirstOtherDevicesTabCommandId &&
93            command_id < kMinDeviceNameCommandId));
94 }
95 
96 // Returns true if the command id identifies a window menu item.
IsWindowModelCommandId(int command_id)97 bool IsWindowModelCommandId(int command_id) {
98   return command_id >= kFirstLocalWindowCommandId &&
99          command_id < kFirstOtherDevicesTabCommandId;
100 }
101 
IsDeviceNameCommandId(int command_id)102 bool IsDeviceNameCommandId(int command_id) {
103   return command_id >= kMinDeviceNameCommandId &&
104       command_id <= kMaxDeviceNameCommandId;
105 }
106 
107 // Convert |tab_vector_index| to command id of menu item, with
108 // |first_command_id| as the base command id.
TabVectorIndexToCommandId(int tab_vector_index,int first_command_id)109 int TabVectorIndexToCommandId(int tab_vector_index, int first_command_id) {
110   int command_id = tab_vector_index + first_command_id;
111   DCHECK(IsTabModelCommandId(command_id));
112   return command_id;
113 }
114 
115 // Convert |window_vector_index| to command id of menu item.
WindowVectorIndexToCommandId(int window_vector_index)116 int WindowVectorIndexToCommandId(int window_vector_index) {
117   int command_id = window_vector_index + kFirstLocalWindowCommandId;
118   DCHECK(IsWindowModelCommandId(command_id));
119   return command_id;
120 }
121 
122 // Convert |command_id| of menu item to index in |local_window_items_|.
CommandIdToWindowVectorIndex(int command_id)123 int CommandIdToWindowVectorIndex(int command_id) {
124   DCHECK(IsWindowModelCommandId(command_id));
125   return command_id - kFirstLocalWindowCommandId;
126 }
127 
CreateFavicon(const gfx::VectorIcon & icon)128 ui::ImageModel CreateFavicon(const gfx::VectorIcon& icon) {
129   return ui::ImageModel::FromVectorIcon(
130       icon, ui::NativeTheme::kColorId_MenuIconColor, 16);
131 }
132 
133 }  // namespace
134 
135 enum RecentTabAction {
136   LOCAL_SESSION_TAB = 0,
137   OTHER_DEVICE_TAB,
138   RESTORE_WINDOW,
139   SHOW_MORE,
140   LIMIT_RECENT_TAB_ACTION
141 };
142 
143 // An element in |RecentTabsSubMenuModel::local_tab_navigation_items_| or
144 // |RecentTabsSubMenuModel::other_devices_tab_navigation_items_| that stores
145 // the navigation information of a local or other devices' tab required to
146 // restore the tab.
147 struct RecentTabsSubMenuModel::TabNavigationItem {
TabNavigationItemRecentTabsSubMenuModel::TabNavigationItem148   TabNavigationItem() : tab_id(SessionID::InvalidValue()) {}
149 
TabNavigationItemRecentTabsSubMenuModel::TabNavigationItem150   TabNavigationItem(const std::string& session_tag,
151                     SessionID tab_id,
152                     const base::string16& title,
153                     const GURL& url)
154       : session_tag(session_tag), tab_id(tab_id), title(title), url(url) {}
155 
156   // For use by std::set for sorting.
operator <RecentTabsSubMenuModel::TabNavigationItem157   bool operator<(const TabNavigationItem& other) const {
158     return url < other.url;
159   }
160 
161   // Empty for local tabs, non-empty for other devices' tabs.
162   std::string session_tag;
163   SessionID tab_id;  // Might be invalid.
164   base::string16 title;
165   GURL url;
166 };
167 
RecentTabsSubMenuModel(ui::AcceleratorProvider * accelerator_provider,Browser * browser)168 RecentTabsSubMenuModel::RecentTabsSubMenuModel(
169     ui::AcceleratorProvider* accelerator_provider,
170     Browser* browser)
171     : ui::SimpleMenuModel(this),
172       browser_(browser),
173       session_sync_service_(
174           SessionSyncServiceFactory::GetInstance()->GetForProfile(
175               browser->profile())) {
176   // Invoke asynchronous call to load tabs from local last session, which does
177   // nothing if the tabs have already been loaded or they shouldn't be loaded.
178   // TabRestoreServiceChanged() will be called after the tabs are loaded.
179   sessions::TabRestoreService* service =
180       TabRestoreServiceFactory::GetForProfile(browser_->profile());
181   if (service) {
182     service->LoadTabsFromLastSession();
183     tab_restore_service_observer_.Add(service);
184   }
185 
186   if (session_sync_service_) {
187     // Using a weak pointer below for simplicity although, strictly speaking,
188     // it's not needed because the subscription itself should take care.
189     foreign_session_updated_subscription_ =
190         session_sync_service_->SubscribeToForeignSessionsChanged(
191             base::BindRepeating(
192                 &RecentTabsSubMenuModel::OnForeignSessionUpdated,
193                 weak_ptr_factory_.GetWeakPtr()));
194   }
195 
196   Build();
197 
198   if (accelerator_provider) {
199     accelerator_provider->GetAcceleratorForCommandId(
200         IDC_RESTORE_TAB, &reopen_closed_tab_accelerator_);
201     accelerator_provider->GetAcceleratorForCommandId(
202         IDC_SHOW_HISTORY, &show_history_accelerator_);
203   }
204 }
205 
~RecentTabsSubMenuModel()206 RecentTabsSubMenuModel::~RecentTabsSubMenuModel() {}
207 
IsCommandIdChecked(int command_id) const208 bool RecentTabsSubMenuModel::IsCommandIdChecked(int command_id) const {
209   return false;
210 }
211 
IsCommandIdEnabled(int command_id) const212 bool RecentTabsSubMenuModel::IsCommandIdEnabled(int command_id) const {
213   return command_id != kRecentlyClosedHeaderCommandId &&
214          command_id != kDisabledRecentlyClosedHeaderCommandId &&
215          command_id != IDC_RECENT_TABS_NO_DEVICE_TABS &&
216          !IsDeviceNameCommandId(command_id);
217 }
218 
GetAcceleratorForCommandId(int command_id,ui::Accelerator * accelerator) const219 bool RecentTabsSubMenuModel::GetAcceleratorForCommandId(
220     int command_id,
221     ui::Accelerator* accelerator) const {
222   // If there are no recently closed items, we show the accelerator beside
223   // the header, otherwise, we show it beside the first item underneath it.
224   int index_in_menu = GetIndexOfCommandId(command_id);
225   int header_index = GetIndexOfCommandId(kRecentlyClosedHeaderCommandId);
226   if ((command_id == kDisabledRecentlyClosedHeaderCommandId ||
227        (header_index != -1 && index_in_menu == header_index + 1)) &&
228       reopen_closed_tab_accelerator_.key_code() != ui::VKEY_UNKNOWN) {
229     *accelerator = reopen_closed_tab_accelerator_;
230     return true;
231   }
232 
233   if (command_id == IDC_SHOW_HISTORY) {
234     *accelerator = show_history_accelerator_;
235     return true;
236   }
237 
238   return false;
239 }
240 
ExecuteCommand(int command_id,int event_flags)241 void RecentTabsSubMenuModel::ExecuteCommand(int command_id, int event_flags) {
242   UMA_HISTOGRAM_MEDIUM_TIMES("WrenchMenu.TimeToAction",
243                              menu_opened_timer_.Elapsed());
244   if (command_id == IDC_SHOW_HISTORY) {
245     UMA_HISTOGRAM_ENUMERATION("WrenchMenu.RecentTabsSubMenu", SHOW_MORE,
246                               LIMIT_RECENT_TAB_ACTION);
247     UMA_HISTOGRAM_MEDIUM_TIMES("WrenchMenu.TimeToAction.ShowHistory",
248                                menu_opened_timer_.Elapsed());
249     LogWrenchMenuAction(MENU_ACTION_SHOW_HISTORY);
250     // We show all "other devices" on the history page.
251     chrome::ExecuteCommandWithDisposition(browser_, IDC_SHOW_HISTORY,
252         ui::DispositionFromEventFlags(event_flags));
253     return;
254   }
255 
256   DCHECK_NE(IDC_RECENT_TABS_NO_DEVICE_TABS, command_id);
257   DCHECK(!IsDeviceNameCommandId(command_id));
258 
259   WindowOpenDisposition disposition = ui::DispositionFromEventFlags(
260       event_flags, WindowOpenDisposition::NEW_FOREGROUND_TAB);
261 
262   sessions::TabRestoreService* service =
263       TabRestoreServiceFactory::GetForProfile(browser_->profile());
264   sessions::LiveTabContext* context =
265       BrowserLiveTabContext::FindContextForWebContents(
266           browser_->tab_strip_model()->GetActiveWebContents());
267   if (IsTabModelCommandId(command_id)) {
268     TabNavigationItems* tab_items = NULL;
269     int tab_items_idx = CommandIdToTabVectorIndex(command_id, &tab_items);
270     const TabNavigationItem& item = (*tab_items)[tab_items_idx];
271     DCHECK(item.tab_id.is_valid() && item.url.is_valid());
272 
273     if (item.session_tag.empty()) {  // Restore tab of local session.
274       if (service && context) {
275         base::RecordAction(
276             base::UserMetricsAction("WrenchMenu_OpenRecentTabFromLocal"));
277         UMA_HISTOGRAM_ENUMERATION("WrenchMenu.RecentTabsSubMenu",
278                                   LOCAL_SESSION_TAB, LIMIT_RECENT_TAB_ACTION);
279         service->RestoreEntryById(context, item.tab_id, disposition);
280       }
281     } else {  // Restore tab of session from other devices.
282       sync_sessions::OpenTabsUIDelegate* open_tabs = GetOpenTabsUIDelegate();
283       if (!open_tabs)
284         return;
285       const sessions::SessionTab* tab;
286       if (!open_tabs->GetForeignTab(item.session_tag, item.tab_id, &tab))
287         return;
288       if (tab->navigations.empty())
289         return;
290       base::RecordAction(
291           base::UserMetricsAction("WrenchMenu_OpenRecentTabFromDevice"));
292       UMA_HISTOGRAM_ENUMERATION("WrenchMenu.RecentTabsSubMenu",
293                                 OTHER_DEVICE_TAB, LIMIT_RECENT_TAB_ACTION);
294       SessionRestore::RestoreForeignSessionTab(
295           browser_->tab_strip_model()->GetActiveWebContents(),
296           *tab, disposition);
297     }
298   } else {
299     DCHECK(IsWindowModelCommandId(command_id));
300     if (service && context) {
301       int window_items_idx = CommandIdToWindowVectorIndex(command_id);
302       DCHECK(window_items_idx >= 0 &&
303              window_items_idx < static_cast<int>(local_window_items_.size()));
304       base::RecordAction(
305           base::UserMetricsAction("WrenchMenu_OpenRecentWindow"));
306       UMA_HISTOGRAM_ENUMERATION("WrenchMenu.RecentTabsSubMenu", RESTORE_WINDOW,
307                                 LIMIT_RECENT_TAB_ACTION);
308       service->RestoreEntryById(context, local_window_items_[window_items_idx],
309                                 disposition);
310     }
311   }
312 
313   browser_->window()->OnTabRestored(command_id);
314 
315   UMA_HISTOGRAM_MEDIUM_TIMES("WrenchMenu.TimeToAction.OpenRecentTab",
316                              menu_opened_timer_.Elapsed());
317   UMA_HISTOGRAM_ENUMERATION("WrenchMenu.MenuAction", MENU_ACTION_RECENT_TAB,
318                              LIMIT_MENU_ACTION);
319 }
320 
GetFirstRecentTabsCommandId()321 int RecentTabsSubMenuModel::GetFirstRecentTabsCommandId() {
322   return WindowVectorIndexToCommandId(0);
323 }
324 
GetLabelFontListAt(int index) const325 const gfx::FontList* RecentTabsSubMenuModel::GetLabelFontListAt(
326     int index) const {
327   int command_id = GetCommandIdAt(index);
328   if (command_id == kRecentlyClosedHeaderCommandId ||
329       IsDeviceNameCommandId(command_id)) {
330     return &ui::ResourceBundle::GetSharedInstance().GetFontList(
331         ui::ResourceBundle::BoldFont);
332   }
333   return NULL;
334 }
335 
GetMaxWidthForItemAtIndex(int item_index) const336 int RecentTabsSubMenuModel::GetMaxWidthForItemAtIndex(int item_index) const {
337   int command_id = GetCommandIdAt(item_index);
338   if (command_id == IDC_RECENT_TABS_NO_DEVICE_TABS ||
339       command_id == kRecentlyClosedHeaderCommandId ||
340       command_id == kDisabledRecentlyClosedHeaderCommandId) {
341     return -1;
342   }
343   return 320;
344 }
345 
GetURLAndTitleForItemAtIndex(int index,std::string * url,base::string16 * title)346 bool RecentTabsSubMenuModel::GetURLAndTitleForItemAtIndex(
347     int index,
348     std::string* url,
349     base::string16* title) {
350   int command_id = GetCommandIdAt(index);
351   if (IsTabModelCommandId(command_id)) {
352     TabNavigationItems* tab_items = NULL;
353     int tab_items_idx = CommandIdToTabVectorIndex(command_id, &tab_items);
354     const TabNavigationItem& item = (*tab_items)[tab_items_idx];
355     *url = item.url.possibly_invalid_spec();
356     *title = item.title;
357     return true;
358   }
359   return false;
360 }
361 
Build()362 void RecentTabsSubMenuModel::Build() {
363   // The menu contains:
364   // - History to open the full history tab.
365   // - Separator
366   // - Recently closed header, then list of local recently closed tabs/windows,
367   //   then separator
368   // - device 1 section header, then list of tabs from device, then separator
369   // - device 2 section header, then list of tabs from device, then separator
370   // - device 3 section header, then list of tabs from device, then separator
371   // |local_tab_navigation_items_| and |other_devices_tab_navigation_items_|
372   // only contain navigatable (and hence executable) tab items for local
373   // recently closed tabs and tabs from other devices respectively.
374   // |local_window_items_| contains the local recently closed windows.
375   InsertItemWithStringIdAt(0, IDC_SHOW_HISTORY, IDS_HISTORY_SHOW_HISTORY);
376   InsertSeparatorAt(1, ui::NORMAL_SEPARATOR);
377   BuildLocalEntries();
378   BuildTabsFromOtherDevices();
379 }
380 
BuildLocalEntries()381 void RecentTabsSubMenuModel::BuildLocalEntries() {
382   last_local_model_index_ = kHistorySeparatorIndex;
383 
384   // All local items use InsertItem*At() to append or insert a menu item.
385   // We're appending if building the entries for the first time i.e. invoked
386   // from Constructor(), inserting when local entries change subsequently i.e.
387   // invoked from TabRestoreServiceChanged().
388   sessions::TabRestoreService* service =
389       TabRestoreServiceFactory::GetForProfile(browser_->profile());
390 
391   if (!service || service->entries().empty()) {
392     // This is to show a disabled restore tab entry with the accelerator to
393     // teach users about this command.
394     InsertItemWithStringIdAt(++last_local_model_index_,
395                              kDisabledRecentlyClosedHeaderCommandId,
396                              IDS_RECENTLY_CLOSED);
397   } else {
398     InsertItemWithStringIdAt(++last_local_model_index_,
399                              kRecentlyClosedHeaderCommandId,
400                              IDS_RECENTLY_CLOSED);
401     SetIcon(last_local_model_index_, CreateFavicon(kTabIcon));
402 
403     int added_count = 0;
404     for (const auto& entry : service->entries()) {
405       if (added_count == kMaxLocalEntries)
406         break;
407       switch (entry->type) {
408         case sessions::TabRestoreService::TAB: {
409           auto& tab =
410               static_cast<const sessions::TabRestoreService::Tab&>(*entry);
411           const sessions::SerializedNavigationEntry& current_navigation =
412               tab.navigations.at(tab.current_navigation_index);
413           BuildLocalTabItem(entry->id, current_navigation.title(),
414                             current_navigation.virtual_url(),
415                             ++last_local_model_index_);
416           break;
417         }
418         case sessions::TabRestoreService::WINDOW: {
419           // TODO(chrisha): Make this menu entry better. When windows contain a
420           // single tab, display that tab directly in the menu. Otherwise, offer
421           // a hover over or alternative mechanism for seeing which tabs were in
422           // the window.
423           BuildLocalWindowItem(
424               entry->id,
425               static_cast<const sessions::TabRestoreService::Window&>(*entry)
426                   .tabs.size(),
427               ++last_local_model_index_);
428           break;
429         }
430       }
431       ++added_count;
432     }
433   }
434   DCHECK_GE(last_local_model_index_, 0);
435 }
436 
BuildTabsFromOtherDevices()437 void RecentTabsSubMenuModel::BuildTabsFromOtherDevices() {
438   // All other devices' items (device headers or tabs) use AddItem*() to append
439   // a menu item, because they take always place in the end of menu.
440 
441   sync_sessions::OpenTabsUIDelegate* open_tabs = GetOpenTabsUIDelegate();
442   std::vector<const sync_sessions::SyncedSession*> sessions;
443   if (!open_tabs || !open_tabs->GetAllForeignSessions(&sessions)) {
444     AddSeparator(ui::NORMAL_SEPARATOR);
445     AddItemWithStringId(IDC_RECENT_TABS_NO_DEVICE_TABS,
446                         IDS_RECENT_TABS_NO_DEVICE_TABS);
447     return;
448   }
449 
450   // Sort sessions from most recent to least recent.
451   std::sort(sessions.begin(), sessions.end(), SortSessionsByRecency);
452 
453   const size_t kMaxSessionsToShow = 3;
454   size_t num_sessions_added = 0;
455   for (size_t i = 0;
456        i < sessions.size() && num_sessions_added < kMaxSessionsToShow; ++i) {
457     const sync_sessions::SyncedSession* session = sessions[i];
458     const std::string& session_tag = session->session_tag;
459 
460     // Collect tabs from all windows of the session, ordered by recency.
461     std::vector<const sessions::SessionTab*> tabs_in_session;
462     if (!open_tabs->GetForeignSessionTabs(session_tag, &tabs_in_session) ||
463         tabs_in_session.empty())
464       continue;
465 
466     // Add the header for the device session.
467     DCHECK(!session->session_name.empty());
468     AddSeparator(ui::NORMAL_SEPARATOR);
469     int command_id = kMinDeviceNameCommandId + i;
470     DCHECK_LE(command_id, kMaxDeviceNameCommandId);
471     AddItem(command_id, base::UTF8ToUTF16(session->session_name));
472     AddDeviceFavicon(GetItemCount() - 1, session->device_type);
473 
474     // Build tab menu items from sorted session tabs.
475     const size_t kMaxTabsPerSessionToShow = 4;
476     for (size_t k = 0;
477          k < std::min(tabs_in_session.size(), kMaxTabsPerSessionToShow);
478          ++k) {
479       BuildOtherDevicesTabItem(session_tag, *tabs_in_session[k]);
480     }  // for all tabs in one session
481 
482     ++num_sessions_added;
483   }  // for all sessions
484 
485   // We are not supposed to get here unless at least some items were added.
486   DCHECK_GT(GetItemCount(), 0);
487 }
488 
BuildLocalTabItem(SessionID session_id,const base::string16 & title,const GURL & url,int curr_model_index)489 void RecentTabsSubMenuModel::BuildLocalTabItem(SessionID session_id,
490                                                const base::string16& title,
491                                                const GURL& url,
492                                                int curr_model_index) {
493   TabNavigationItem item(std::string(), session_id, title, url);
494   int command_id = TabVectorIndexToCommandId(
495       local_tab_navigation_items_.size(), kFirstLocalTabCommandId);
496   // See comments in BuildLocalEntries() about usage of InsertItem*At().
497   // There may be no tab title, in which case, use the url as tab title.
498   InsertItemAt(curr_model_index, command_id,
499                title.empty() ? base::UTF8ToUTF16(item.url.spec()) : title);
500   AddTabFavicon(command_id, item.url);
501   local_tab_navigation_items_.push_back(item);
502 }
503 
BuildLocalWindowItem(SessionID window_id,int num_tabs,int curr_model_index)504 void RecentTabsSubMenuModel::BuildLocalWindowItem(SessionID window_id,
505                                                   int num_tabs,
506                                                   int curr_model_index) {
507   int command_id = WindowVectorIndexToCommandId(local_window_items_.size());
508   // See comments in BuildLocalEntries() about usage of InsertItem*At().
509   InsertItemAt(curr_model_index, command_id, l10n_util::GetPluralStringFUTF16(
510       IDS_RECENTLY_CLOSED_WINDOW, num_tabs));
511   SetIcon(curr_model_index, CreateFavicon(kTabIcon));
512   local_window_items_.push_back(window_id);
513 }
514 
BuildOtherDevicesTabItem(const std::string & session_tag,const sessions::SessionTab & tab)515 void RecentTabsSubMenuModel::BuildOtherDevicesTabItem(
516     const std::string& session_tag,
517     const sessions::SessionTab& tab) {
518   const sessions::SerializedNavigationEntry& current_navigation =
519       tab.navigations.at(tab.normalized_navigation_index());
520   TabNavigationItem item(session_tag, tab.tab_id, current_navigation.title(),
521                          current_navigation.virtual_url());
522   int command_id = TabVectorIndexToCommandId(
523       other_devices_tab_navigation_items_.size(),
524       kFirstOtherDevicesTabCommandId);
525   // See comments in BuildTabsFromOtherDevices() about usage of AddItem*().
526   // There may be no tab title, in which case, use the url as tab title.
527   AddItem(command_id,
528           current_navigation.title().empty() ?
529               base::UTF8ToUTF16(item.url.spec()) : current_navigation.title());
530   AddTabFavicon(command_id, item.url);
531   other_devices_tab_navigation_items_.push_back(item);
532 }
533 
AddDeviceFavicon(int index_in_menu,sync_pb::SyncEnums::DeviceType device_type)534 void RecentTabsSubMenuModel::AddDeviceFavicon(
535     int index_in_menu,
536     sync_pb::SyncEnums::DeviceType device_type) {
537   const gfx::VectorIcon* favicon = nullptr;
538   switch (device_type) {
539     case sync_pb::SyncEnums::TYPE_PHONE:
540       favicon = &kSmartphoneIcon;
541       break;
542 
543     case sync_pb::SyncEnums::TYPE_TABLET:
544       favicon = &kTabletIcon;
545       break;
546 
547     case sync_pb::SyncEnums::TYPE_CROS:
548     case sync_pb::SyncEnums::TYPE_WIN:
549     case sync_pb::SyncEnums::TYPE_MAC:
550     case sync_pb::SyncEnums::TYPE_LINUX:
551     case sync_pb::SyncEnums::TYPE_OTHER:
552     case sync_pb::SyncEnums::TYPE_UNSET:
553       favicon = &kLaptopIcon;
554       break;
555   }
556 
557   SetIcon(index_in_menu, CreateFavicon(*favicon));
558 }
559 
AddTabFavicon(int command_id,const GURL & url)560 void RecentTabsSubMenuModel::AddTabFavicon(int command_id, const GURL& url) {
561   int index_in_menu = GetIndexOfCommandId(command_id);
562 
563   // Set default icon first.
564   SetIcon(index_in_menu,
565           ui::ImageModel::FromImage(favicon::GetDefaultFavicon()));
566 
567   bool is_local_tab = command_id < kFirstOtherDevicesTabCommandId;
568   if (is_local_tab) {
569     // Request only from local storage to avoid leaking user data.
570     favicon::FaviconService* favicon_service =
571         FaviconServiceFactory::GetForProfile(
572             browser_->profile(), ServiceAccessType::EXPLICIT_ACCESS);
573     // Can be null for tests.
574     if (!favicon_service)
575       return;
576     favicon_service->GetFaviconImageForPageURL(
577         url,
578         base::BindOnce(&RecentTabsSubMenuModel::OnFaviconDataAvailable,
579                        weak_ptr_factory_.GetWeakPtr(), command_id),
580         &local_tab_cancelable_task_tracker_);
581   } else {
582     favicon::HistoryUiFaviconRequestHandler*
583         history_ui_favicon_request_handler =
584             HistoryUiFaviconRequestHandlerFactory::GetForBrowserContext(
585                 browser_->profile());
586     // Can be null for tests.
587     if (!history_ui_favicon_request_handler)
588       return;
589     history_ui_favicon_request_handler->GetFaviconImageForPageURL(
590         url,
591         base::BindOnce(&RecentTabsSubMenuModel::OnFaviconDataAvailable,
592                        weak_ptr_factory_for_other_devices_tab_.GetWeakPtr(),
593                        command_id),
594 
595         favicon::HistoryUiFaviconRequestOrigin::kRecentTabs);
596   }
597 }
598 
OnFaviconDataAvailable(int command_id,const favicon_base::FaviconImageResult & image_result)599 void RecentTabsSubMenuModel::OnFaviconDataAvailable(
600     int command_id,
601     const favicon_base::FaviconImageResult& image_result) {
602   if (image_result.image.IsEmpty()) {
603     // Default icon has already been set.
604     return;
605   }
606   int index_in_menu = GetIndexOfCommandId(command_id);
607   DCHECK_GT(index_in_menu, -1);
608   SetIcon(index_in_menu, ui::ImageModel::FromImage(image_result.image));
609   ui::MenuModelDelegate* delegate = menu_model_delegate();
610   if (delegate)
611     delegate->OnIconChanged(index_in_menu);
612   return;
613 }
614 
CommandIdToTabVectorIndex(int command_id,TabNavigationItems ** tab_items)615 int RecentTabsSubMenuModel::CommandIdToTabVectorIndex(
616     int command_id,
617     TabNavigationItems** tab_items) {
618   DCHECK(IsTabModelCommandId(command_id));
619   if (command_id >= kFirstOtherDevicesTabCommandId) {
620     *tab_items = &other_devices_tab_navigation_items_;
621     return command_id - kFirstOtherDevicesTabCommandId;
622   }
623   *tab_items = &local_tab_navigation_items_;
624   return command_id - kFirstLocalTabCommandId;
625 }
626 
ClearLocalEntries()627 void RecentTabsSubMenuModel::ClearLocalEntries() {
628   // Remove local items (recently closed tabs and windows) from menumodel.
629   while (last_local_model_index_ > kHistorySeparatorIndex)
630     RemoveItemAt(last_local_model_index_--);
631 
632   // Cancel asynchronous FaviconService::GetFaviconImageForPageURL() tasks of
633   // all local tabs.
634   local_tab_cancelable_task_tracker_.TryCancelAll();
635 
636   // Remove all local tab navigation items.
637   local_tab_navigation_items_.clear();
638 
639   // Remove all local window items.
640   local_window_items_.clear();
641 }
642 
ClearTabsFromOtherDevices()643 void RecentTabsSubMenuModel::ClearTabsFromOtherDevices() {
644   DCHECK_GE(last_local_model_index_, 0);
645   int count = GetItemCount();
646   for (int index = count - 1; index > last_local_model_index_; --index)
647     RemoveItemAt(index);
648 
649   weak_ptr_factory_for_other_devices_tab_.InvalidateWeakPtrs();
650 
651   other_devices_tab_navigation_items_.clear();
652 }
653 
654 sync_sessions::OpenTabsUIDelegate*
GetOpenTabsUIDelegate()655 RecentTabsSubMenuModel::GetOpenTabsUIDelegate() {
656   DCHECK(session_sync_service_);
657   return session_sync_service_->GetOpenTabsUIDelegate();
658 }
659 
TabRestoreServiceChanged(sessions::TabRestoreService * service)660 void RecentTabsSubMenuModel::TabRestoreServiceChanged(
661     sessions::TabRestoreService* service) {
662   ClearLocalEntries();
663 
664   BuildLocalEntries();
665 
666   ui::MenuModelDelegate* delegate = menu_model_delegate();
667   if (delegate)
668     delegate->OnMenuStructureChanged();
669 }
670 
TabRestoreServiceDestroyed(sessions::TabRestoreService * service)671 void RecentTabsSubMenuModel::TabRestoreServiceDestroyed(
672     sessions::TabRestoreService* service) {
673   TabRestoreServiceChanged(service);
674 }
675 
OnForeignSessionUpdated()676 void RecentTabsSubMenuModel::OnForeignSessionUpdated() {
677   ClearTabsFromOtherDevices();
678 
679   BuildTabsFromOtherDevices();
680 
681   ui::MenuModelDelegate* delegate = menu_model_delegate();
682   if (delegate)
683     delegate->OnMenuStructureChanged();
684 }
685