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