| // 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; |
| } |
| |