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