blob: 50c7da145babee1ef21176050e32a4a9e76f20d1 [file] [log] [blame]
// Copyright (c) 2006-2008 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 "chrome/browser/back_forward_menu_model.h"
#include "chrome/browser/browser.h"
#include "chrome/browser/history_tab_ui.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/l10n_util.h"
#include "net/base/registry_controlled_domain.h"
#include "generated_resources.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) {
}
BackForwardMenuModel::~BackForwardMenuModel() {
}
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::GetHistoryItemCount() const {
TabContents* contents = GetTabContents();
NavigationController* controller = contents->controller();
int items = 0;
if (model_type_ == FORWARD_MENU_DELEGATE) {
// Only count items from n+1 to end (if n is current entry)
items = controller->GetEntryCount() -
controller->GetCurrentEntryIndex() - 1;
} else {
items = 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();
NavigationController* controller = contents->controller();
int chapter_stops = 0;
int current_entry = controller->GetCurrentEntryIndex();
if (history_items == kMaxHistoryItems) {
int chapter_id = current_entry;
if (model_type_ == FORWARD_MENU_DELEGATE) {
chapter_id += history_items;
} else {
chapter_id -= history_items;
}
do {
chapter_id = GetIndexOfNextChapterStop(chapter_id,
model_type_ == FORWARD_MENU_DELEGATE);
if (chapter_id != -1)
++chapter_stops;
} while (chapter_id != -1 && chapter_stops < kMaxChapterStops);
}
return chapter_stops;
}
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;
}
int BackForwardMenuModel::MenuIdToNavEntryIndex(int menu_id) const {
TabContents* contents = GetTabContents();
NavigationController* controller = contents->controller();
int history_items = GetHistoryItemCount();
DCHECK(menu_id > 0);
// Convert anything above the History items separator.
if (menu_id <= history_items) {
if (model_type_ == FORWARD_MENU_DELEGATE) {
// The |menu_id| is relative to our current position, so we need to add.
menu_id += controller->GetCurrentEntryIndex();
} else {
// Back menu is reverse.
menu_id = controller->GetCurrentEntryIndex() - menu_id;
}
return menu_id;
}
if (menu_id == history_items + 1)
return -1; // Don't translate the separator for history items.
if (menu_id >= history_items + 1 + GetChapterStopCount(history_items) + 1)
return -1; // This is beyond the last chapter stop so we abort.
// This menu item is a chapter stop located between the two separators.
menu_id = FindChapterStop(history_items,
model_type_ == FORWARD_MENU_DELEGATE,
menu_id - history_items - 1 - 1);
return menu_id;
}
NavigationEntry* BackForwardMenuModel::GetNavigationEntry(int menu_id) const {
TabContents* contents = GetTabContents();
NavigationController* controller = contents->controller();
int index = MenuIdToNavEntryIndex(menu_id);
return controller->GetEntryAtIndex(index);
}
std::wstring BackForwardMenuModel::GetLabel(int menu_id) const {
// Return label "Show Full History" for the last item of the menu.
if (menu_id == GetItemCount())
return l10n_util::GetString(IDS_SHOWFULLHISTORY_LINK);
// Return an empty string for a separator.
if (IsItemSeparator(menu_id))
return L"";
NavigationEntry* entry = GetNavigationEntry(menu_id);
return entry->title();
}
const SkBitmap& BackForwardMenuModel::GetIcon(int menu_id) const {
// Return NULL if the item doesn't have an icon
if (!HasIcon(menu_id))
return GetEmptyIcon();
NavigationEntry* entry = GetNavigationEntry(menu_id);
return entry->favicon().bitmap();
}
bool BackForwardMenuModel::IsItemSeparator(int menu_id) const {
int history_items = GetHistoryItemCount();
// If the menu_id is higher than the number of history items + separator,
// we then consider if it is a chapter-stop entry.
if (menu_id > history_items + 1) {
// 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 (menu_id == history_items + 1 +
chapter_stops + 1);
}
// Look to see if we have reached the separator for the history items.
return menu_id == history_items + 1;
}
bool BackForwardMenuModel::HasIcon(int menu_id) const {
// Using "id" not "id - 1" because the last item "Show Full History"
// doesn't have an icon.
return menu_id < GetItemCount() && !IsItemSeparator(menu_id);
}
bool BackForwardMenuModel::SupportsCommand(int menu_id) const {
return menu_id - 1 < GetItemCount() && !IsItemSeparator(menu_id);
}
bool BackForwardMenuModel::IsCommandEnabled(int menu_id) const {
return menu_id - 1 < GetItemCount() && !IsItemSeparator(menu_id);
}
std::wstring BackForwardMenuModel::BuildActionName(
const std::wstring& action, int index) const {
DCHECK(!action.empty());
DCHECK(index >= -1);
std::wstring metric_string;
if (model_type_ == FORWARD_MENU_DELEGATE)
metric_string += L"ForwardMenu_";
else
metric_string += L"BackMenu_";
metric_string += action;
if (index != -1)
metric_string += IntToWString(index);
return metric_string;
}
void BackForwardMenuModel::ExecuteCommand(int menu_id) {
TabContents* contents = GetTabContents();
NavigationController* controller = contents->controller();
DCHECK(!IsItemSeparator(menu_id));
// Execute the command for the last item: "Show Full History".
if (menu_id == GetItemCount()) {
UserMetrics::RecordComputedAction(BuildActionName(L"ShowFullHistory", -1),
controller->profile());
browser_->ShowNativeUITab(HistoryTabUI::GetURL());
return;
}
// Log whether it was a history or chapter click.
if (menu_id <= GetHistoryItemCount()) {
UserMetrics::RecordComputedAction(
BuildActionName(L"HistoryClick", menu_id), controller->profile());
} else {
UserMetrics::RecordComputedAction(
BuildActionName(L"ChapterClick", menu_id - GetHistoryItemCount() - 1),
controller->profile());
}
int index = MenuIdToNavEntryIndex(menu_id);
if (index >= 0 && index < controller->GetEntryCount())
controller->GoToIndex(index);
}
void BackForwardMenuModel::MenuWillShow() {
UserMetrics::RecordComputedAction(BuildActionName(L"Popup", -1),
browser_->profile());
}
std::wstring BackForwardMenuModel::GetShowFullHistoryLabel() const {
return l10n_util::GetString(IDS_SHOWFULLHISTORY_LINK);
}
int BackForwardMenuModel::GetIndexOfNextChapterStop(int start_from,
bool forward) const {
TabContents* contents = GetTabContents();
NavigationController* controller = contents->controller();
int max_count = controller->GetEntryCount();
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();
NavigationController* controller = contents->controller();
int entry = controller->GetCurrentEntryIndex() + offset;
for (int i = 0; i < skip + 1; i++)
entry = GetIndexOfNextChapterStop(entry, forward);
return entry;
}