Chromium Code Reviews| OLD | NEW |
|---|---|
| (Empty) | |
| 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/string_number_conversions.h" | |
| 9 #include "base/utf_string_conversions.h" | |
| 10 #include "chrome/app/chrome_command_ids.h" | |
| 11 #include "chrome/browser/favicon/favicon_service_factory.h" | |
| 12 #include "chrome/browser/prefs/scoped_user_pref_update.h" | |
| 13 #include "chrome/browser/profiles/profile.h" | |
| 14 #include "chrome/browser/sessions/session_restore.h" | |
| 15 #include "chrome/browser/sessions/tab_restore_service_delegate.h" | |
| 16 #include "chrome/browser/sessions/tab_restore_service_factory.h" | |
| 17 #include "chrome/browser/sync/glue/session_model_associator.h" | |
| 18 #include "chrome/browser/sync/glue/synced_session.h" | |
| 19 #include "chrome/browser/sync/profile_sync_service.h" | |
| 20 #include "chrome/browser/sync/profile_sync_service_factory.h" | |
| 21 #include "chrome/browser/ui/browser.h" | |
| 22 #include "chrome/browser/ui/browser_commands.h" | |
| 23 #include "chrome/browser/ui/browser_tabstrip.h" | |
| 24 #include "chrome/common/pref_names.h" | |
| 25 #include "chrome/common/time_format.h" | |
| 26 #include "chrome/common/url_constants.h" | |
| 27 #include "grit/generated_resources.h" | |
| 28 #include "grit/ui_resources.h" | |
| 29 #include "ui/base/accelerators/accelerator.h" | |
| 30 #include "ui/base/l10n/l10n_util.h" | |
| 31 #include "ui/base/resource/resource_bundle.h" | |
| 32 #include "ui/gfx/favicon_size.h" | |
| 33 | |
| 34 namespace { | |
| 35 | |
| 36 // Comparator function for use with std::sort that will sort sessions by | |
| 37 // descending modified_time (i.e., most recent first). | |
| 38 bool SortSessionsByRecency(const browser_sync::SyncedSession* s1, | |
| 39 const browser_sync::SyncedSession* s2) { | |
| 40 return s1->modified_time > s2->modified_time; | |
| 41 } | |
| 42 | |
| 43 // Helper function that returns the largest tab timestamp for the window. | |
| 44 base::Time GetMostRecentTabTimestamp(const SessionWindow* window) { | |
| 45 base::Time max_timestamp; | |
| 46 for (size_t i = 0, num_tabs = window->tabs.size(); i < num_tabs; ++i) { | |
| 47 SessionTab* tab = window->tabs[i]; | |
| 48 if (tab->timestamp > max_timestamp) | |
| 49 max_timestamp = tab->timestamp; | |
| 50 } | |
| 51 return max_timestamp; | |
| 52 } | |
| 53 | |
| 54 // Comparator function to sort windows by last-modified time. Windows in a | |
| 55 // session share the same timestamp, so we need to use tab timestamps instead. | |
| 56 // instead. | |
| 57 bool SortWindowsByRecency(const SessionWindow* w1, const SessionWindow* w2) { | |
| 58 return GetMostRecentTabTimestamp(w1) > GetMostRecentTabTimestamp(w2); | |
| 59 } | |
| 60 | |
| 61 } // namepace | |
| 62 | |
| 63 struct RecentTabsSubMenuModel::NavigationItem { | |
|
sky
2012/11/07 00:10:47
Add description.
kuan
2012/11/07 02:29:46
Done.
| |
| 64 NavigationItem() : tab_id(-1) { | |
| 65 } | |
| 66 NavigationItem(const std::string& session_tag, | |
|
sky
2012/11/07 00:10:47
nit: newline between 65/66 and 70/71.
kuan
2012/11/07 02:29:46
Done.
| |
| 67 const SessionID::id_type& tab_id, | |
| 68 const GURL& url) | |
| 69 : session_tag(session_tag), tab_id(tab_id), url(url) { | |
|
sky
2012/11/07 00:10:47
since you wrapped the constructor wrap each param
kuan
2012/11/07 02:29:46
Done.
| |
| 70 } | |
| 71 // For use by std::set for sorting. | |
| 72 bool operator<(const NavigationItem& other) const { | |
| 73 return url < other.url; | |
| 74 } | |
| 75 | |
| 76 std::string session_tag; | |
| 77 SessionID::id_type tab_id; | |
| 78 GURL url; | |
| 79 }; | |
| 80 | |
| 81 RecentTabsSubMenuModel::RecentTabsSubMenuModel( | |
| 82 ui::AcceleratorProvider* accelerator_provider, | |
| 83 Browser* browser) | |
| 84 : ALLOW_THIS_IN_INITIALIZER_LIST(ui::SimpleMenuModel(this)), | |
| 85 accelerator_provider_(accelerator_provider), | |
| 86 browser_(browser), | |
| 87 default_favicon_(NULL), | |
| 88 ALLOW_THIS_IN_INITIALIZER_LIST(weak_ptr_factory_(this)) { | |
| 89 Build(); | |
| 90 } | |
| 91 | |
| 92 RecentTabsSubMenuModel::~RecentTabsSubMenuModel() { | |
| 93 weak_ptr_factory_.InvalidateWeakPtrs(); | |
| 94 } | |
| 95 | |
| 96 bool RecentTabsSubMenuModel::IsCommandIdChecked(int command_id) const { | |
| 97 return false; | |
| 98 } | |
| 99 | |
| 100 bool RecentTabsSubMenuModel::IsCommandIdEnabled(int command_id) const { | |
| 101 return command_id == IDC_RESTORE_TAB ? | |
| 102 chrome::IsCommandEnabled(browser_, command_id) : !model_.empty(); | |
| 103 } | |
| 104 | |
| 105 bool RecentTabsSubMenuModel::GetAcceleratorForCommandId( | |
| 106 int command_id, ui::Accelerator* accelerator) { | |
| 107 return command_id == IDC_RESTORE_TAB ? | |
| 108 accelerator_provider_->GetAcceleratorForCommandId(command_id, | |
| 109 accelerator) : | |
| 110 false; | |
| 111 } | |
| 112 | |
| 113 void RecentTabsSubMenuModel::ExecuteCommand(int command_id) { | |
| 114 ExecuteCommand(command_id, 0); | |
| 115 } | |
| 116 | |
| 117 void RecentTabsSubMenuModel::ExecuteCommand(int command_id, int event_flags) { | |
| 118 if (command_id == IDC_RESTORE_TAB) { | |
| 119 chrome::ExecuteCommandWithDisposition(browser_, command_id, | |
| 120 chrome::DispositionFromEventFlags(event_flags)); | |
| 121 return; | |
| 122 } | |
| 123 | |
| 124 // An empty |model_| means there's no executable menu items. | |
| 125 // When building menu items, |command_id_| was set to zero for device section | |
| 126 // header, which is non-executable. | |
| 127 if (model_.empty() || command_id == 0) | |
| 128 return; | |
| 129 | |
| 130 DCHECK_LT(command_id, static_cast<int>(model_.size())); | |
| 131 const NavigationItem& item = model_[command_id]; | |
| 132 DCHECK(item.tab_id > -1 && item.url.is_valid()); | |
| 133 | |
| 134 if (item.session_tag.empty()) { // Restore tab of local session. | |
| 135 TabRestoreService* service = | |
| 136 TabRestoreServiceFactory::GetForProfile(browser_->profile()); | |
| 137 if (!service) | |
| 138 return; | |
| 139 TabRestoreServiceDelegate* delegate = | |
| 140 TabRestoreServiceDelegate::FindDelegateForWebContents( | |
| 141 chrome::GetActiveWebContents(browser_)); | |
| 142 if (!delegate) | |
| 143 return; | |
| 144 service->RestoreEntryById(delegate, item.tab_id, | |
| 145 chrome::DispositionFromEventFlags(event_flags)); | |
| 146 } else { // Restore tab of foreign session. | |
| 147 browser_sync::SessionModelAssociator* associator = GetModelAssociator(); | |
| 148 if (!associator) | |
| 149 return; | |
| 150 const SessionTab* tab; | |
| 151 if (!associator->GetForeignTab(item.session_tag, item.tab_id, &tab)) | |
| 152 return; | |
| 153 if (tab->navigations.empty()) | |
| 154 return; | |
| 155 SessionRestore::RestoreForeignSessionTab( | |
| 156 chrome::GetActiveWebContents(browser_), *tab, | |
| 157 chrome::DispositionFromEventFlags(event_flags)); | |
| 158 } | |
| 159 } | |
| 160 | |
| 161 void RecentTabsSubMenuModel::Build() { | |
| 162 // For now when only last opened tab can be restored (instead of multiple), | |
| 163 // |model_| only contains items for other devices: | |
| 164 // - section header for device name (command_id = 0) | |
| 165 // - tabs from other devices (command_id = index into |model_|, which will | |
| 166 // never be 0 since there would be always be a section header) | |
| 167 // - separator that separates tabs of each device (comand id = -1) | |
| 168 // Later, if multiple tabs in local session can be restored, |model_| will | |
| 169 // contain items in the entire menu, and first separator for other devices | |
| 170 // is only needed if there's at least 1 local tab to restore. | |
| 171 BuildRecentlyClosed(); | |
| 172 BuildDevices(); | |
| 173 if (model_.empty()) | |
| 174 AddItemWithStringId(0, IDS_RECENT_TABS_NO_DEVICE_TABS); | |
| 175 } | |
| 176 | |
| 177 void RecentTabsSubMenuModel::BuildRecentlyClosed() { | |
| 178 #if !defined(SHOW_MULTIPLE_RECENTLY_CLOSED_TABS) | |
|
sky
2012/11/07 00:10:47
Why the ifdef?
kuan
2012/11/07 02:29:46
i explained it in the cl description.
david wrote
sky
2012/11/07 14:40:12
The ifdefs make the code a lot harder to follow. A
kuan
2012/11/07 16:48:52
Done.
"Reopen closed tab" is disabled if there's
| |
| 179 AddItemWithStringId(IDC_RESTORE_TAB, IDS_RESTORE_TAB); | |
| 180 AddSeparator(ui::NORMAL_SEPARATOR); | |
| 181 #else | |
| 182 TabRestoreService* service = | |
| 183 TabRestoreServiceFactory::GetForProfile(browser_->profile()); | |
|
sky
2012/11/07 00:10:47
What happens if the contents of the tabrestoreserv
kuan
2012/11/07 02:29:46
since this code is not being used for now, i've ad
sky
2012/11/07 14:40:12
Yet more of a reason to nuke this code.
kuan
2012/11/07 16:48:52
Done.
| |
| 184 | |
| 185 const int kRecentlyClosedCount = 10; | |
| 186 const TabRestoreService::Entries& entries = service->entries(); | |
| 187 int added_count = 0; | |
| 188 std::set<NavigationItem> seen_tabs; | |
|
sky
2012/11/07 00:10:47
Is there a particular reason for trying to avoid c
kuan
2012/11/07 02:29:46
i believe david didn't want to repeat tabs with th
| |
| 189 for (TabRestoreService::Entries::const_iterator it = entries.begin(); | |
| 190 it != entries.end() && added_count < kRecentlyClosedCount; ++it) { | |
| 191 TabRestoreService::Entry* entry = *it; | |
| 192 | |
| 193 if (entry->type == TabRestoreService::WINDOW) { | |
| 194 TabRestoreService::Window* window_entry = | |
| 195 static_cast<TabRestoreService::Window*>(entry); | |
| 196 std::vector<TabRestoreService::Tab>& tabs = window_entry->tabs; | |
| 197 if (tabs.empty()) | |
| 198 continue; | |
| 199 | |
| 200 // Loop over the window's tabs and add them to the menu. | |
| 201 std::vector<TabRestoreService::Tab>::const_iterator it; | |
|
sky
2012/11/07 00:10:47
Use a different name thant that of it defined on 1
kuan
2012/11/07 02:29:46
Done.
| |
| 202 for (it = tabs.begin(); it != tabs.end(); ++it) { | |
| 203 TabRestoreService::Tab tab = *it; | |
| 204 if (BuildRecentlyClosedTabItem(tab, &seen_tabs)) | |
|
sky
2012/11/07 00:10:47
Why are you extracting the tabs from window entrie
kuan
2012/11/07 02:29:46
i assume u're talking about RecentClosedTabsHandle
sky
2012/11/07 17:35:02
I'm referring to why do you look through all the t
kuan
2012/11/07 17:38:23
this code is nuked in patch set 3.
| |
| 205 ++added_count; | |
| 206 } | |
| 207 } else if (entry->type == TabRestoreService::TAB) { | |
| 208 TabRestoreService::Tab* tab = static_cast<TabRestoreService::Tab*>(entry); | |
| 209 if (!tab) | |
|
sky
2012/11/07 00:10:47
If this were NULL you would have crashed on 193.
kuan
2012/11/07 02:29:46
hm.. i thot that even if entry is !NULL, if it's t
sky
2012/11/07 14:40:12
No, you would basically be casting to the wrong th
kuan
2012/11/07 16:48:52
Done. code is nuked.
| |
| 210 continue; | |
| 211 | |
| 212 if (BuildRecentlyClosedTabItem(*tab, &seen_tabs)) | |
| 213 ++added_count; | |
| 214 } | |
| 215 } | |
| 216 #endif // defined(SHOW_MULTIPLE_RECENTLY_CLOSED_TABS) | |
| 217 } | |
| 218 | |
| 219 bool RecentTabsSubMenuModel::BuildRecentlyClosedTabItem( | |
| 220 const TabRestoreService::Tab& entry, | |
| 221 std::set<NavigationItem>* seen_tabs) { | |
| 222 #if !defined(SHOW_MULTIPLE_RECENTLY_CLOSED_TABS) | |
| 223 return false; | |
| 224 #else | |
| 225 const TabNavigation& current_navigation = | |
| 226 entry.navigations[entry.current_navigation_index]; | |
| 227 NavigationItem item(std::string(), entry.id, | |
| 228 current_navigation.virtual_url()); | |
| 229 if (seen_tabs->count(item)) | |
| 230 return false; | |
|
sky
2012/11/07 00:10:47
nit: indentation is off.
kuan
2012/11/07 02:29:46
Done.
| |
| 231 BuildTabToRestore(item, current_navigation.title()); | |
| 232 seen_tabs->insert(item); | |
| 233 return true; | |
| 234 #endif // defined(SHOW_MULTIPLE_RECENTLY_CLOSED_TABS) | |
| 235 } | |
| 236 | |
| 237 bool RecentTabsSubMenuModel::BuildForeignTabItem( | |
| 238 const std::string& session_tag, | |
| 239 const SessionTab& tab, | |
| 240 const std::string& session_name, | |
| 241 bool need_separator) { | |
| 242 if (tab.navigations.empty()) | |
| 243 return false; | |
| 244 int selected_index = std::min(tab.current_navigation_index, | |
| 245 static_cast<int>(tab.navigations.size() - 1)); | |
| 246 const TabNavigation& current_navigation = tab.navigations.at(selected_index); | |
| 247 const GURL& tab_url = current_navigation.virtual_url(); | |
| 248 if (tab_url == GURL(chrome::kChromeUINewTabURL)) | |
| 249 return false; | |
| 250 if (need_separator) { | |
| 251 AddSeparator(ui::NORMAL_SEPARATOR); | |
| 252 model_.push_back(NavigationItem()); | |
| 253 } | |
| 254 if (!session_name.empty()) { | |
| 255 AddItem(0, UTF8ToUTF16(session_name)); | |
| 256 model_.push_back(NavigationItem()); | |
| 257 } | |
| 258 NavigationItem item(session_tag, tab.tab_id.id(), | |
| 259 current_navigation.virtual_url()); | |
| 260 BuildTabToRestore(item, current_navigation.title()); | |
| 261 return true; | |
| 262 } | |
| 263 | |
| 264 void RecentTabsSubMenuModel::BuildTabToRestore(const NavigationItem& item, | |
| 265 const string16& tab_title) { | |
| 266 AddItem(model_.size(), tab_title); | |
| 267 AddFavicon(model_.size(), item.url); | |
| 268 model_.push_back(item); | |
| 269 } | |
| 270 | |
| 271 void RecentTabsSubMenuModel::AddFavicon(int model_index, const GURL& url) { | |
| 272 if (!default_favicon_) { | |
| 273 ResourceBundle& rb = ResourceBundle::GetSharedInstance(); | |
| 274 default_favicon_ = &rb.GetNativeImageNamed(IDR_DEFAULT_FAVICON); | |
| 275 } | |
| 276 // Set default icon first. | |
| 277 SetIcon(GetIndexInMenu(model_index), *default_favicon_); | |
| 278 // Start request to fetch actual icon if possible. | |
| 279 FaviconService* favicon_service = FaviconServiceFactory::GetForProfile( | |
| 280 browser_->profile(), Profile::EXPLICIT_ACCESS); | |
| 281 if (!favicon_service) | |
| 282 return; | |
| 283 FaviconService::Handle handle = favicon_service->GetFaviconImageForURL( | |
| 284 FaviconService::FaviconForURLParams(browser_->profile(), url, | |
|
sky
2012/11/07 00:10:47
nit: indentation is off here.
kuan
2012/11/07 02:29:46
Done.
| |
| 285 history::FAVICON, gfx::kFaviconSize, &favicon_consumer_), | |
| 286 base::Bind(&RecentTabsSubMenuModel::OnFaviconDataAvailable, | |
| 287 weak_ptr_factory_.GetWeakPtr())); | |
| 288 favicon_consumer_.SetClientData(favicon_service, handle, model_index); | |
| 289 } | |
| 290 | |
| 291 void RecentTabsSubMenuModel::OnFaviconDataAvailable( | |
| 292 FaviconService::Handle handle, | |
| 293 const history::FaviconImageResult& image_result) { | |
| 294 if (image_result.image.IsEmpty()) | |
| 295 return; | |
| 296 DCHECK(!model_.empty()); | |
| 297 int model_index = favicon_consumer_.GetClientData( | |
| 298 FaviconServiceFactory::GetForProfile(browser_->profile(), | |
| 299 Profile::EXPLICIT_ACCESS), | |
| 300 handle); | |
| 301 DCHECK(model_index > 0 && model_index < static_cast<int>(model_.size())); | |
| 302 DCHECK(model_[model_index].tab_id > -1 && model_[model_index].url.is_valid()); | |
| 303 int index_in_menu = GetIndexInMenu(model_index); | |
| 304 SetIcon(index_in_menu, image_result.image); | |
| 305 if (menu_model_delegate()) | |
| 306 menu_model_delegate()->OnIconChanged(index_in_menu); | |
| 307 } | |
| 308 | |
| 309 void RecentTabsSubMenuModel::BuildDevices() { | |
| 310 browser_sync::SessionModelAssociator* associator = GetModelAssociator(); | |
| 311 if (!associator) | |
| 312 return; | |
| 313 | |
| 314 std::vector<const browser_sync::SyncedSession*> sessions; | |
| 315 if (!associator->GetAllForeignSessions(&sessions)) | |
| 316 return; | |
| 317 | |
| 318 // Sort sessions from most recent to least recent. | |
| 319 std::sort(sessions.begin(), sessions.end(), SortSessionsByRecency); | |
| 320 | |
| 321 const size_t kMaxSessionsToShow = 10; | |
| 322 bool need_separator = false; | |
| 323 for (size_t i = 0; i < sessions.size() && i < kMaxSessionsToShow; ++i) { | |
| 324 const browser_sync::SyncedSession* session = sessions[i]; | |
| 325 const std::string& session_tag = session->session_tag; | |
| 326 | |
| 327 // Get windows of session. | |
| 328 std::vector<const SessionWindow*> windows; | |
| 329 if (!associator->GetForeignSession(session_tag, &windows) || | |
| 330 windows.empty()) { | |
| 331 continue; | |
| 332 } | |
| 333 // Sort windows from most recent to least recent, based on tabs in windows. | |
| 334 std::sort(windows.begin(), windows.end(), SortWindowsByRecency); | |
| 335 | |
| 336 const int kMaxTabsPerSessionToShow = 18; | |
| 337 int num_tabs_in_session = 0; | |
| 338 for (size_t j = 0; | |
| 339 j < windows.size() && num_tabs_in_session < kMaxTabsPerSessionToShow; | |
| 340 ++j) { | |
| 341 const SessionWindow* window = windows[j]; | |
| 342 // Get tabs of window. | |
| 343 for (size_t k = 0; | |
| 344 k < window->tabs.size() && | |
| 345 num_tabs_in_session < kMaxTabsPerSessionToShow; | |
| 346 ++k) { | |
| 347 if (BuildForeignTabItem(session_tag, *window->tabs[k], | |
| 348 // Only need |session_name| for the first tab of the session. | |
| 349 !num_tabs_in_session ? session->session_name : std::string(), | |
| 350 need_separator)) { | |
| 351 need_separator = false; | |
| 352 ++num_tabs_in_session; | |
| 353 } | |
| 354 } // for all tabs of one window | |
| 355 } // for all windows of one session | |
| 356 | |
| 357 if (num_tabs_in_session > 0) | |
| 358 need_separator = true; | |
| 359 } // for all sessions | |
| 360 } | |
| 361 | |
| 362 int RecentTabsSubMenuModel::GetIndexInMenu(int model_index) const { | |
| 363 // For now when only last closed tab can be restored, |model_| does not | |
| 364 // include IDC_RESTORE_TAB and the separator after. | |
| 365 const int kNumItemsNotInModel = 2; | |
| 366 // |model_index| indexes into |model_|, which doesn't include | |
| 367 // kNumItemsNotInModel, so add that to get index in menu. | |
| 368 return model_index + kNumItemsNotInModel; | |
| 369 } | |
| 370 | |
| 371 browser_sync::SessionModelAssociator* | |
| 372 RecentTabsSubMenuModel::GetModelAssociator() const { | |
| 373 ProfileSyncService* service = ProfileSyncServiceFactory::GetInstance()-> | |
| 374 GetForProfile(browser_->profile()); | |
| 375 // Only return the associator if it exists and it is done syncing sessions. | |
| 376 return service && service->ShouldPushChanges() ? | |
| 377 service->GetSessionModelAssociator() : NULL; | |
| 378 } | |
| OLD | NEW |