summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--chrome/app/chrome_dll_resource.h2
-rw-r--r--chrome/app/generated_resources.grd3
-rw-r--r--chrome/app/nibs/MainMenu.xib7
-rw-r--r--chrome/browser/cocoa/history_menu_bridge.h148
-rw-r--r--chrome/browser/cocoa/history_menu_bridge.mm220
-rw-r--r--chrome/browser/cocoa/history_menu_bridge_unittest.mm278
-rw-r--r--chrome/browser/cocoa/history_menu_cocoa_controller.h1
-rw-r--r--chrome/browser/cocoa/history_menu_cocoa_controller.mm33
-rw-r--r--chrome/browser/cocoa/history_menu_cocoa_controller_unittest.mm88
-rw-r--r--chrome/browser/sessions/tab_restore_service.h6
10 files changed, 519 insertions, 267 deletions
diff --git a/chrome/app/chrome_dll_resource.h b/chrome/app/chrome_dll_resource.h
index feadb72..c72cf1a 100644
--- a/chrome/app/chrome_dll_resource.h
+++ b/chrome/app/chrome_dll_resource.h
@@ -273,8 +273,6 @@
#define IDC_SYSTEM_OPTIONS 45000 // ChromeOS only
#define IDC_INTERNET_OPTIONS 45100 // ChromeOS only
#define IDC_HISTORY_MENU 46000 // OSX only
-#define IDC_HISTORY_MENU_VISITED 46100 // OSX only
-#define IDC_HISTORY_MENU_CLOSED 46200 // OSX only
#define IDC_INPUT_METHODS_MENU 46300 // Linux only
// Custom context menu entries
diff --git a/chrome/app/generated_resources.grd b/chrome/app/generated_resources.grd
index 19ea2420..cca5768 100644
--- a/chrome/app/generated_resources.grd
+++ b/chrome/app/generated_resources.grd
@@ -7119,6 +7119,9 @@ Keep your key file in a safe place. You will need it to create new versions of y
<message name="IDS_HISTORY_CLOSED_MAC" desc="The Mac menu item for the header of recently closed items in the history menu.">
Recently Closed
</message>
+ <message name="IDS_HISTORY_CLOSED_RESTORE_WINDOW_MAC" desc="The Mac menu item for restoring all the tabs of a recently closed window.">
+ Restore All Tabs
+ </message>
<!-- Bookmarks menu -->
<message name="IDS_BOOKMARK_MANAGER_MAC" desc="The Mac menu item for opening the bookmark manager, in the bookmark menu.">
Bookmark Manager
diff --git a/chrome/app/nibs/MainMenu.xib b/chrome/app/nibs/MainMenu.xib
index 38a97ec..c4e689c 100644
--- a/chrome/app/nibs/MainMenu.xib
+++ b/chrome/app/nibs/MainMenu.xib
@@ -878,6 +878,7 @@
<int key="NSMnemonicLoc">2147483647</int>
<reference key="NSOnImage" ref="353210768"/>
<reference key="NSMixedImage" ref="549394948"/>
+ <int key="NSTag">400</int>
</object>
<object class="NSMenuItem" id="792145602">
<reference key="NSMenu" ref="436720301"/>
@@ -887,7 +888,7 @@
<int key="NSMnemonicLoc">2147483647</int>
<reference key="NSOnImage" ref="353210768"/>
<reference key="NSMixedImage" ref="549394948"/>
- <int key="NSTag">46100</int>
+ <int key="NSTag">401</int>
</object>
<object class="NSMenuItem" id="259488787">
<reference key="NSMenu" ref="436720301"/>
@@ -898,6 +899,7 @@
<int key="NSMnemonicLoc">2147483647</int>
<reference key="NSOnImage" ref="353210768"/>
<reference key="NSMixedImage" ref="549394948"/>
+ <int key="NSTag">440</int>
</object>
<object class="NSMenuItem" id="101838950">
<reference key="NSMenu" ref="436720301"/>
@@ -907,7 +909,7 @@
<int key="NSMnemonicLoc">2147483647</int>
<reference key="NSOnImage" ref="353210768"/>
<reference key="NSMixedImage" ref="549394948"/>
- <int key="NSTag">46200</int>
+ <int key="NSTag">441</int>
</object>
<object class="NSMenuItem" id="517951834">
<reference key="NSMenu" ref="436720301"/>
@@ -918,6 +920,7 @@
<int key="NSMnemonicLoc">2147483647</int>
<reference key="NSOnImage" ref="353210768"/>
<reference key="NSMixedImage" ref="549394948"/>
+ <int key="NSTag">480</int>
</object>
<object class="NSMenuItem" id="64100325">
<reference key="NSMenu" ref="436720301"/>
diff --git a/chrome/browser/cocoa/history_menu_bridge.h b/chrome/browser/cocoa/history_menu_bridge.h
index bfa7959b..ef84031 100644
--- a/chrome/browser/cocoa/history_menu_bridge.h
+++ b/chrome/browser/cocoa/history_menu_bridge.h
@@ -6,12 +6,14 @@
#define CHROME_BROWSER_COCOA_HISTORY_MENU_BRIDGE_H_
#import <Cocoa/Cocoa.h>
+#include <map>
+
#include "base/scoped_nsobject.h"
#include "base/scoped_ptr.h"
-#include "base/scoped_vector.h"
#include "chrome/browser/cancelable_request.h"
#import "chrome/browser/favicon_service.h"
#include "chrome/browser/history/history.h"
+#include "chrome/browser/sessions/session_id.h"
#include "chrome/browser/sessions/tab_restore_service.h"
#include "chrome/common/notification_observer.h"
@@ -22,24 +24,34 @@ class Profile;
class TabNavigationEntry;
@class HistoryMenuCocoaController;
-// C++ controller for the history menu; one per AppController (means there
+namespace {
+
+class HistoryMenuBridgeTest;
+
+}
+
+// C++ bridge for the history menu; one per AppController (means there
// is only one). This class observes various data sources, namely the
// HistoryService and the TabRestoreService, and then updates the NSMenu when
// there is new data.
//
// The history menu is broken up into sections: most visisted and recently
-// closed. The overall menu has a tag of IDC_HISTORY_MENU, with two sections
-// IDC_HISTORY_MENU_VISITED and IDC_HISTORY_MENU_CLOSED, which are used to
-// delineate the two sections. Items within these sections are assigned tags
-// within IDC_HISTORY_MENU_* + 1..99. Most Chromium Cocoa menu items are static
-// from a nib (e.g. New Tab), but may be enabled/disabled under certain
-// circumstances (e.g. Cut and Paste). In addition, most Cocoa menu items have
-// firstResponder: as a target. Unusually, history menu items are created
-// dynamically. They also have a target of HistoryMenuCocoaController, not
-// firstResponder. See HistoryMenuBridge::AddItemToMenu(). Unlike most of our
-// Cocoa-Bridge classes, the HMCC is not at the root of the ownership model
-// because its only function is to respond to menu item actions; everything
-// else is done in this bridge.
+// closed. The overall menu has a tag of IDC_HISTORY_MENU, with the user content
+// items having the local tags defined in the enum below. Items within a section
+// all share the same tag. The structure of the menu is laid out in MainMenu.xib
+// and the generated content is inserted after the Title elements. The recently
+// closed section is special in that those menu items can have submenus to list
+// all the tabs within that closed window. By convention, these submenu items
+// have a tag that's equal to the parent + 1. Tags within the history menu have
+// a range of [400,500) and do not go through CommandDispatch for their target-
+// action mechanism.
+//
+// These menu items do not use firstResponder as their target. Rather, they are
+// hooked directly up to the HistoryMenuCocoaController that then bridges back
+// to this class. These items are created via the AddItemToMenu() helper. Also,
+// unlike the typical ownership model, this bridge owns its controller. The
+// controller is very thin and only exists to interact with Cocoa, but this
+// class does the bulk of the work.
class HistoryMenuBridge : public NotificationObserver,
public TabRestoreService::Observer {
public:
@@ -47,7 +59,19 @@ class HistoryMenuBridge : public NotificationObserver,
// we pull things from different sources with different data types.
struct HistoryItem {
public:
- HistoryItem() : icon_requested(false) {}
+ HistoryItem()
+ : icon_requested(false),
+ menu_item(nil),
+ session_id(0) {}
+
+ // Copy constructor allowed.
+ HistoryItem(const HistoryItem& copy)
+ : title(copy.title),
+ url(copy.url),
+ icon_requested(false),
+ menu_item(nil),
+ session_id(copy.session_id) {}
+
~HistoryItem() {}
// The title for the menu item.
@@ -67,8 +91,36 @@ class HistoryMenuBridge : public NotificationObserver,
// The pointer to the item after it has been created. Weak.
NSMenuItem* menu_item;
+ // This ID is unique for a browser session and can be passed to the
+ // TabRestoreService to re-open the closed window or tab that this
+ // references. A non-0 session ID indicates that this is an entry can be
+ // restored that way. Otherwise, the URL will be used to open the item and
+ // this ID will be 0.
+ SessionID::id_type session_id;
+
+ // If the HistoryItem is a window, this will be the vector of tabs. Note
+ // that this is a list of weak references. The |menu_item_map_| is the owner
+ // of all items. If it is not a window, then the entry is a single page and
+ // the vector will be empty.
+ std::vector<HistoryItem*> tabs;
+
private:
- DISALLOW_COPY_AND_ASSIGN(HistoryItem);
+ // Copying is explicitly allowed, but assignment is not.
+ void operator=(const HistoryItem&);
+ };
+
+ // These tags are not global view tags and are local to the history menu. The
+ // normal procedure for menu items is to go through CommandDispatch, but since
+ // history menu items are hooked directly up to their target, they do not need
+ // to have the global IDC view tags.
+ enum Tags {
+ kMostVisitedSeparator = 400, // Separator before most visited section.
+ kMostVisitedTitle = 401, // Title of the most visited section.
+ kMostVisited = 420, // Used for all entries in the most visited section.
+ kRecentlyClosedSeparator = 440, // Item before recently closed section.
+ kRecentlyClosedTitle = 441, // Title of recently closed section.
+ kRecentlyClosed = 460, // Used for items in the recently closed section.
+ kShowFullSeparator = 480 // Separator after the recently closed section.
};
explicit HistoryMenuBridge(Profile* profile);
@@ -83,27 +135,34 @@ class HistoryMenuBridge : public NotificationObserver,
virtual void TabRestoreServiceChanged(TabRestoreService* service);
virtual void TabRestoreServiceDestroyed(TabRestoreService* service);
+ // Looks up an NSMenuItem in the |menu_item_map_| and returns the
+ // corresponding HistoryItem.
+ HistoryItem* HistoryItemForMenuItem(NSMenuItem* item);
+
// I wish I has a "friend @class" construct. These are used by the HMCC
// to access model information when responding to actions.
HistoryService* service();
Profile* profile();
- const ScopedVector<HistoryItem>* const visited_results();
- const ScopedVector<HistoryItem>* const closed_results();
protected:
// Return the History menu.
- NSMenu* HistoryMenu();
-
- // Clear items in the given |menu|. The menu is broken into sections, defined
- // by IDC_HISTORY_MENU_* constants. This function will clear |count| menu
- // items, starting from |tag|.
- void ClearMenuSection(NSMenu* menu, NSInteger tag, unsigned int count);
-
- // Adds a given title and URL to HistoryMenu() with a certain tag and index.
- void AddItemToMenu(HistoryItem* item,
- NSMenu* menu,
- NSInteger tag,
- NSInteger index);
+ virtual NSMenu* HistoryMenu();
+
+ // Clear items in the given |menu|. Menu items in the same section are given
+ // the same tag. This will go through the entire history menu, removing all
+ // items with a given tag. Note that this will recurse to submenus, removing
+ // child items from the menu item map. This will only remove items that have
+ // a target hooked up to the |controller_|.
+ void ClearMenuSection(NSMenu* menu, NSInteger tag);
+
+ // Adds a given title and URL to the passed-in menu with a certain tag and
+ // index. This will add |item| and the newly created menu item to the
+ // |menu_item_map_|, which takes ownership. Items are deleted in
+ // ClearMenuSection(). This returns the new menu item that was just added.
+ NSMenuItem* AddItemToMenu(HistoryItem* item,
+ NSMenu* menu,
+ NSInteger tag,
+ NSInteger index);
// Called by the ctor if |service_| is ready at the time, or by a
// notification receiver. Finishes initialization tasks by subscribing for
@@ -118,9 +177,9 @@ class HistoryMenuBridge : public NotificationObserver,
void OnVisitedHistoryResults(CancelableRequestProvider::Handle handle,
std::vector<PageUsageData*>* results);
- // Tries to add the current Tab's TabNavigationEntry's NavigationEntry object
- // to |closed_results_|. Return TRUE if the operation completed successfully.
- bool AddNavigationForTab(const TabRestoreService::Tab& entry);
+ // Creates a HistoryItem* for the given tab entry. Caller takes ownership of
+ // the result and must delete it when finished.
+ HistoryItem* HistoryItemForTab(const TabRestoreService::Tab& entry);
// Helper function that sends an async request to the FaviconService to get
// an icon. The callback will update the NSMenuItem directly.
@@ -141,28 +200,29 @@ class HistoryMenuBridge : public NotificationObserver,
void CancelFaviconRequest(HistoryItem* item);
private:
- friend class HistoryMenuBridgeTest;
+ friend class ::HistoryMenuBridgeTest;
+ friend class HistoryMenuCocoaControllerTest;
scoped_nsobject<HistoryMenuCocoaController> controller_; // strong
- Profile* profile_; // weak
- HistoryService* history_service_; // weak
- TabRestoreService* tab_restore_service_; // weak
+ Profile* profile_; // weak
+ HistoryService* history_service_; // weak
+ TabRestoreService* tab_restore_service_; // weak
NotificationRegistrar registrar_;
CancelableRequestConsumer cancelable_request_consumer_;
- // The most recent results we've received.
- ScopedVector<HistoryItem> visited_results_;
- ScopedVector<HistoryItem> closed_results_;
+ // Mapping of NSMenuItems to HistoryItems. This owns the HistoryItems until
+ // they are removed and deleted via ClearMenuSection().
+ std::map<NSMenuItem*, HistoryItem*> menu_item_map_;
// Maps HistoryItems to favicon request Handles.
CancelableRequestConsumerTSimple<HistoryItem*> favicon_consumer_;
- // We coalesce requests to re-create the menu. |create_in_progress_| is true
- // whenever we are either waiting for the history service to return query
- // results, or when we are rebuilding. |need_recreate_| is true whenever a
- // rebuild has been scheduled but is waiting for the current one to finish.
+ // Requests to re-create the menu are coalesced. |create_in_progress_| is true
+ // when either waiting for the history service to return query results, or
+ // when the menu is rebuilding. |need_recreate_| is true whenever a rebuild
+ // has been scheduled but is waiting for the current one to finish.
bool create_in_progress_;
bool need_recreate_;
diff --git a/chrome/browser/cocoa/history_menu_bridge.mm b/chrome/browser/cocoa/history_menu_bridge.mm
index 82c8beb..093ab40 100644
--- a/chrome/browser/cocoa/history_menu_bridge.mm
+++ b/chrome/browser/cocoa/history_menu_bridge.mm
@@ -4,6 +4,7 @@
#include "chrome/browser/cocoa/history_menu_bridge.h"
+#include "app/l10n_util.h"
#include "app/resource_bundle.h"
#include "base/callback.h"
#include "base/stl_util-inl.h"
@@ -20,6 +21,7 @@
#include "chrome/common/url_constants.h"
#include "gfx/codec/png_codec.h"
#include "grit/app_resources.h"
+#include "grit/generated_resources.h"
#include "grit/theme_resources.h"
#include "skia/ext/skia_utils_mac.h"
#include "third_party/skia/include/core/SkBitmap.h"
@@ -27,10 +29,10 @@
namespace {
// Menus more than this many chars long will get trimmed.
-const NSUInteger kMaximumMenuWidthInChars = 65;
+const NSUInteger kMaximumMenuWidthInChars = 50;
// When trimming, use this many chars from each side.
-const NSUInteger kMenuTrimSizeInChars = 30;
+const NSUInteger kMenuTrimSizeInChars = 25;
// Number of days to consider when getting the number of most visited items.
const int kMostVisitedScope = 90;
@@ -64,8 +66,10 @@ HistoryMenuBridge::HistoryMenuBridge(Profile* profile)
// TODO(???): NULL here means we're OTR. Show this in the GUI somehow?
tab_restore_service_ = profile_->GetTabRestoreService();
- if (tab_restore_service_)
+ if (tab_restore_service_) {
tab_restore_service_->AddObserver(this);
+ tab_restore_service_->LoadTabsFromLastSession();
+ }
}
ResourceBundle& rb = ResourceBundle::GetSharedInstance();
@@ -99,6 +103,14 @@ HistoryMenuBridge::~HistoryMenuBridge() {
if (tab_restore_service_)
tab_restore_service_->RemoveObserver(this);
+
+ // Since the map owns the HistoryItems, delete anything that still exists.
+ std::map<NSMenuItem*, HistoryItem*>::iterator it = menu_item_map_.begin();
+ while (it != menu_item_map_.end()) {
+ HistoryItem* item = it->second;
+ menu_item_map_.erase(it++);
+ delete item;
+ }
}
void HistoryMenuBridge::Observe(NotificationType type,
@@ -129,51 +141,88 @@ void HistoryMenuBridge::Observe(NotificationType type,
void HistoryMenuBridge::TabRestoreServiceChanged(TabRestoreService* service) {
const TabRestoreService::Entries& entries = service->entries();
- // Clear the history menu before modifying |closed_results_|.
+ // Clear the history menu before rebuilding.
NSMenu* menu = HistoryMenu();
- ClearMenuSection(menu, IDC_HISTORY_MENU_CLOSED, closed_results_.size());
- closed_results_.reset();
+ ClearMenuSection(menu, kRecentlyClosed);
+
+ // Index for the next menu item.
+ NSInteger index = [menu indexOfItemWithTag:kRecentlyClosedTitle] + 1;
+ NSUInteger added_count = 0;
- unsigned int added_count = 0;
for (TabRestoreService::Entries::const_iterator it = entries.begin();
it != entries.end() && added_count < kRecentlyClosedCount; ++it) {
TabRestoreService::Entry* entry = *it;
- // If we have a window, loop over all of its tabs. This could consume all
- // of |kRecentlyClosedCount| in a given outer loop iteration.
+ // If this is a window, create a submenu for all of its tabs.
if (entry->type == TabRestoreService::WINDOW) {
TabRestoreService::Window* entry_win = (TabRestoreService::Window*)entry;
- std::vector<TabRestoreService::Tab> tabs = entry_win->tabs;
+ std::vector<TabRestoreService::Tab>& tabs = entry_win->tabs;
+ if (!tabs.size())
+ continue;
+
+ // Create the item for the parent/window.
+ HistoryItem* item = new HistoryItem();
+ if (tabs.size() == 1) {
+ item->title = l10n_util::GetStringUTF16(
+ IDS_NEW_TAB_RECENTLY_CLOSED_WINDOW_SINGLE);
+ } else {
+ item->title =l10n_util::GetStringFUTF16(
+ IDS_NEW_TAB_RECENTLY_CLOSED_WINDOW_MULTIPLE,
+ IntToString16(tabs.size()));
+ }
+ item->session_id = entry_win->id;
+
+ // Create the submenu.
+ scoped_nsobject<NSMenu> submenu([[NSMenu alloc] init]);
+
+ // Create standard items within the window submenu.
+ NSString* restore_title = l10n_util::GetNSString(
+ IDS_HISTORY_CLOSED_RESTORE_WINDOW_MAC);
+ scoped_nsobject<NSMenuItem> restore_item(
+ [[NSMenuItem alloc] initWithTitle:restore_title
+ action:@selector(openHistoryMenuItem:)
+ keyEquivalent:@""]);
+ [restore_item setTarget:controller_.get()];
+ // Duplicate the HistoryItem otherwise the different NSMenuItems will
+ // point to the same HistoryItem, which would then be double-freed when
+ // removing the items from the map or in the dtor.
+ HistoryItem* dup_item = new HistoryItem(*item);
+ menu_item_map_.insert(std::make_pair(restore_item.get(), dup_item));
+ [submenu addItem:restore_item.get()];
+ [submenu addItem:[NSMenuItem separatorItem]];
+
+ // Loop over the window's tabs and add them to the submenu.
+ NSInteger subindex = [[submenu itemArray] count];
std::vector<TabRestoreService::Tab>::const_iterator it;
- for (it = tabs.begin(); it != tabs.end() &&
- added_count < kRecentlyClosedCount; ++it) {
+ for (it = tabs.begin(); it != tabs.end(); ++it) {
TabRestoreService::Tab tab = *it;
- if (AddNavigationForTab(tab))
- ++added_count;
+ HistoryItem* tab_item = HistoryItemForTab(tab);
+ if (tab_item) {
+ item->tabs.push_back(tab_item);
+ AddItemToMenu(tab_item, submenu.get(), kRecentlyClosed + 1,
+ subindex++);
+ }
+ }
+
+ // Sometimes it is possible for there to not be any subitems for a given
+ // window; if that is the case, do not add the entry to the main menu.
+ if ([[submenu itemArray] count] > 2) {
+ // Create the menu item parent.
+ NSMenuItem* parent_item =
+ AddItemToMenu(item, menu, kRecentlyClosed, index++);
+ [parent_item setSubmenu:submenu.get()];
+ ++added_count;
}
} else if (entry->type == TabRestoreService::TAB) {
TabRestoreService::Tab* tab =
static_cast<TabRestoreService::Tab*>(entry);
- if (AddNavigationForTab(*tab))
+ HistoryItem* item = HistoryItemForTab(*tab);
+ if (item) {
+ AddItemToMenu(item, menu, kRecentlyClosed, index++);
++added_count;
+ }
}
}
-
- // Remove extraneous/old results.
- if (closed_results_.size() > kRecentlyClosedCount)
- STLDeleteContainerPointers(closed_results_.begin(),
- closed_results_.end() - kRecentlyClosedCount);
-
- NSInteger top_index = [menu indexOfItemWithTag:IDC_HISTORY_MENU_CLOSED] + 1;
-
- int i = 0; // Count offsets for |tag| and |index| in AddItemToMenu().
- for (ScopedVector<HistoryItem>::const_iterator it = closed_results_.begin();
- it != closed_results_.end(); ++it) {
- HistoryItem* item = *it;
- NSInteger tag = IDC_HISTORY_MENU_CLOSED + 1 + i;
- AddItemToMenu(item, HistoryMenu(), tag, top_index + i);
- ++i;
- }
}
void HistoryMenuBridge::TabRestoreServiceDestroyed(
@@ -181,6 +230,15 @@ void HistoryMenuBridge::TabRestoreServiceDestroyed(
// Intentionally left blank. We hold a weak reference to the service.
}
+HistoryMenuBridge::HistoryItem* HistoryMenuBridge::HistoryItemForMenuItem(
+ NSMenuItem* item) {
+ std::map<NSMenuItem*, HistoryItem*>::iterator it = menu_item_map_.find(item);
+ if (it != menu_item_map_.end()) {
+ return it->second;
+ }
+ return NULL;
+}
+
HistoryService* HistoryMenuBridge::service() {
return history_service_;
}
@@ -189,61 +247,42 @@ Profile* HistoryMenuBridge::profile() {
return profile_;
}
-const ScopedVector<HistoryMenuBridge::HistoryItem>* const
- HistoryMenuBridge::visited_results() {
- return &visited_results_;
-}
-
-const ScopedVector<HistoryMenuBridge::HistoryItem>* const
- HistoryMenuBridge::closed_results() {
- return &closed_results_;
-}
-
NSMenu* HistoryMenuBridge::HistoryMenu() {
NSMenu* history_menu = [[[NSApp mainMenu] itemWithTag:IDC_HISTORY_MENU]
submenu];
return history_menu;
}
-void HistoryMenuBridge::ClearMenuSection(NSMenu* menu,
- NSInteger tag,
- unsigned int count) {
- const NSInteger max_tag = tag + count + 1;
-
- // Get the index of the first item in the section, excluding the header.
- NSInteger index = [menu indexOfItemWithTag:tag] + 1;
- if (index <= 0 || index >= [menu numberOfItems])
- return; // The section is at the end, empty.
-
- // Remove at the same index, usually, because the menu will shrink by one
- // item each time, shifting all the lower elements up. If we hit a "unhooked"
- // menu item, don't remove it, but advance the index to skip the item.
- NSInteger item_tag = tag;
- while (count > 0 && item_tag < max_tag && index < [menu numberOfItems]) {
- NSMenuItem* menu_item = [menu itemAtIndex:index];
- item_tag = [menu_item tag];
- if ([menu_item action] == @selector(openHistoryMenuItem:)) {
- // If there is a pending favicon request for this menu item, find and
- // cancel it.
- HistoryItem* item =
- const_cast<HistoryItem*>([controller_ itemForTag:item_tag]);
- if (item)
+void HistoryMenuBridge::ClearMenuSection(NSMenu* menu, NSInteger tag) {
+ for (NSMenuItem* menu_item in [menu itemArray]) {
+ if ([menu_item tag] == tag && [menu_item target] == controller_.get()) {
+ // This is an item that should be removed, so find the corresponding model
+ // item.
+ HistoryItem* item = HistoryItemForMenuItem(menu_item);
+
+ // Cancel favicon requests that could hold onto stale pointers. Also
+ // remove the item from the mapping.
+ if (item) {
CancelFaviconRequest(item);
+ menu_item_map_.erase(menu_item);
+ delete item;
+ }
- // Now remove it from the menu.
- [menu removeItemAtIndex:index];
- --count;
- }
- else {
- ++index;
+ // If this menu item has a submenu, recurse.
+ if ([menu_item hasSubmenu]) {
+ ClearMenuSection([menu_item submenu], tag + 1);
+ }
+
+ // Now actually remove the item from the menu.
+ [menu removeItem:menu_item];
}
}
}
-void HistoryMenuBridge::AddItemToMenu(HistoryItem* item,
- NSMenu* menu,
- NSInteger tag,
- NSInteger index) {
+NSMenuItem* HistoryMenuBridge::AddItemToMenu(HistoryItem* item,
+ NSMenu* menu,
+ NSInteger tag,
+ NSInteger index) {
NSString* title = base::SysUTF16ToNSString(item->title);
std::string url_string = item->url.possibly_invalid_spec();
@@ -251,7 +290,7 @@ void HistoryMenuBridge::AddItemToMenu(HistoryItem* item,
if ([title isEqualToString:@""])
title = base::SysUTF8ToNSString(url_string);
NSString* full_title = title;
- if (false && [title length] > kMaximumMenuWidthInChars) {
+ if ([title length] > kMaximumMenuWidthInChars) {
// TODO(rsesek): use app/text_elider.h once it uses string16 and can
// take out the middle of strings.
title = [NSString stringWithFormat:@"%@…%@",
@@ -268,7 +307,7 @@ void HistoryMenuBridge::AddItemToMenu(HistoryItem* item,
[menu_item setTag:tag];
if (item->icon.get())
[menu_item setImage:item->icon.get()];
- else
+ else if (!item->tabs.size())
[menu_item setImage:default_favicon_.get()];
// Add a tooltip.
@@ -277,7 +316,10 @@ void HistoryMenuBridge::AddItemToMenu(HistoryItem* item,
[menu_item setToolTip:tooltip];
[menu insertItem:menu_item atIndex:index];
- item->menu_item = menu_item;
+ item->menu_item = menu_item.get();
+ menu_item_map_.insert(std::make_pair(menu_item.get(), item));
+
+ return menu_item;
}
void HistoryMenuBridge::Init() {
@@ -305,10 +347,8 @@ void HistoryMenuBridge::OnVisitedHistoryResults(
CancelableRequestProvider::Handle handle,
std::vector<PageUsageData*>* results) {
NSMenu* menu = HistoryMenu();
- NSInteger top_item = [menu indexOfItemWithTag:IDC_HISTORY_MENU_VISITED] + 1;
-
- ClearMenuSection(menu, IDC_HISTORY_MENU_VISITED, visited_results_.size());
- visited_results_.reset();
+ ClearMenuSection(menu, kMostVisited);
+ NSInteger top_item = [menu indexOfItemWithTag:kMostVisitedTitle] + 1;
size_t count = results->size();
for (size_t i = 0; i < count; ++i) {
@@ -323,12 +363,8 @@ void HistoryMenuBridge::OnVisitedHistoryResults(
} else {
GetFaviconForHistoryItem(item);
}
- visited_results_.push_back(item); // ScopedVector takes ownership.
-
- // Use the large gaps in tags assignment to create the tag for history menu
- // items.
- NSInteger tag = IDC_HISTORY_MENU_VISITED + 1 + i;
- AddItemToMenu(item, HistoryMenu(), tag, top_item + i);
+ // This will add |item| to the |menu_item_map_|, which takes ownership.
+ AddItemToMenu(item, HistoryMenu(), kMostVisited, top_item + i);
}
// We are already invalid by the time we finished, darn.
@@ -338,25 +374,25 @@ void HistoryMenuBridge::OnVisitedHistoryResults(
create_in_progress_ = false;
}
-bool HistoryMenuBridge::AddNavigationForTab(
+HistoryMenuBridge::HistoryItem* HistoryMenuBridge::HistoryItemForTab(
const TabRestoreService::Tab& entry) {
if (entry.navigations.empty())
- return false;
+ return NULL;
const TabNavigation& current_navigation =
entry.navigations.at(entry.current_navigation_index);
if (current_navigation.virtual_url() == GURL(chrome::kChromeUINewTabURL))
- return false;
+ return NULL;
HistoryItem* item = new HistoryItem();
item->title = current_navigation.title();
item->url = current_navigation.virtual_url();
- closed_results_.push_back(item); // ScopedVector takes ownership.
+ item->session_id = entry.id;
// Tab navigations don't come with icons, so we always have to request them.
GetFaviconForHistoryItem(item);
- return true;
+ return item;
}
void HistoryMenuBridge::GetFaviconForHistoryItem(HistoryItem* item) {
diff --git a/chrome/browser/cocoa/history_menu_bridge_unittest.mm b/chrome/browser/cocoa/history_menu_bridge_unittest.mm
index e8d5dd7..0d23906 100644
--- a/chrome/browser/cocoa/history_menu_bridge_unittest.mm
+++ b/chrome/browser/cocoa/history_menu_bridge_unittest.mm
@@ -11,36 +11,58 @@
#include "chrome/app/chrome_dll_resource.h"
#include "chrome/browser/browser.h"
#include "chrome/browser/cancelable_request.h"
+#include "chrome/browser/cocoa/browser_test_helper.h"
#include "chrome/browser/cocoa/cocoa_test_helper.h"
#include "chrome/browser/cocoa/history_menu_bridge.h"
-#include "chrome/browser/cocoa/browser_test_helper.h"
+#include "chrome/browser/sessions/tab_restore_service.h"
#include "gfx/codec/png_codec.h"
+#include "testing/gmock/include/gmock/gmock.h"
#include "testing/gtest/include/gtest/gtest.h"
+namespace {
+
+class MockTRS : public TabRestoreService {
+ public:
+ MockTRS(Profile* profile) : TabRestoreService(profile, NULL) {}
+ MOCK_CONST_METHOD0(entries, const TabRestoreService::Entries&());
+};
+
+class MockBridge : public HistoryMenuBridge {
+ public:
+ MockBridge(Profile* profile)
+ : HistoryMenuBridge(profile),
+ menu_([[NSMenu alloc] initWithTitle:@"History"]) {}
+
+ virtual NSMenu* HistoryMenu() {
+ return menu_.get();
+ }
+
+ private:
+ scoped_nsobject<NSMenu> menu_;
+};
+
class HistoryMenuBridgeTest : public CocoaTest {
public:
virtual void SetUp() {
CocoaTest::SetUp();
browser_test_helper_.profile()->CreateFaviconService();
+ bridge_.reset(new MockBridge(browser_test_helper_.profile()));
}
// We are a friend of HistoryMenuBridge (and have access to
// protected methods), but none of the classes generated by TEST_F()
// are. Wraps common commands.
- void ClearMenuSection(HistoryMenuBridge* bridge,
- NSMenu* menu,
- NSInteger tag,
- unsigned int count) {
- bridge->ClearMenuSection(menu, tag, count);
+ void ClearMenuSection(NSMenu* menu,
+ NSInteger tag) {
+ bridge_->ClearMenuSection(menu, tag);
}
- void AddItemToBridgeMenu(HistoryMenuBridge* bridge,
- HistoryMenuBridge::HistoryItem* item,
+ void AddItemToBridgeMenu(HistoryMenuBridge::HistoryItem* item,
NSMenu* menu,
NSInteger tag,
NSInteger index) {
- bridge->AddItemToMenu(item, menu, tag, index);
+ bridge_->AddItemToMenu(item, menu, tag, index);
}
NSMenuItem* AddItemToMenu(NSMenu* menu,
@@ -50,8 +72,10 @@ class HistoryMenuBridgeTest : public CocoaTest {
NSMenuItem* item = [[[NSMenuItem alloc] initWithTitle:title action:NULL
keyEquivalent:@""] autorelease];
[item setTag:tag];
- if (selector)
+ if (selector) {
[item setAction:selector];
+ [item setTarget:bridge_->controller_.get()];
+ }
[menu addItem:item];
return item;
}
@@ -64,93 +88,88 @@ class HistoryMenuBridgeTest : public CocoaTest {
return item;
}
- void GetFaviconForHistoryItem(HistoryMenuBridge* bridge,
- HistoryMenuBridge::HistoryItem* item) {
- bridge->GetFaviconForHistoryItem(item);
+ MockTRS::Tab CreateSessionTab(const GURL& url, const string16& title) {
+ MockTRS::Tab tab;
+ tab.current_navigation_index = 0;
+ tab.navigations.push_back(
+ TabNavigation(0, url, GURL(), title, std::string(),
+ PageTransition::LINK));
+ return tab;
}
- void GotFaviconData(HistoryMenuBridge* bridge,
- FaviconService::Handle handle,
+ void GetFaviconForHistoryItem(HistoryMenuBridge::HistoryItem* item) {
+ bridge_->GetFaviconForHistoryItem(item);
+ }
+
+ void GotFaviconData(FaviconService::Handle handle,
bool know_favicon,
scoped_refptr<RefCountedBytes> data,
bool expired,
GURL url) {
- bridge->GotFaviconData(handle, know_favicon, data, expired, url);
+ bridge_->GotFaviconData(handle, know_favicon, data, expired, url);
}
CancelableRequestConsumerTSimple<HistoryMenuBridge::HistoryItem*>&
- favicon_consumer(HistoryMenuBridge* bridge) {
- return bridge->favicon_consumer_;
+ favicon_consumer() {
+ return bridge_->favicon_consumer_;
}
BrowserTestHelper browser_test_helper_;
+ scoped_ptr<MockBridge> bridge_;
};
// Edge case test for clearing until the end of a menu.
TEST_F(HistoryMenuBridgeTest, ClearHistoryMenuUntilEnd) {
- scoped_ptr<HistoryMenuBridge> bridge(
- new HistoryMenuBridge(browser_test_helper_.profile()));
- EXPECT_TRUE(bridge.get());
-
NSMenu* menu = [[[NSMenu alloc] initWithTitle:@"history foo"] autorelease];
- NSInteger tag = IDC_HISTORY_MENU_VISITED;
- AddItemToMenu(menu, @"HEADER", NULL, tag);
+ AddItemToMenu(menu, @"HEADER", NULL, HistoryMenuBridge::kMostVisitedTitle);
- AddItemToMenu(menu, @"alpha", @selector(openHistoryMenuItem:), ++tag);
- AddItemToMenu(menu, @"bravo", @selector(openHistoryMenuItem:), ++tag);
- AddItemToMenu(menu, @"charlie", @selector(openHistoryMenuItem:), ++tag);
- AddItemToMenu(menu, @"delta", @selector(openHistoryMenuItem:), ++tag);
+ NSInteger tag = HistoryMenuBridge::kMostVisited;
+ AddItemToMenu(menu, @"alpha", @selector(openHistoryMenuItem:), tag);
+ AddItemToMenu(menu, @"bravo", @selector(openHistoryMenuItem:), tag);
+ AddItemToMenu(menu, @"charlie", @selector(openHistoryMenuItem:), tag);
+ AddItemToMenu(menu, @"delta", @selector(openHistoryMenuItem:), tag);
- ClearMenuSection(bridge.get(), menu, IDC_HISTORY_MENU_VISITED, 4);
+ ClearMenuSection(menu, HistoryMenuBridge::kMostVisited);
EXPECT_EQ(1, [menu numberOfItems]);
- EXPECT_EQ(@"HEADER", [[menu itemWithTag:IDC_HISTORY_MENU_VISITED] title]);
+ EXPECT_TRUE([@"HEADER" isEqualToString:
+ [[menu itemWithTag:HistoryMenuBridge::kMostVisitedTitle] title]]);
}
// Skip menu items that are not hooked up to |-openHistoryMenuItem:|.
TEST_F(HistoryMenuBridgeTest, ClearHistoryMenuSkipping) {
- scoped_ptr<HistoryMenuBridge> bridge(
- new HistoryMenuBridge(browser_test_helper_.profile()));
- EXPECT_TRUE(bridge.get());
-
NSMenu* menu = [[[NSMenu alloc] initWithTitle:@"history foo"] autorelease];
- NSInteger section_tag = IDC_HISTORY_MENU_VISITED;
- AddItemToMenu(menu, @"HEADER", NULL, section_tag);
- NSInteger tag = section_tag;
+ AddItemToMenu(menu, @"HEADER", NULL, HistoryMenuBridge::kMostVisitedTitle);
- AddItemToMenu(menu, @"alpha", @selector(openHistoryMenuItem:), ++tag);
- AddItemToMenu(menu, @"bravo", @selector(openHistoryMenuItem:), ++tag);
- AddItemToMenu(menu, @"unhooked", NULL, ++tag);
- AddItemToMenu(menu, @"charlie", @selector(openHistoryMenuItem:), ++tag);
+ NSInteger tag = HistoryMenuBridge::kMostVisited;
+ AddItemToMenu(menu, @"alpha", @selector(openHistoryMenuItem:), tag);
+ AddItemToMenu(menu, @"bravo", @selector(openHistoryMenuItem:), tag);
+ AddItemToMenu(menu, @"TITLE", NULL, HistoryMenuBridge::kRecentlyClosedTitle);
+ AddItemToMenu(menu, @"charlie", @selector(openHistoryMenuItem:), tag);
- ClearMenuSection(bridge.get(), menu, section_tag, 4);
+ ClearMenuSection(menu, tag);
EXPECT_EQ(2, [menu numberOfItems]);
- EXPECT_EQ(@"HEADER", [[menu itemWithTag:section_tag] title]);
- EXPECT_EQ(@"unhooked", [[menu itemAtIndex:1] title]);
+ EXPECT_TRUE([@"HEADER" isEqualToString:
+ [[menu itemWithTag:HistoryMenuBridge::kMostVisitedTitle] title]]);
+ EXPECT_TRUE([@"TITLE" isEqualToString:
+ [[menu itemAtIndex:1] title]]);
}
// Edge case test for clearing an empty menu.
TEST_F(HistoryMenuBridgeTest, ClearHistoryMenuEmpty) {
- scoped_ptr<HistoryMenuBridge> bridge(
- new HistoryMenuBridge(browser_test_helper_.profile()));
- EXPECT_TRUE(bridge.get());
-
NSMenu* menu = [[[NSMenu alloc] initWithTitle:@"history foo"] autorelease];
- AddItemToMenu(menu, @"HEADER", NULL, IDC_HISTORY_MENU_VISITED);
+ AddItemToMenu(menu, @"HEADER", NULL, HistoryMenuBridge::kMostVisited);
- ClearMenuSection(bridge.get(), menu, IDC_HISTORY_MENU_VISITED, 1);
+ ClearMenuSection(menu, HistoryMenuBridge::kMostVisited);
EXPECT_EQ(1, [menu numberOfItems]);
- EXPECT_EQ(@"HEADER", [[menu itemWithTag:IDC_HISTORY_MENU_VISITED] title]);
+ EXPECT_TRUE([@"HEADER" isEqualToString:
+ [[menu itemWithTag:HistoryMenuBridge::kMostVisited] title]]);
}
// Test that AddItemToMenu() properly adds HistoryItem objects as menus.
TEST_F(HistoryMenuBridgeTest, AddItemToMenu) {
- scoped_ptr<HistoryMenuBridge> bridge(
- new HistoryMenuBridge(browser_test_helper_.profile()));
- EXPECT_TRUE(bridge.get());
-
NSMenu* menu = [[[NSMenu alloc] initWithTitle:@"history foo"] autorelease];
const string16 short_url = ASCIIToUTF16("http://foo/");
@@ -159,11 +178,13 @@ TEST_F(HistoryMenuBridgeTest, AddItemToMenu) {
"or.be.reasonably-displayed-in-a-menu"
"without.looking-ridiculous.com/"); // 140 chars total
- scoped_ptr<HistoryMenuBridge::HistoryItem> item1(CreateItem(short_url));
- AddItemToBridgeMenu(bridge.get(), item1.get(), menu, 100, 0);
+ // HistoryItems are owned by the HistoryMenuBridge when AddItemToBridgeMenu()
+ // is called, which places them into the |menu_item_map_|, which owns them.
+ HistoryMenuBridge::HistoryItem* item1 = CreateItem(short_url);
+ AddItemToBridgeMenu(item1, menu, 100, 0);
- scoped_ptr<HistoryMenuBridge::HistoryItem> item2(CreateItem(long_url));
- AddItemToBridgeMenu(bridge.get(), item2.get(), menu, 101, 1);
+ HistoryMenuBridge::HistoryItem* item2 = CreateItem(long_url);
+ AddItemToBridgeMenu(item2, menu, 101, 1);
EXPECT_EQ(2, [menu numberOfItems]);
@@ -188,22 +209,142 @@ TEST_F(HistoryMenuBridgeTest, AddItemToMenu) {
EXPECT_GE([[[menu itemAtIndex:1] toolTip] length], (2*long_url.length()-5));
}
+// Test that the menu is created for a set of simple tabs.
+TEST_F(HistoryMenuBridgeTest, RecentlyClosedTabs) {
+ scoped_refptr<MockTRS> trs(new MockTRS(browser_test_helper_.profile()));
+ MockTRS::Entries entries;
+
+ MockTRS::Tab tab1 = CreateSessionTab(GURL("http://google.com"),
+ ASCIIToUTF16("Google"));
+ tab1.id = 24;
+ entries.push_back(&tab1);
+
+ MockTRS::Tab tab2 = CreateSessionTab(GURL("http://apple.com"),
+ ASCIIToUTF16("Apple"));
+ tab2.id = 42;
+ entries.push_back(&tab2);
+
+ using ::testing::ReturnRef;
+ EXPECT_CALL(*trs.get(), entries()).WillOnce(ReturnRef(entries));
+
+ bridge_->TabRestoreServiceChanged(trs.get());
+
+ NSMenu* menu = bridge_->HistoryMenu();
+ ASSERT_EQ(2U, [[menu itemArray] count]);
+
+ NSMenuItem* item1 = [menu itemAtIndex:0];
+ MockBridge::HistoryItem* hist1 = bridge_->HistoryItemForMenuItem(item1);
+ EXPECT_TRUE(hist1);
+ EXPECT_EQ(24, hist1->session_id);
+ EXPECT_TRUE([@"Google" isEqualToString:[item1 title]]);
+
+ NSMenuItem* item2 = [menu itemAtIndex:1];
+ MockBridge::HistoryItem* hist2 = bridge_->HistoryItemForMenuItem(item2);
+ EXPECT_TRUE(hist2);
+ EXPECT_EQ(42, hist2->session_id);
+ EXPECT_TRUE([@"Apple" isEqualToString:[item2 title]]);
+}
+
+// Test that the menu is created for a mix of windows and tabs.
+TEST_F(HistoryMenuBridgeTest, RecentlyClosedTabsAndWindows) {
+ scoped_refptr<MockTRS> trs(new MockTRS(browser_test_helper_.profile()));
+ MockTRS::Entries entries;
+
+ MockTRS::Tab tab1 = CreateSessionTab(GURL("http://google.com"),
+ ASCIIToUTF16("Google"));
+ tab1.id = 24;
+ entries.push_back(&tab1);
+
+ MockTRS::Window win1;
+ win1.id = 30;
+ win1.tabs.push_back(
+ CreateSessionTab(GURL("http://foo.com"), ASCIIToUTF16("foo")));
+ win1.tabs[0].id = 31;
+ win1.tabs.push_back(
+ CreateSessionTab(GURL("http://bar.com"), ASCIIToUTF16("bar")));
+ win1.tabs[1].id = 32;
+ entries.push_back(&win1);
+
+ MockTRS::Tab tab2 = CreateSessionTab(GURL("http://apple.com"),
+ ASCIIToUTF16("Apple"));
+ tab2.id = 42;
+ entries.push_back(&tab2);
+
+ MockTRS::Window win2;
+ win2.id = 50;
+ win2.tabs.push_back(
+ CreateSessionTab(GURL("http://magic.com"), ASCIIToUTF16("magic")));
+ win2.tabs[0].id = 51;
+ win2.tabs.push_back(
+ CreateSessionTab(GURL("http://goats.com"), ASCIIToUTF16("goats")));
+ win2.tabs[1].id = 52;
+ win2.tabs.push_back(
+ CreateSessionTab(GURL("http://teleporter.com"),
+ ASCIIToUTF16("teleporter")));
+ win2.tabs[1].id = 53;
+ entries.push_back(&win2);
+
+ using ::testing::ReturnRef;
+ EXPECT_CALL(*trs.get(), entries()).WillOnce(ReturnRef(entries));
+
+ bridge_->TabRestoreServiceChanged(trs.get());
+
+ NSMenu* menu = bridge_->HistoryMenu();
+ ASSERT_EQ(4U, [[menu itemArray] count]);
+
+ NSMenuItem* item1 = [menu itemAtIndex:0];
+ MockBridge::HistoryItem* hist1 = bridge_->HistoryItemForMenuItem(item1);
+ EXPECT_TRUE(hist1);
+ EXPECT_EQ(24, hist1->session_id);
+ EXPECT_TRUE([@"Google" isEqualToString:[item1 title]]);
+
+ NSMenuItem* item2 = [menu itemAtIndex:1];
+ MockBridge::HistoryItem* hist2 = bridge_->HistoryItemForMenuItem(item2);
+ EXPECT_TRUE(hist2);
+ EXPECT_EQ(30, hist2->session_id);
+ EXPECT_EQ(2U, hist2->tabs.size());
+ // Do not test menu item title because it is localized.
+ NSMenu* submenu1 = [item2 submenu];
+ EXPECT_EQ(4U, [[submenu1 itemArray] count]);
+ // Do not test Restore All Tabs because it is localiced.
+ EXPECT_TRUE([[submenu1 itemAtIndex:1] isSeparatorItem]);
+ EXPECT_TRUE([@"foo" isEqualToString:[[submenu1 itemAtIndex:2] title]]);
+ EXPECT_TRUE([@"bar" isEqualToString:[[submenu1 itemAtIndex:3] title]]);
+
+ NSMenuItem* item3 = [menu itemAtIndex:2];
+ MockBridge::HistoryItem* hist3 = bridge_->HistoryItemForMenuItem(item3);
+ EXPECT_TRUE(hist3);
+ EXPECT_EQ(42, hist3->session_id);
+ EXPECT_TRUE([@"Apple" isEqualToString:[item3 title]]);
+
+ NSMenuItem* item4 = [menu itemAtIndex:3];
+ MockBridge::HistoryItem* hist4 = bridge_->HistoryItemForMenuItem(item4);
+ EXPECT_TRUE(hist4);
+ EXPECT_EQ(50, hist4->session_id);
+ EXPECT_EQ(3U, hist4->tabs.size());
+ // Do not test menu item title because it is localized.
+ NSMenu* submenu2 = [item4 submenu];
+ EXPECT_EQ(5U, [[submenu2 itemArray] count]);
+ // Do not test Restore All Tabs because it is localiced.
+ EXPECT_TRUE([[submenu2 itemAtIndex:1] isSeparatorItem]);
+ EXPECT_TRUE([@"magic" isEqualToString:[[submenu2 itemAtIndex:2] title]]);
+ EXPECT_TRUE([@"goats" isEqualToString:[[submenu2 itemAtIndex:3] title]]);
+ EXPECT_TRUE([@"teleporter" isEqualToString:[[submenu2 itemAtIndex:4] title]]);
+}
+
// Tests that we properly request an icon from the FaviconService.
TEST_F(HistoryMenuBridgeTest, GetFaviconForHistoryItem) {
- TestingProfile* profile = browser_test_helper_.profile();
- HistoryMenuBridge bridge(profile);
-
// Create a fake item.
HistoryMenuBridge::HistoryItem item;
item.title = ASCIIToUTF16("Title");
item.url = GURL("http://google.com");
// Request the icon.
- GetFaviconForHistoryItem(&bridge, &item);
+ GetFaviconForHistoryItem(&item);
// Make sure that there is ClientData for the request.
std::vector<HistoryMenuBridge::HistoryItem*> data;
- favicon_consumer(&bridge).GetAllClientData(&data);
+ favicon_consumer().GetAllClientData(&data);
ASSERT_EQ(data.size(), 1U);
EXPECT_EQ(&item, data[0]);
@@ -213,9 +354,6 @@ TEST_F(HistoryMenuBridgeTest, GetFaviconForHistoryItem) {
}
TEST_F(HistoryMenuBridgeTest, GotFaviconData) {
- TestingProfile* profile = browser_test_helper_.profile();
- HistoryMenuBridge bridge(profile);
-
// Create a dummy bitmap.
SkBitmap bitmap;
bitmap.setConfig(SkBitmap::kARGB_8888_Config, 25, 25);
@@ -233,13 +371,15 @@ TEST_F(HistoryMenuBridgeTest, GotFaviconData) {
HistoryMenuBridge::HistoryItem item;
scoped_nsobject<NSMenuItem> menu_item([[NSMenuItem alloc] init]);
item.menu_item = menu_item.get();
- GetFaviconForHistoryItem(&bridge, &item);
+ GetFaviconForHistoryItem(&item);
// Pretend to be called back.
- GotFaviconData(&bridge, item.icon_handle, true, bytes, false, GURL());
+ GotFaviconData(item.icon_handle, true, bytes, false, GURL());
// Make sure the callback works.
EXPECT_EQ(false, item.icon_requested);
EXPECT_TRUE(item.icon.get());
EXPECT_TRUE([item.menu_item image]);
}
+
+} // namespace
diff --git a/chrome/browser/cocoa/history_menu_cocoa_controller.h b/chrome/browser/cocoa/history_menu_cocoa_controller.h
index beb5576..efe7645 100644
--- a/chrome/browser/cocoa/history_menu_cocoa_controller.h
+++ b/chrome/browser/cocoa/history_menu_cocoa_controller.h
@@ -25,7 +25,6 @@
@end // HistoryMenuCocoaController
@interface HistoryMenuCocoaController (ExposedForUnitTests)
-- (const HistoryMenuBridge::HistoryItem*)itemForTag:(NSInteger)tag;
- (void)openURLForItem:(const HistoryMenuBridge::HistoryItem*)node;
@end // HistoryMenuCocoaController (ExposedForUnitTests)
diff --git a/chrome/browser/cocoa/history_menu_cocoa_controller.mm b/chrome/browser/cocoa/history_menu_cocoa_controller.mm
index a0672b3..8e3f672 100644
--- a/chrome/browser/cocoa/history_menu_cocoa_controller.mm
+++ b/chrome/browser/cocoa/history_menu_cocoa_controller.mm
@@ -11,6 +11,7 @@
#include "chrome/browser/cocoa/event_utils.h"
#include "chrome/browser/history/history.h"
#include "chrome/browser/history/history_types.h"
+#include "chrome/browser/profile.h"
#include "chrome/browser/tab_contents/tab_contents.h"
#include "webkit/glue/window_open_disposition.h"
@@ -29,34 +30,22 @@
Browser* browser = Browser::GetOrCreateTabbedBrowser(bridge_->profile());
WindowOpenDisposition disposition =
event_utils::WindowOpenDispositionFromNSEvent([NSApp currentEvent]);
- browser->OpenURL(node->url, GURL(), disposition,
- PageTransition::AUTO_BOOKMARK);
-}
-- (const HistoryMenuBridge::HistoryItem*)itemForTag:(NSInteger)tag {
- const ScopedVector<HistoryMenuBridge::HistoryItem>* results = NULL;
- NSInteger tag_base = 0;
- if (tag > IDC_HISTORY_MENU_VISITED && tag < IDC_HISTORY_MENU_CLOSED) {
- results = bridge_->visited_results();
- tag_base = IDC_HISTORY_MENU_VISITED;
- } else if (tag > IDC_HISTORY_MENU_CLOSED) {
- results = bridge_->closed_results();
- tag_base = IDC_HISTORY_MENU_CLOSED;
+ // If this item can be restored using TabRestoreService, do so. Otherwise,
+ // just load the URL.
+ TabRestoreService* service = bridge_->profile()->GetTabRestoreService();
+ if (node->session_id && service) {
+ service->RestoreEntryById(browser, node->session_id, false);
} else {
- NOTREACHED();
+ DCHECK(node->url.is_valid());
+ browser->OpenURL(node->url, GURL(), disposition,
+ PageTransition::AUTO_BOOKMARK);
}
-
- DCHECK(tag > tag_base);
- size_t index = tag - tag_base - 1;
- if (index >= results->size())
- return NULL;
- return (*results)[index];
}
- (IBAction)openHistoryMenuItem:(id)sender {
- NSInteger tag = [sender tag];
- const HistoryMenuBridge::HistoryItem* item = [self itemForTag:tag];
- DCHECK(item->url.is_valid());
+ const HistoryMenuBridge::HistoryItem* item =
+ bridge_->HistoryItemForMenuItem(sender);
[self openURLForItem:item];
}
diff --git a/chrome/browser/cocoa/history_menu_cocoa_controller_unittest.mm b/chrome/browser/cocoa/history_menu_cocoa_controller_unittest.mm
index 9f687f4..a57e12b 100644
--- a/chrome/browser/cocoa/history_menu_cocoa_controller_unittest.mm
+++ b/chrome/browser/cocoa/history_menu_cocoa_controller_unittest.mm
@@ -2,11 +2,14 @@
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
-#include "base/scoped_vector.h"
+#include "base/scoped_nsobject.h"
+#include "base/scoped_ptr.h"
#include "base/string_util.h"
#include "base/sys_string_conversions.h"
#include "chrome/app/chrome_dll_resource.h"
#include "chrome/browser/browser.h"
+#include "chrome/browser/cocoa/browser_test_helper.h"
+#include "chrome/browser/cocoa/cocoa_test_helper.h"
#include "chrome/browser/cocoa/history_menu_bridge.h"
#include "chrome/browser/cocoa/history_menu_cocoa_controller.h"
#include "testing/gtest/include/gtest/gtest.h"
@@ -14,13 +17,12 @@
@interface FakeHistoryMenuController : HistoryMenuCocoaController {
@public
BOOL opened_[2];
- ScopedVector<HistoryMenuBridge::HistoryItem> items_;
}
@end
@implementation FakeHistoryMenuController
-- (id)init {
+- (id)initTest {
if ((self = [super init])) {
opened_[0] = NO;
opened_[1] = NO;
@@ -28,40 +30,62 @@
return self;
}
-- (HistoryMenuBridge::HistoryItem*)itemForTag:(NSInteger)tag {
- HistoryMenuBridge::HistoryItem* item = new HistoryMenuBridge::HistoryItem();
- if (tag == 0) {
- item->title = ASCIIToUTF16("uno");
+- (void)openURLForItem:(HistoryMenuBridge::HistoryItem*)item {
+ opened_[item->session_id] = YES;
+}
+
+@end // FakeHistoryMenuController
+
+class HistoryMenuCocoaControllerTest : public CocoaTest {
+ public:
+
+ virtual void SetUp() {
+ CocoaTest::SetUp();
+ bridge_.reset(new HistoryMenuBridge(browser_test_helper_.profile()));
+ bridge_->controller_.reset(
+ [[FakeHistoryMenuController alloc] initWithBridge:bridge_.get()]);
+ [controller() initTest];
+ }
+
+ void CreateItems(NSMenu* menu) {
+ HistoryMenuBridge::HistoryItem* item = new HistoryMenuBridge::HistoryItem();
item->url = GURL("http://google.com");
- } else if (tag == 1) {
- item->title = ASCIIToUTF16("duo");
+ item->session_id = 0;
+ bridge_->AddItemToMenu(item, menu, HistoryMenuBridge::kMostVisited, 0);
+
+ item = new HistoryMenuBridge::HistoryItem();
item->url = GURL("http://apple.com");
- } else {
- NOTREACHED();
+ item->session_id = 1;
+ bridge_->AddItemToMenu(item, menu, HistoryMenuBridge::kMostVisited, 1);
}
- // We push the item into a scoped vector that will delete it on destruction.
- items_.push_back(item);
- return item;
-}
-- (void)openURLForItem:(HistoryMenuBridge::HistoryItem*)item {
- std::string url = item->url.possibly_invalid_spec();
- if (url.find("http://google.com") != std::string::npos)
- opened_[0] = YES;
- if (url.find("http://apple.com") != std::string::npos)
- opened_[1] = YES;
-}
+ std::map<NSMenuItem*, HistoryMenuBridge::HistoryItem*>& menu_item_map() {
+ return bridge_->menu_item_map_;
+ }
-@end // FakeHistoryMenuController
+ FakeHistoryMenuController* controller() {
+ return static_cast<FakeHistoryMenuController*>(bridge_->controller_.get());
+ }
+
+ private:
+ BrowserTestHelper browser_test_helper_;
+ scoped_ptr<HistoryMenuBridge> bridge_;
+};
+
+TEST_F(HistoryMenuCocoaControllerTest, OpenURLForItem) {
+
+ scoped_nsobject<NSMenu> menu([[NSMenu alloc] initWithTitle:@"History"]);
+ CreateItems(menu.get());
+
+ std::map<NSMenuItem*, HistoryMenuBridge::HistoryItem*>& items =
+ menu_item_map();
+ std::map<NSMenuItem*, HistoryMenuBridge::HistoryItem*>::iterator it =
+ items.begin();
-TEST(HistoryMenuCocoaControllerTest, OpenURLForItem) {
- FakeHistoryMenuController* c = [[FakeHistoryMenuController alloc] init];
- NSMenuItem* item = [[[NSMenuItem alloc] init] autorelease];
- for (int i = 0; i < 2; ++i) {
- [item setTag:i];
- ASSERT_EQ(c->opened_[i], NO);
- [c openHistoryMenuItem:item];
- ASSERT_EQ(c->opened_[i], YES);
+ for ( ; it != items.end(); ++it) {
+ HistoryMenuBridge::HistoryItem* item = it->second;
+ EXPECT_FALSE(controller()->opened_[item->session_id]);
+ [controller() openHistoryMenuItem:it->first];
+ EXPECT_TRUE(controller()->opened_[item->session_id]);
}
- [c release];
}
diff --git a/chrome/browser/sessions/tab_restore_service.h b/chrome/browser/sessions/tab_restore_service.h
index 229557f..9e81722 100644
--- a/chrome/browser/sessions/tab_restore_service.h
+++ b/chrome/browser/sessions/tab_restore_service.h
@@ -147,7 +147,7 @@ class TabRestoreService : public BaseSessionService {
// Returns the entries, ordered with most recently closed entries at the
// front.
- const Entries& entries() const { return entries_; }
+ virtual const Entries& entries() const { return entries_; }
// Restores the most recently closed entry. Does nothing if there are no
// entries to restore. If the most recently restored entry is a tab, it is
@@ -172,6 +172,8 @@ class TabRestoreService : public BaseSessionService {
protected:
virtual void Save();
+ virtual ~TabRestoreService();
+
private:
// Used to indicate what has loaded.
enum LoadState {
@@ -191,8 +193,6 @@ class TabRestoreService : public BaseSessionService {
LOADED_LAST_SESSION = 1 << 4
};
- virtual ~TabRestoreService();
-
// Populates the tab's navigations from the NavigationController, and its
// browser_id and tabstrip_index from the browser.
void PopulateTab(Tab* tab,