| OLD | NEW |
| (Empty) |
| 1 // Copyright (c) 2010 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 "build/build_config.h" | |
| 6 | |
| 7 #include "chrome/browser/back_forward_menu_model.h" | |
| 8 | |
| 9 #include "app/l10n_util.h" | |
| 10 #include "app/text_elider.h" | |
| 11 #include "app/resource_bundle.h" | |
| 12 #include "base/string_number_conversions.h" | |
| 13 #include "chrome/browser/metrics/user_metrics.h" | |
| 14 #include "chrome/browser/tab_contents/navigation_controller.h" | |
| 15 #include "chrome/browser/tab_contents/navigation_entry.h" | |
| 16 #include "chrome/browser/tab_contents/tab_contents.h" | |
| 17 #include "chrome/browser/ui/browser.h" | |
| 18 #include "chrome/common/url_constants.h" | |
| 19 #include "grit/generated_resources.h" | |
| 20 #include "grit/theme_resources.h" | |
| 21 #include "net/base/registry_controlled_domain.h" | |
| 22 | |
| 23 const int BackForwardMenuModel::kMaxHistoryItems = 12; | |
| 24 const int BackForwardMenuModel::kMaxChapterStops = 5; | |
| 25 static const int kMaxWidth = 700; | |
| 26 | |
| 27 BackForwardMenuModel::BackForwardMenuModel(Browser* browser, | |
| 28 ModelType model_type) | |
| 29 : browser_(browser), | |
| 30 test_tab_contents_(NULL), | |
| 31 model_type_(model_type) { | |
| 32 } | |
| 33 | |
| 34 bool BackForwardMenuModel::HasIcons() const { | |
| 35 return true; | |
| 36 } | |
| 37 | |
| 38 int BackForwardMenuModel::GetItemCount() const { | |
| 39 int items = GetHistoryItemCount(); | |
| 40 | |
| 41 if (items > 0) { | |
| 42 int chapter_stops = 0; | |
| 43 | |
| 44 // Next, we count ChapterStops, if any. | |
| 45 if (items == kMaxHistoryItems) | |
| 46 chapter_stops = GetChapterStopCount(items); | |
| 47 | |
| 48 if (chapter_stops) | |
| 49 items += chapter_stops + 1; // Chapter stops also need a separator. | |
| 50 | |
| 51 // If the menu is not empty, add two positions in the end | |
| 52 // for a separator and a "Show Full History" item. | |
| 53 items += 2; | |
| 54 } | |
| 55 | |
| 56 return items; | |
| 57 } | |
| 58 | |
| 59 menus::MenuModel::ItemType BackForwardMenuModel::GetTypeAt(int index) const { | |
| 60 return IsSeparator(index) ? TYPE_SEPARATOR : TYPE_COMMAND; | |
| 61 } | |
| 62 | |
| 63 int BackForwardMenuModel::GetCommandIdAt(int index) const { | |
| 64 return index; | |
| 65 } | |
| 66 | |
| 67 string16 BackForwardMenuModel::GetLabelAt(int index) const { | |
| 68 // Return label "Show Full History" for the last item of the menu. | |
| 69 if (index == GetItemCount() - 1) | |
| 70 return l10n_util::GetStringUTF16(IDS_SHOWFULLHISTORY_LINK); | |
| 71 | |
| 72 // Return an empty string for a separator. | |
| 73 if (IsSeparator(index)) | |
| 74 return string16(); | |
| 75 | |
| 76 // Return the entry title, escaping any '&' characters and eliding it if it's | |
| 77 // super long. | |
| 78 NavigationEntry* entry = GetNavigationEntry(index); | |
| 79 string16 menu_text(entry->GetTitleForDisplay( | |
| 80 &GetTabContents()->controller())); | |
| 81 menu_text = gfx::ElideText(menu_text, gfx::Font(), kMaxWidth, false); | |
| 82 | |
| 83 for (size_t i = menu_text.find('&'); i != string16::npos; | |
| 84 i = menu_text.find('&', i + 2)) { | |
| 85 menu_text.insert(i, 1, '&'); | |
| 86 } | |
| 87 return menu_text; | |
| 88 } | |
| 89 | |
| 90 bool BackForwardMenuModel::IsLabelDynamicAt(int index) const { | |
| 91 // This object is only used for a single showing of a menu. | |
| 92 return false; | |
| 93 } | |
| 94 | |
| 95 bool BackForwardMenuModel::GetAcceleratorAt( | |
| 96 int index, | |
| 97 menus::Accelerator* accelerator) const { | |
| 98 return false; | |
| 99 } | |
| 100 | |
| 101 bool BackForwardMenuModel::IsItemCheckedAt(int index) const { | |
| 102 return false; | |
| 103 } | |
| 104 | |
| 105 int BackForwardMenuModel::GetGroupIdAt(int index) const { | |
| 106 return false; | |
| 107 } | |
| 108 | |
| 109 bool BackForwardMenuModel::GetIconAt(int index, SkBitmap* icon) const { | |
| 110 if (!ItemHasIcon(index)) | |
| 111 return false; | |
| 112 | |
| 113 if (index == GetItemCount() - 1) { | |
| 114 *icon = *ResourceBundle::GetSharedInstance().GetBitmapNamed( | |
| 115 IDR_HISTORY_FAVICON); | |
| 116 } else { | |
| 117 NavigationEntry* entry = GetNavigationEntry(index); | |
| 118 *icon = entry->favicon().bitmap(); | |
| 119 } | |
| 120 | |
| 121 return true; | |
| 122 } | |
| 123 | |
| 124 menus::ButtonMenuItemModel* BackForwardMenuModel::GetButtonMenuItemAt( | |
| 125 int index) const { | |
| 126 return NULL; | |
| 127 } | |
| 128 | |
| 129 bool BackForwardMenuModel::IsEnabledAt(int index) const { | |
| 130 return index < GetItemCount() && !IsSeparator(index); | |
| 131 } | |
| 132 | |
| 133 menus::MenuModel* BackForwardMenuModel::GetSubmenuModelAt(int index) const { | |
| 134 return NULL; | |
| 135 } | |
| 136 | |
| 137 void BackForwardMenuModel::HighlightChangedTo(int index) { | |
| 138 } | |
| 139 | |
| 140 void BackForwardMenuModel::ActivatedAt(int index) { | |
| 141 ActivatedAtWithDisposition(index, CURRENT_TAB); | |
| 142 } | |
| 143 | |
| 144 void BackForwardMenuModel::ActivatedAtWithDisposition( | |
| 145 int index, int disposition) { | |
| 146 Profile* profile = browser_->profile(); | |
| 147 | |
| 148 DCHECK(!IsSeparator(index)); | |
| 149 | |
| 150 // Execute the command for the last item: "Show Full History". | |
| 151 if (index == GetItemCount() - 1) { | |
| 152 UserMetrics::RecordComputedAction(BuildActionName("ShowFullHistory", -1), | |
| 153 profile); | |
| 154 browser_->ShowSingletonTab(GURL(chrome::kChromeUIHistoryURL), false); | |
| 155 return; | |
| 156 } | |
| 157 | |
| 158 // Log whether it was a history or chapter click. | |
| 159 if (index < GetHistoryItemCount()) { | |
| 160 UserMetrics::RecordComputedAction( | |
| 161 BuildActionName("HistoryClick", index), profile); | |
| 162 } else { | |
| 163 UserMetrics::RecordComputedAction( | |
| 164 BuildActionName("ChapterClick", index - GetHistoryItemCount() - 1), | |
| 165 profile); | |
| 166 } | |
| 167 | |
| 168 int controller_index = MenuIndexToNavEntryIndex(index); | |
| 169 if (!browser_->NavigateToIndexWithDisposition( | |
| 170 controller_index, static_cast<WindowOpenDisposition>(disposition))) { | |
| 171 NOTREACHED(); | |
| 172 } | |
| 173 } | |
| 174 | |
| 175 void BackForwardMenuModel::MenuWillShow() { | |
| 176 UserMetrics::RecordComputedAction(BuildActionName("Popup", -1), | |
| 177 browser_->profile()); | |
| 178 } | |
| 179 | |
| 180 bool BackForwardMenuModel::IsSeparator(int index) const { | |
| 181 int history_items = GetHistoryItemCount(); | |
| 182 // If the index is past the number of history items + separator, | |
| 183 // we then consider if it is a chapter-stop entry. | |
| 184 if (index > history_items) { | |
| 185 // We either are in ChapterStop area, or at the end of the list (the "Show | |
| 186 // Full History" link). | |
| 187 int chapter_stops = GetChapterStopCount(history_items); | |
| 188 if (chapter_stops == 0) | |
| 189 return false; // We must have reached the "Show Full History" link. | |
| 190 // Otherwise, look to see if we have reached the separator for the | |
| 191 // chapter-stops. If not, this is a chapter stop. | |
| 192 return (index == history_items + 1 + chapter_stops); | |
| 193 } | |
| 194 | |
| 195 // Look to see if we have reached the separator for the history items. | |
| 196 return index == history_items; | |
| 197 } | |
| 198 | |
| 199 int BackForwardMenuModel::GetHistoryItemCount() const { | |
| 200 TabContents* contents = GetTabContents(); | |
| 201 int items = 0; | |
| 202 | |
| 203 if (model_type_ == FORWARD_MENU) { | |
| 204 // Only count items from n+1 to end (if n is current entry) | |
| 205 items = contents->controller().entry_count() - | |
| 206 contents->controller().GetCurrentEntryIndex() - 1; | |
| 207 } else { | |
| 208 items = contents->controller().GetCurrentEntryIndex(); | |
| 209 } | |
| 210 | |
| 211 if (items > kMaxHistoryItems) | |
| 212 items = kMaxHistoryItems; | |
| 213 else if (items < 0) | |
| 214 items = 0; | |
| 215 | |
| 216 return items; | |
| 217 } | |
| 218 | |
| 219 int BackForwardMenuModel::GetChapterStopCount(int history_items) const { | |
| 220 TabContents* contents = GetTabContents(); | |
| 221 | |
| 222 int chapter_stops = 0; | |
| 223 int current_entry = contents->controller().GetCurrentEntryIndex(); | |
| 224 | |
| 225 if (history_items == kMaxHistoryItems) { | |
| 226 int chapter_id = current_entry; | |
| 227 if (model_type_ == FORWARD_MENU) { | |
| 228 chapter_id += history_items; | |
| 229 } else { | |
| 230 chapter_id -= history_items; | |
| 231 } | |
| 232 | |
| 233 do { | |
| 234 chapter_id = GetIndexOfNextChapterStop(chapter_id, | |
| 235 model_type_ == FORWARD_MENU); | |
| 236 if (chapter_id != -1) | |
| 237 ++chapter_stops; | |
| 238 } while (chapter_id != -1 && chapter_stops < kMaxChapterStops); | |
| 239 } | |
| 240 | |
| 241 return chapter_stops; | |
| 242 } | |
| 243 | |
| 244 int BackForwardMenuModel::GetIndexOfNextChapterStop(int start_from, | |
| 245 bool forward) const { | |
| 246 TabContents* contents = GetTabContents(); | |
| 247 NavigationController& controller = contents->controller(); | |
| 248 | |
| 249 int max_count = controller.entry_count(); | |
| 250 if (start_from < 0 || start_from >= max_count) | |
| 251 return -1; // Out of bounds. | |
| 252 | |
| 253 if (forward) { | |
| 254 if (start_from < max_count - 1) { | |
| 255 // We want to advance over the current chapter stop, so we add one. | |
| 256 // We don't need to do this when direction is backwards. | |
| 257 start_from++; | |
| 258 } else { | |
| 259 return -1; | |
| 260 } | |
| 261 } | |
| 262 | |
| 263 NavigationEntry* start_entry = controller.GetEntryAtIndex(start_from); | |
| 264 const GURL& url = start_entry->url(); | |
| 265 | |
| 266 if (!forward) { | |
| 267 // When going backwards we return the first entry we find that has a | |
| 268 // different domain. | |
| 269 for (int i = start_from - 1; i >= 0; --i) { | |
| 270 if (!net::RegistryControlledDomainService::SameDomainOrHost(url, | |
| 271 controller.GetEntryAtIndex(i)->url())) | |
| 272 return i; | |
| 273 } | |
| 274 // We have reached the beginning without finding a chapter stop. | |
| 275 return -1; | |
| 276 } else { | |
| 277 // When going forwards we return the entry before the entry that has a | |
| 278 // different domain. | |
| 279 for (int i = start_from + 1; i < max_count; ++i) { | |
| 280 if (!net::RegistryControlledDomainService::SameDomainOrHost(url, | |
| 281 controller.GetEntryAtIndex(i)->url())) | |
| 282 return i - 1; | |
| 283 } | |
| 284 // Last entry is always considered a chapter stop. | |
| 285 return max_count - 1; | |
| 286 } | |
| 287 } | |
| 288 | |
| 289 int BackForwardMenuModel::FindChapterStop(int offset, | |
| 290 bool forward, | |
| 291 int skip) const { | |
| 292 if (offset < 0 || skip < 0) | |
| 293 return -1; | |
| 294 | |
| 295 if (!forward) | |
| 296 offset *= -1; | |
| 297 | |
| 298 TabContents* contents = GetTabContents(); | |
| 299 int entry = contents->controller().GetCurrentEntryIndex() + offset; | |
| 300 for (int i = 0; i < skip + 1; i++) | |
| 301 entry = GetIndexOfNextChapterStop(entry, forward); | |
| 302 | |
| 303 return entry; | |
| 304 } | |
| 305 | |
| 306 bool BackForwardMenuModel::ItemHasCommand(int index) const { | |
| 307 return index < GetItemCount() && !IsSeparator(index); | |
| 308 } | |
| 309 | |
| 310 bool BackForwardMenuModel::ItemHasIcon(int index) const { | |
| 311 return index < GetItemCount() && !IsSeparator(index); | |
| 312 } | |
| 313 | |
| 314 string16 BackForwardMenuModel::GetShowFullHistoryLabel() const { | |
| 315 return l10n_util::GetStringUTF16(IDS_SHOWFULLHISTORY_LINK); | |
| 316 } | |
| 317 | |
| 318 TabContents* BackForwardMenuModel::GetTabContents() const { | |
| 319 // We use the test tab contents if the unit test has specified it. | |
| 320 return test_tab_contents_ ? test_tab_contents_ : | |
| 321 browser_->GetSelectedTabContents(); | |
| 322 } | |
| 323 | |
| 324 int BackForwardMenuModel::MenuIndexToNavEntryIndex(int index) const { | |
| 325 TabContents* contents = GetTabContents(); | |
| 326 int history_items = GetHistoryItemCount(); | |
| 327 | |
| 328 DCHECK_GE(index, 0); | |
| 329 | |
| 330 // Convert anything above the History items separator. | |
| 331 if (index < history_items) { | |
| 332 if (model_type_ == FORWARD_MENU) { | |
| 333 index += contents->controller().GetCurrentEntryIndex() + 1; | |
| 334 } else { | |
| 335 // Back menu is reverse. | |
| 336 index = contents->controller().GetCurrentEntryIndex() - (index + 1); | |
| 337 } | |
| 338 return index; | |
| 339 } | |
| 340 if (index == history_items) | |
| 341 return -1; // Don't translate the separator for history items. | |
| 342 | |
| 343 if (index >= history_items + 1 + GetChapterStopCount(history_items)) | |
| 344 return -1; // This is beyond the last chapter stop so we abort. | |
| 345 | |
| 346 // This menu item is a chapter stop located between the two separators. | |
| 347 index = FindChapterStop(history_items, | |
| 348 model_type_ == FORWARD_MENU, | |
| 349 index - history_items - 1); | |
| 350 | |
| 351 return index; | |
| 352 } | |
| 353 | |
| 354 NavigationEntry* BackForwardMenuModel::GetNavigationEntry(int index) const { | |
| 355 int controller_index = MenuIndexToNavEntryIndex(index); | |
| 356 NavigationController& controller = GetTabContents()->controller(); | |
| 357 if (controller_index >= 0 && controller_index < controller.entry_count()) | |
| 358 return controller.GetEntryAtIndex(controller_index); | |
| 359 | |
| 360 NOTREACHED(); | |
| 361 return NULL; | |
| 362 } | |
| 363 | |
| 364 std::string BackForwardMenuModel::BuildActionName( | |
| 365 const std::string& action, int index) const { | |
| 366 DCHECK(!action.empty()); | |
| 367 DCHECK(index >= -1); | |
| 368 std::string metric_string; | |
| 369 if (model_type_ == FORWARD_MENU) | |
| 370 metric_string += "ForwardMenu_"; | |
| 371 else | |
| 372 metric_string += "BackMenu_"; | |
| 373 metric_string += action; | |
| 374 if (index != -1) { | |
| 375 // +1 is for historical reasons (indices used to start at 1). | |
| 376 metric_string += base::IntToString(index + 1); | |
| 377 } | |
| 378 return metric_string; | |
| 379 } | |
| OLD | NEW |