| // Copyright (c) 2006-2009 The Chromium Authors. All rights reserved. |
| // Use of this source code is governed by a BSD-style license that can be |
| // found in the LICENSE file. |
| |
| #include "build/build_config.h" |
| |
| #include "chrome/browser/back_forward_menu_model.h" |
| |
| #include "app/l10n_util.h" |
| #include "app/resource_bundle.h" |
| #include "chrome/browser/browser.h" |
| #include "chrome/browser/metrics/user_metrics.h" |
| #include "chrome/browser/tab_contents/navigation_controller.h" |
| #include "chrome/browser/tab_contents/navigation_entry.h" |
| #include "chrome/browser/tab_contents/tab_contents.h" |
| #include "chrome/common/url_constants.h" |
| #include "grit/generated_resources.h" |
| #include "grit/theme_resources.h" |
| #include "net/base/registry_controlled_domain.h" |
| |
| const int BackForwardMenuModel::kMaxHistoryItems = 12; |
| const int BackForwardMenuModel::kMaxChapterStops = 5; |
| |
| BackForwardMenuModel::BackForwardMenuModel(Browser* browser, |
| ModelType model_type) |
| : browser_(browser), |
| test_tab_contents_(NULL), |
| model_type_(model_type) { |
| } |
| |
| bool BackForwardMenuModel::HasIcons() const { |
| return true; |
| } |
| |
| int BackForwardMenuModel::GetItemCount() const { |
| int items = GetHistoryItemCount(); |
| |
| if (items > 0) { |
| int chapter_stops = 0; |
| |
| // Next, we count ChapterStops, if any. |
| if (items == kMaxHistoryItems) |
| chapter_stops = GetChapterStopCount(items); |
| |
| if (chapter_stops) |
| items += chapter_stops + 1; // Chapter stops also need a separator. |
| |
| // If the menu is not empty, add two positions in the end |
| // for a separator and a "Show Full History" item. |
| items += 2; |
| } |
| |
| return items; |
| } |
| |
| menus::MenuModel::ItemType BackForwardMenuModel::GetTypeAt(int index) const { |
| return IsSeparator(index) ? TYPE_SEPARATOR : TYPE_COMMAND; |
| } |
| |
| int BackForwardMenuModel::GetCommandIdAt(int index) const { |
| return index; |
| } |
| |
| string16 BackForwardMenuModel::GetLabelAt(int index) const { |
| // Return label "Show Full History" for the last item of the menu. |
| if (index == GetItemCount() - 1) |
| return l10n_util::GetStringUTF16(IDS_SHOWFULLHISTORY_LINK); |
| |
| // Return an empty string for a separator. |
| if (IsSeparator(index)) |
| return string16(); |
| |
| // Return the entry title, escaping any '&' characters. |
| NavigationEntry* entry = GetNavigationEntry(index); |
| string16 menu_text(entry->GetTitleForDisplay( |
| &GetTabContents()->controller())); |
| for (size_t i = menu_text.find('&'); i != string16::npos; |
| i = menu_text.find('&', i + 2)) { |
| menu_text.insert(i, 1, '&'); |
| } |
| return menu_text; |
| } |
| |
| bool BackForwardMenuModel::IsLabelDynamicAt(int index) const { |
| // This object is only used for a single showing of a menu. |
| return false; |
| } |
| |
| bool BackForwardMenuModel::GetAcceleratorAt( |
| int index, |
| menus::Accelerator* accelerator) const { |
| return false; |
| } |
| |
| bool BackForwardMenuModel::IsItemCheckedAt(int index) const { |
| return false; |
| } |
| |
| int BackForwardMenuModel::GetGroupIdAt(int index) const { |
| return false; |
| } |
| |
| bool BackForwardMenuModel::GetIconAt(int index, SkBitmap* icon) const { |
| if (!ItemHasIcon(index)) |
| return false; |
| |
| if (index == GetItemCount() - 1) { |
| *icon = *ResourceBundle::GetSharedInstance().GetBitmapNamed( |
| IDR_HISTORY_FAVICON); |
| } else { |
| NavigationEntry* entry = GetNavigationEntry(index); |
| *icon = entry->favicon().bitmap(); |
| } |
| |
| return true; |
| } |
| |
| bool BackForwardMenuModel::IsEnabledAt(int index) const { |
| return index < GetItemCount() && !IsSeparator(index); |
| } |
| |
| menus::MenuModel* BackForwardMenuModel::GetSubmenuModelAt(int index) const { |
| return NULL; |
| } |
| |
| void BackForwardMenuModel::HighlightChangedTo(int index) { |
| } |
| |
| void BackForwardMenuModel::ActivatedAt(int index) { |
| ActivatedAtWithDisposition(index, CURRENT_TAB); |
| } |
| |
| void BackForwardMenuModel::ActivatedAtWithDisposition( |
| int index, |
| WindowOpenDisposition disposition) { |
| Profile* profile = browser_->profile(); |
| |
| DCHECK(!IsSeparator(index)); |
| |
| // Execute the command for the last item: "Show Full History". |
| if (index == GetItemCount() - 1) { |
| UserMetrics::RecordComputedAction(BuildActionName("ShowFullHistory", -1), |
| profile); |
| browser_->ShowSingletonTab(GURL(chrome::kChromeUIHistoryURL)); |
| return; |
| } |
| |
| // Log whether it was a history or chapter click. |
| if (index < GetHistoryItemCount()) { |
| UserMetrics::RecordComputedAction( |
| BuildActionName("HistoryClick", index), profile); |
| } else { |
| UserMetrics::RecordComputedAction( |
| BuildActionName("ChapterClick", index - GetHistoryItemCount() - 1), |
| profile); |
| } |
| |
| int controller_index = MenuIndexToNavEntryIndex(index); |
| if (!browser_->NavigateToIndexWithDisposition(controller_index, |
| disposition)) { |
| NOTREACHED(); |
| } |
| } |
| |
| void BackForwardMenuModel::MenuWillShow() { |
| UserMetrics::RecordComputedAction(BuildActionName("Popup", -1), |
| browser_->profile()); |
| } |
| |
| bool BackForwardMenuModel::IsSeparator(int index) const { |
| int history_items = GetHistoryItemCount(); |
| // If the index is past the number of history items + separator, |
| // we then consider if it is a chapter-stop entry. |
| if (index > history_items) { |
| // We either are in ChapterStop area, or at the end of the list (the "Show |
| // Full History" link). |
| int chapter_stops = GetChapterStopCount(history_items); |
| if (chapter_stops == 0) |
| return false; // We must have reached the "Show Full History" link. |
| // Otherwise, look to see if we have reached the separator for the |
| // chapter-stops. If not, this is a chapter stop. |
| return (index == history_items + 1 + chapter_stops); |
| } |
| |
| // Look to see if we have reached the separator for the history items. |
| return index == history_items; |
| } |
| |
| int BackForwardMenuModel::GetHistoryItemCount() const { |
| TabContents* contents = GetTabContents(); |
| int items = 0; |
| |
| if (model_type_ == FORWARD_MENU) { |
| // Only count items from n+1 to end (if n is current entry) |
| items = contents->controller().entry_count() - |
| contents->controller().GetCurrentEntryIndex() - 1; |
| } else { |
| items = contents->controller().GetCurrentEntryIndex(); |
| } |
| |
| if (items > kMaxHistoryItems) |
| items = kMaxHistoryItems; |
| else if (items < 0) |
| items = 0; |
| |
| return items; |
| } |
| |
| int BackForwardMenuModel::GetChapterStopCount(int history_items) const { |
| TabContents* contents = GetTabContents(); |
| |
| int chapter_stops = 0; |
| int current_entry = contents->controller().GetCurrentEntryIndex(); |
| |
| if (history_items == kMaxHistoryItems) { |
| int chapter_id = current_entry; |
| if (model_type_ == FORWARD_MENU) { |
| chapter_id += history_items; |
| } else { |
| chapter_id -= history_items; |
| } |
| |
| do { |
| chapter_id = GetIndexOfNextChapterStop(chapter_id, |
| model_type_ == FORWARD_MENU); |
| if (chapter_id != -1) |
| ++chapter_stops; |
| } while (chapter_id != -1 && chapter_stops < kMaxChapterStops); |
| } |
| |
| return chapter_stops; |
| } |
| |
| int BackForwardMenuModel::GetIndexOfNextChapterStop(int start_from, |
| bool forward) const { |
| TabContents* contents = GetTabContents(); |
| NavigationController& controller = contents->controller(); |
| |
| int max_count = controller.entry_count(); |
| if (start_from < 0 || start_from >= max_count) |
| return -1; // Out of bounds. |
| |
| if (forward) { |
| if (start_from < max_count - 1) { |
| // We want to advance over the current chapter stop, so we add one. |
| // We don't need to do this when direction is backwards. |
| start_from++; |
| } else { |
| return -1; |
| } |
| } |
| |
| NavigationEntry* start_entry = controller.GetEntryAtIndex(start_from); |
| const GURL& url = start_entry->url(); |
| |
| if (!forward) { |
| // When going backwards we return the first entry we find that has a |
| // different domain. |
| for (int i = start_from - 1; i >= 0; --i) { |
| if (!net::RegistryControlledDomainService::SameDomainOrHost(url, |
| controller.GetEntryAtIndex(i)->url())) |
| return i; |
| } |
| // We have reached the beginning without finding a chapter stop. |
| return -1; |
| } else { |
| // When going forwards we return the entry before the entry that has a |
| // different domain. |
| for (int i = start_from + 1; i < max_count; ++i) { |
| if (!net::RegistryControlledDomainService::SameDomainOrHost(url, |
| controller.GetEntryAtIndex(i)->url())) |
| return i - 1; |
| } |
| // Last entry is always considered a chapter stop. |
| return max_count - 1; |
| } |
| } |
| |
| int BackForwardMenuModel::FindChapterStop(int offset, |
| bool forward, |
| int skip) const { |
| if (offset < 0 || skip < 0) |
| return -1; |
| |
| if (!forward) |
| offset *= -1; |
| |
| TabContents* contents = GetTabContents(); |
| int entry = contents->controller().GetCurrentEntryIndex() + offset; |
| for (int i = 0; i < skip + 1; i++) |
| entry = GetIndexOfNextChapterStop(entry, forward); |
| |
| return entry; |
| } |
| |
| bool BackForwardMenuModel::ItemHasCommand(int index) const { |
| return index < GetItemCount() && !IsSeparator(index); |
| } |
| |
| bool BackForwardMenuModel::ItemHasIcon(int index) const { |
| return index < GetItemCount() && !IsSeparator(index); |
| } |
| |
| string16 BackForwardMenuModel::GetShowFullHistoryLabel() const { |
| return l10n_util::GetStringUTF16(IDS_SHOWFULLHISTORY_LINK); |
| } |
| |
| TabContents* BackForwardMenuModel::GetTabContents() const { |
| // We use the test tab contents if the unit test has specified it. |
| return test_tab_contents_ ? test_tab_contents_ : |
| browser_->GetSelectedTabContents(); |
| } |
| |
| int BackForwardMenuModel::MenuIndexToNavEntryIndex(int index) const { |
| TabContents* contents = GetTabContents(); |
| int history_items = GetHistoryItemCount(); |
| |
| DCHECK_GE(index, 0); |
| |
| // Convert anything above the History items separator. |
| if (index < history_items) { |
| if (model_type_ == FORWARD_MENU) { |
| index += contents->controller().GetCurrentEntryIndex() + 1; |
| } else { |
| // Back menu is reverse. |
| index = contents->controller().GetCurrentEntryIndex() - (index + 1); |
| } |
| return index; |
| } |
| if (index == history_items) |
| return -1; // Don't translate the separator for history items. |
| |
| if (index >= history_items + 1 + GetChapterStopCount(history_items)) |
| return -1; // This is beyond the last chapter stop so we abort. |
| |
| // This menu item is a chapter stop located between the two separators. |
| index = FindChapterStop(history_items, |
| model_type_ == FORWARD_MENU, |
| index - history_items - 1); |
| |
| return index; |
| } |
| |
| NavigationEntry* BackForwardMenuModel::GetNavigationEntry(int index) const { |
| int controller_index = MenuIndexToNavEntryIndex(index); |
| NavigationController& controller = GetTabContents()->controller(); |
| if (controller_index >= 0 && controller_index < controller.entry_count()) |
| return controller.GetEntryAtIndex(controller_index); |
| |
| NOTREACHED(); |
| return NULL; |
| } |
| |
| std::string BackForwardMenuModel::BuildActionName( |
| const std::string& action, int index) const { |
| DCHECK(!action.empty()); |
| DCHECK(index >= -1); |
| std::string metric_string; |
| if (model_type_ == FORWARD_MENU) |
| metric_string += "ForwardMenu_"; |
| else |
| metric_string += "BackMenu_"; |
| metric_string += action; |
| if (index != -1) { |
| // +1 is for historical reasons (indices used to start at 1). |
| metric_string += IntToString(index + 1); |
| } |
| return metric_string; |
| } |