1// Copyright (c) 2011 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#ifndef CHROME_BROWSER_UI_COCOA_HISTORY_MENU_BRIDGE_H_
6#define CHROME_BROWSER_UI_COCOA_HISTORY_MENU_BRIDGE_H_
7#pragma once
8
9#import <Cocoa/Cocoa.h>
10#include <map>
11
12#include "base/memory/ref_counted.h"
13#include "base/memory/scoped_nsobject.h"
14#import "chrome/browser/favicon_service.h"
15#include "chrome/browser/history/history.h"
16#include "chrome/browser/sessions/session_id.h"
17#include "chrome/browser/sessions/tab_restore_service.h"
18#include "chrome/browser/sessions/tab_restore_service_observer.h"
19#include "content/browser/cancelable_request.h"
20#include "content/common/notification_observer.h"
21
22class NavigationEntry;
23class NotificationRegistrar;
24class PageUsageData;
25class Profile;
26class TabNavigationEntry;
27class TabRestoreService;
28@class HistoryMenuCocoaController;
29
30namespace {
31
32class HistoryMenuBridgeTest;
33
34}
35
36// C++ bridge for the history menu; one per AppController (means there
37// is only one). This class observes various data sources, namely the
38// HistoryService and the TabRestoreService, and then updates the NSMenu when
39// there is new data.
40//
41// The history menu is broken up into sections: most visisted and recently
42// closed. The overall menu has a tag of IDC_HISTORY_MENU, with the user content
43// items having the local tags defined in the enum below. Items within a section
44// all share the same tag. The structure of the menu is laid out in MainMenu.xib
45// and the generated content is inserted after the Title elements. The recently
46// closed section is special in that those menu items can have submenus to list
47// all the tabs within that closed window. By convention, these submenu items
48// have a tag that's equal to the parent + 1. Tags within the history menu have
49// a range of [400,500) and do not go through CommandDispatch for their target-
50// action mechanism.
51//
52// These menu items do not use firstResponder as their target. Rather, they are
53// hooked directly up to the HistoryMenuCocoaController that then bridges back
54// to this class. These items are created via the AddItemToMenu() helper. Also,
55// unlike the typical ownership model, this bridge owns its controller. The
56// controller is very thin and only exists to interact with Cocoa, but this
57// class does the bulk of the work.
58class HistoryMenuBridge : public NotificationObserver,
59                          public TabRestoreServiceObserver {
60 public:
61  // This is a generalization of the data we store in the history menu because
62  // we pull things from different sources with different data types.
63  struct HistoryItem {
64   public:
65    HistoryItem();
66    // Copy constructor allowed.
67    HistoryItem(const HistoryItem& copy);
68    ~HistoryItem();
69
70    // The title for the menu item.
71    string16 title;
72    // The URL that will be navigated to if the user selects this item.
73    GURL url;
74    // Favicon for the URL.
75    scoped_nsobject<NSImage> icon;
76
77    // If the icon is being requested from the FaviconService, |icon_requested|
78    // will be true and |icon_handle| will be non-NULL. If this is false, then
79    // |icon_handle| will be NULL.
80    bool icon_requested;
81    // The Handle given to us by the FaviconService for the icon fetch request.
82    FaviconService::Handle icon_handle;
83
84    // The pointer to the item after it has been created. Strong; NSMenu also
85    // retains this. During a rebuild flood (if the user closes a lot of tabs
86    // quickly), the NSMenu can release the item before the HistoryItem has
87    // been fully deleted. If this were a weak pointer, it would result in a
88    // zombie.
89    scoped_nsobject<NSMenuItem> menu_item;
90
91    // This ID is unique for a browser session and can be passed to the
92    // TabRestoreService to re-open the closed window or tab that this
93    // references. A non-0 session ID indicates that this is an entry can be
94    // restored that way. Otherwise, the URL will be used to open the item and
95    // this ID will be 0.
96    SessionID::id_type session_id;
97
98    // If the HistoryItem is a window, this will be the vector of tabs. Note
99    // that this is a list of weak references. The |menu_item_map_| is the owner
100    // of all items. If it is not a window, then the entry is a single page and
101    // the vector will be empty.
102    std::vector<HistoryItem*> tabs;
103
104   private:
105    // Copying is explicitly allowed, but assignment is not.
106    void operator=(const HistoryItem&);
107  };
108
109  // These tags are not global view tags and are local to the history menu. The
110  // normal procedure for menu items is to go through CommandDispatch, but since
111  // history menu items are hooked directly up to their target, they do not need
112  // to have the global IDC view tags.
113  enum Tags {
114    kMostVisitedSeparator = 400,  // Separator before most visited section.
115    kMostVisitedTitle = 401,  // Title of the most visited section.
116    kMostVisited = 420,  // Used for all entries in the most visited section.
117    kRecentlyClosedSeparator = 440,  // Item before recently closed section.
118    kRecentlyClosedTitle = 441,  // Title of recently closed section.
119    kRecentlyClosed = 460,  // Used for items in the recently closed section.
120    kShowFullSeparator = 480  // Separator after the recently closed section.
121  };
122
123  explicit HistoryMenuBridge(Profile* profile);
124  virtual ~HistoryMenuBridge();
125
126  // Overriden from NotificationObserver.
127  virtual void Observe(NotificationType type,
128                       const NotificationSource& source,
129                       const NotificationDetails& details);
130
131  // For TabRestoreServiceObserver
132  virtual void TabRestoreServiceChanged(TabRestoreService* service);
133  virtual void TabRestoreServiceDestroyed(TabRestoreService* service);
134
135  // Looks up an NSMenuItem in the |menu_item_map_| and returns the
136  // corresponding HistoryItem.
137  HistoryItem* HistoryItemForMenuItem(NSMenuItem* item);
138
139  // I wish I has a "friend @class" construct. These are used by the HMCC
140  // to access model information when responding to actions.
141  HistoryService* service();
142  Profile* profile();
143
144 protected:
145  // Return the History menu.
146  virtual NSMenu* HistoryMenu();
147
148  // Clear items in the given |menu|. Menu items in the same section are given
149  // the same tag. This will go through the entire history menu, removing all
150  // items with a given tag. Note that this will recurse to submenus, removing
151  // child items from the menu item map. This will only remove items that have
152  // a target hooked up to the |controller_|.
153  void ClearMenuSection(NSMenu* menu, NSInteger tag);
154
155  // Adds a given title and URL to the passed-in menu with a certain tag and
156  // index. This will add |item| and the newly created menu item to the
157  // |menu_item_map_|, which takes ownership. Items are deleted in
158  // ClearMenuSection(). This returns the new menu item that was just added.
159  NSMenuItem* AddItemToMenu(HistoryItem* item,
160                            NSMenu* menu,
161                            NSInteger tag,
162                            NSInteger index);
163
164  // Called by the ctor if |service_| is ready at the time, or by a
165  // notification receiver. Finishes initialization tasks by subscribing for
166  // change notifications and calling CreateMenu().
167  void Init();
168
169  // Does the query for the history information to create the menu.
170  void CreateMenu();
171
172  // Callback method for when HistoryService query results are ready with the
173  // most recently-visited sites.
174  void OnVisitedHistoryResults(CancelableRequestProvider::Handle handle,
175                               std::vector<PageUsageData*>* results);
176
177  // Creates a HistoryItem* for the given tab entry. Caller takes ownership of
178  // the result and must delete it when finished.
179  HistoryItem* HistoryItemForTab(const TabRestoreService::Tab& entry);
180
181  // Helper function that sends an async request to the FaviconService to get
182  // an icon. The callback will update the NSMenuItem directly.
183  void GetFaviconForHistoryItem(HistoryItem* item);
184
185  // Callback for the FaviconService to return favicon image data when we
186  // request it. This decodes the raw data, updates the HistoryItem, and then
187  // sets the image on the menu. Called on the same same thread that
188  // GetFaviconForHistoryItem() was called on (UI thread).
189  void GotFaviconData(FaviconService::Handle handle,
190                      history::FaviconData favicon);
191
192  // Cancels a favicon load request for a given HistoryItem, if one is in
193  // progress.
194  void CancelFaviconRequest(HistoryItem* item);
195
196 private:
197  friend class ::HistoryMenuBridgeTest;
198  friend class HistoryMenuCocoaControllerTest;
199
200  scoped_nsobject<HistoryMenuCocoaController> controller_;  // strong
201
202  Profile* profile_;  // weak
203  HistoryService* history_service_;  // weak
204  TabRestoreService* tab_restore_service_;  // weak
205
206  NotificationRegistrar registrar_;
207  CancelableRequestConsumer cancelable_request_consumer_;
208
209  // Mapping of NSMenuItems to HistoryItems. This owns the HistoryItems until
210  // they are removed and deleted via ClearMenuSection().
211  std::map<NSMenuItem*, HistoryItem*> menu_item_map_;
212
213  // Maps HistoryItems to favicon request Handles.
214  CancelableRequestConsumerTSimple<HistoryItem*> favicon_consumer_;
215
216  // Requests to re-create the menu are coalesced. |create_in_progress_| is true
217  // when either waiting for the history service to return query results, or
218  // when the menu is rebuilding. |need_recreate_| is true whenever a rebuild
219  // has been scheduled but is waiting for the current one to finish.
220  bool create_in_progress_;
221  bool need_recreate_;
222
223  // The default favicon if a HistoryItem does not have one.
224  scoped_nsobject<NSImage> default_favicon_;
225
226  DISALLOW_COPY_AND_ASSIGN(HistoryMenuBridge);
227};
228
229#endif  // CHROME_BROWSER_UI_COCOA_HISTORY_MENU_BRIDGE_H_
230