Revert of Add button to add new FSP services to Files app. (patchset #8 id:140001...
[chromium-blink-merge.git] / chrome / browser / ui / cocoa / history_menu_bridge.mm
blob560d64b763f69a7a1b0046e2b7da2cfce525b3d7
1 // Copyright (c) 2012 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.
5 #include "chrome/browser/ui/cocoa/history_menu_bridge.h"
7 #include "base/bind.h"
8 #include "base/strings/string_number_conversions.h"
9 #include "base/strings/string_util.h"
10 #include "base/strings/sys_string_conversions.h"
11 #include "base/strings/utf_string_conversions.h"
12 #include "chrome/app/chrome_command_ids.h"  // IDC_HISTORY_MENU
13 #include "chrome/browser/favicon/favicon_service_factory.h"
14 #include "chrome/browser/history/history_service_factory.h"
15 #include "chrome/browser/profiles/profile.h"
16 #include "chrome/browser/sessions/tab_restore_service_factory.h"
17 #import "chrome/browser/ui/cocoa/history_menu_cocoa_controller.h"
18 #include "chrome/grit/generated_resources.h"
19 #include "grit/theme_resources.h"
20 #include "ui/base/l10n/l10n_util.h"
21 #include "ui/base/resource/resource_bundle.h"
22 #include "ui/gfx/image/image.h"
23 #include "ui/gfx/text_elider.h"
24 #include "ui/resources/grit/ui_resources.h"
26 namespace {
28 // Maximum number of pixels to use for a menu item title.
29 const float kTitlePixelWidth = 400;
31 // Number of days to consider when getting the number of visited items.
32 const int kVisitedScope = 90;
34 // The number of visisted results to get.
35 const int kVisitedCount = 15;
37 // The number of recently closed items to get.
38 const unsigned int kRecentlyClosedCount = 10;
40 }  // namespace
42 HistoryMenuBridge::HistoryItem::HistoryItem()
43     : icon_requested(false),
44       icon_task_id(base::CancelableTaskTracker::kBadTaskId),
45       menu_item(nil),
46       session_id(0) {}
48 HistoryMenuBridge::HistoryItem::HistoryItem(const HistoryItem& copy)
49     : title(copy.title),
50       url(copy.url),
51       icon_requested(false),
52       icon_task_id(base::CancelableTaskTracker::kBadTaskId),
53       menu_item(nil),
54       session_id(copy.session_id) {}
56 HistoryMenuBridge::HistoryItem::~HistoryItem() {
59 HistoryMenuBridge::HistoryMenuBridge(Profile* profile)
60     : controller_([[HistoryMenuCocoaController alloc] initWithBridge:this]),
61       profile_(profile),
62       history_service_(NULL),
63       tab_restore_service_(NULL),
64       create_in_progress_(false),
65       need_recreate_(false),
66       history_service_observer_(this) {
67   // If we don't have a profile, do not bother initializing our data sources.
68   // This shouldn't happen except in unit tests.
69   if (profile_) {
70     // Check to see if the history service is ready. Because it loads async, it
71     // may not be ready when the Bridge is created. If this happens, register
72     // for a notification that tells us the HistoryService is ready.
73     history::HistoryService* hs = HistoryServiceFactory::GetForProfile(
74         profile_, ServiceAccessType::EXPLICIT_ACCESS);
75     if (hs) {
76       history_service_observer_.Add(hs);
77       if (hs->BackendLoaded()) {
78         history_service_ = hs;
79         Init();
80       }
81     }
83     tab_restore_service_ = TabRestoreServiceFactory::GetForProfile(profile_);
84     if (tab_restore_service_) {
85       tab_restore_service_->AddObserver(this);
86       // If the tab entries are already loaded, invoke the observer method to
87       // build the "Recently Closed" section. Otherwise it will be when the
88       // backend loads.
89       if (!tab_restore_service_->IsLoaded())
90         tab_restore_service_->LoadTabsFromLastSession();
91       else
92         TabRestoreServiceChanged(tab_restore_service_);
93     }
94   }
96   ResourceBundle& rb = ResourceBundle::GetSharedInstance();
97   default_favicon_.reset(
98       rb.GetNativeImageNamed(IDR_DEFAULT_FAVICON).CopyNSImage());
100   // Set the static icons in the menu.
101   NSMenuItem* item = [HistoryMenu() itemWithTag:IDC_SHOW_HISTORY];
102   [item setImage:rb.GetNativeImageNamed(IDR_HISTORY_FAVICON).ToNSImage()];
106 // Note that all requests sent to either the history service or the favicon
107 // service will be automatically cancelled by their respective Consumers, so
108 // task cancellation is not done manually here in the dtor.
109 HistoryMenuBridge::~HistoryMenuBridge() {
110   // Unregister ourselves as observers and notifications.
111   DCHECK(profile_);
113   if (tab_restore_service_)
114     tab_restore_service_->RemoveObserver(this);
116   // Since the map owns the HistoryItems, delete anything that still exists.
117   std::map<NSMenuItem*, HistoryItem*>::iterator it = menu_item_map_.begin();
118   while (it != menu_item_map_.end()) {
119     HistoryItem* item  = it->second;
120     menu_item_map_.erase(it++);
121     delete item;
122   }
125 void HistoryMenuBridge::TabRestoreServiceChanged(TabRestoreService* service) {
126   const TabRestoreService::Entries& entries = service->entries();
128   // Clear the history menu before rebuilding.
129   NSMenu* menu = HistoryMenu();
130   ClearMenuSection(menu, kRecentlyClosed);
132   // Index for the next menu item.
133   NSInteger index = [menu indexOfItemWithTag:kRecentlyClosedTitle] + 1;
134   NSUInteger added_count = 0;
136   for (TabRestoreService::Entries::const_iterator it = entries.begin();
137        it != entries.end() && added_count < kRecentlyClosedCount; ++it) {
138     TabRestoreService::Entry* entry = *it;
140     // If this is a window, create a submenu for all of its tabs.
141     if (entry->type == TabRestoreService::WINDOW) {
142       TabRestoreService::Window* entry_win = (TabRestoreService::Window*)entry;
143       std::vector<TabRestoreService::Tab>& tabs = entry_win->tabs;
144       if (!tabs.size())
145         continue;
147       // Create the item for the parent/window. Do not set the title yet because
148       // the actual number of items that are in the menu will not be known until
149       // things like the NTP are filtered out, which is done when the tab items
150       // are actually created.
151       HistoryItem* item = new HistoryItem();
152       item->session_id = entry_win->id;
154       // Create the submenu.
155       base::scoped_nsobject<NSMenu> submenu([[NSMenu alloc] init]);
157       // Create standard items within the window submenu.
158       NSString* restore_title = l10n_util::GetNSString(
159           IDS_HISTORY_CLOSED_RESTORE_WINDOW_MAC);
160       base::scoped_nsobject<NSMenuItem> restore_item(
161           [[NSMenuItem alloc] initWithTitle:restore_title
162                                      action:@selector(openHistoryMenuItem:)
163                               keyEquivalent:@""]);
164       [restore_item setTarget:controller_.get()];
165       // Duplicate the HistoryItem otherwise the different NSMenuItems will
166       // point to the same HistoryItem, which would then be double-freed when
167       // removing the items from the map or in the dtor.
168       HistoryItem* dup_item = new HistoryItem(*item);
169       menu_item_map_.insert(std::make_pair(restore_item.get(), dup_item));
170       [submenu addItem:restore_item.get()];
171       [submenu addItem:[NSMenuItem separatorItem]];
173       // Loop over the window's tabs and add them to the submenu.
174       NSInteger subindex = [[submenu itemArray] count];
175       std::vector<TabRestoreService::Tab>::const_iterator it;
176       for (it = tabs.begin(); it != tabs.end(); ++it) {
177         TabRestoreService::Tab tab = *it;
178         HistoryItem* tab_item = HistoryItemForTab(tab);
179         if (tab_item) {
180           item->tabs.push_back(tab_item);
181           AddItemToMenu(tab_item, submenu.get(), kRecentlyClosed + 1,
182                         subindex++);
183         }
184       }
186       // Now that the number of tabs that has been added is known, set the title
187       // of the parent menu item.
188       if (item->tabs.size() == 1) {
189         item->title = l10n_util::GetStringUTF16(
190             IDS_NEW_TAB_RECENTLY_CLOSED_WINDOW_SINGLE);
191       } else {
192         item->title =l10n_util::GetStringFUTF16(
193             IDS_NEW_TAB_RECENTLY_CLOSED_WINDOW_MULTIPLE,
194                 base::IntToString16(item->tabs.size()));
195       }
197       // Sometimes it is possible for there to not be any subitems for a given
198       // window; if that is the case, do not add the entry to the main menu.
199       if ([[submenu itemArray] count] > 2) {
200         // Create the menu item parent.
201         NSMenuItem* parent_item =
202             AddItemToMenu(item, menu, kRecentlyClosed, index++);
203         [parent_item setSubmenu:submenu.get()];
204         ++added_count;
205       }
206     } else if (entry->type == TabRestoreService::TAB) {
207       TabRestoreService::Tab* tab =
208           static_cast<TabRestoreService::Tab*>(entry);
209       HistoryItem* item = HistoryItemForTab(*tab);
210       if (item) {
211         AddItemToMenu(item, menu, kRecentlyClosed, index++);
212         ++added_count;
213       }
214     }
215   }
218 void HistoryMenuBridge::TabRestoreServiceDestroyed(
219     TabRestoreService* service) {
220   // Intentionally left blank. We hold a weak reference to the service.
223 void HistoryMenuBridge::ResetMenu() {
224   NSMenu* menu = HistoryMenu();
225   ClearMenuSection(menu, kVisited);
226   ClearMenuSection(menu, kRecentlyClosed);
229 void HistoryMenuBridge::BuildMenu() {
230   // If the history service is ready, use it. Otherwise, a Notification will
231   // force an update when it's loaded.
232   if (history_service_)
233     CreateMenu();
236 HistoryMenuBridge::HistoryItem* HistoryMenuBridge::HistoryItemForMenuItem(
237     NSMenuItem* item) {
238   std::map<NSMenuItem*, HistoryItem*>::iterator it = menu_item_map_.find(item);
239   if (it != menu_item_map_.end()) {
240     return it->second;
241   }
242   return NULL;
245 history::HistoryService* HistoryMenuBridge::service() {
246   return history_service_;
249 Profile* HistoryMenuBridge::profile() {
250   return profile_;
253 NSMenu* HistoryMenuBridge::HistoryMenu() {
254   NSMenu* history_menu = [[[NSApp mainMenu] itemWithTag:IDC_HISTORY_MENU]
255                             submenu];
256   return history_menu;
259 void HistoryMenuBridge::ClearMenuSection(NSMenu* menu, NSInteger tag) {
260   for (NSMenuItem* menu_item in [menu itemArray]) {
261     if ([menu_item tag] == tag  && [menu_item target] == controller_.get()) {
262       // This is an item that should be removed, so find the corresponding model
263       // item.
264       HistoryItem* item = HistoryItemForMenuItem(menu_item);
266       // Cancel favicon requests that could hold onto stale pointers. Also
267       // remove the item from the mapping.
268       if (item) {
269         CancelFaviconRequest(item);
270         menu_item_map_.erase(menu_item);
271         delete item;
272       }
274       // If this menu item has a submenu, recurse.
275       if ([menu_item hasSubmenu]) {
276         ClearMenuSection([menu_item submenu], tag + 1);
277       }
279       // Now actually remove the item from the menu.
280       [menu removeItem:menu_item];
281     }
282   }
285 NSMenuItem* HistoryMenuBridge::AddItemToMenu(HistoryItem* item,
286                                              NSMenu* menu,
287                                              NSInteger tag,
288                                              NSInteger index) {
289   // Elide the title of the history item, or use the URL if there is none.
290   std::string url = item->url.possibly_invalid_spec();
291   base::string16 full_title = item->title;
292   base::string16 title =
293       gfx::ElideText(full_title.empty() ? base::UTF8ToUTF16(url) : full_title,
294                      gfx::FontList(gfx::Font([NSFont menuFontOfSize:0])),
295                      kTitlePixelWidth,
296                      gfx::ELIDE_MIDDLE);
298   item->menu_item.reset(
299       [[NSMenuItem alloc] initWithTitle:base::SysUTF16ToNSString(title)
300                                  action:nil
301                           keyEquivalent:@""]);
302   [item->menu_item setTarget:controller_];
303   [item->menu_item setAction:@selector(openHistoryMenuItem:)];
304   [item->menu_item setTag:tag];
305   if (item->icon.get())
306     [item->menu_item setImage:item->icon.get()];
307   else if (!item->tabs.size())
308     [item->menu_item setImage:default_favicon_.get()];
310   // Add a tooltip.
311   NSString* tooltip = [NSString stringWithFormat:@"%@\n%@",
312       base::SysUTF16ToNSString(full_title), base::SysUTF8ToNSString(url)];
313   [item->menu_item setToolTip:tooltip];
315   [menu insertItem:item->menu_item.get() atIndex:index];
316   menu_item_map_.insert(std::make_pair(item->menu_item.get(), item));
318   return item->menu_item.get();
321 void HistoryMenuBridge::Init() {
322   DCHECK(history_service_);
325 void HistoryMenuBridge::CreateMenu() {
326   // If we're currently running CreateMenu(), wait until it finishes.
327   if (create_in_progress_)
328     return;
329   create_in_progress_ = true;
330   need_recreate_ = false;
332   DCHECK(history_service_);
334   history::QueryOptions options;
335   options.max_count = kVisitedCount;
336   options.SetRecentDayRange(kVisitedScope);
338   history_service_->QueryHistory(
339       base::string16(),
340       options,
341       base::Bind(&HistoryMenuBridge::OnVisitedHistoryResults,
342                  base::Unretained(this)),
343       &cancelable_task_tracker_);
346 void HistoryMenuBridge::OnHistoryChanged() {
347   // History has changed, rebuild menu.
348   need_recreate_ = true;
349   CreateMenu();
352 void HistoryMenuBridge::OnVisitedHistoryResults(
353     history::QueryResults* results) {
354   NSMenu* menu = HistoryMenu();
355   ClearMenuSection(menu, kVisited);
356   NSInteger top_item = [menu indexOfItemWithTag:kVisitedTitle] + 1;
358   size_t count = results->size();
359   for (size_t i = 0; i < count; ++i) {
360     const history::URLResult& result = (*results)[i];
362     HistoryItem* item = new HistoryItem;
363     item->title = result.title();
364     item->url = result.url();
366     // Need to explicitly get the favicon for each row.
367     GetFaviconForHistoryItem(item);
369     // This will add |item| to the |menu_item_map_|, which takes ownership.
370     AddItemToMenu(item, HistoryMenu(), kVisited, top_item + i);
371   }
373   // We are already invalid by the time we finished, darn.
374   if (need_recreate_)
375     CreateMenu();
377   create_in_progress_ = false;
380 HistoryMenuBridge::HistoryItem* HistoryMenuBridge::HistoryItemForTab(
381     const TabRestoreService::Tab& entry) {
382   DCHECK(!entry.navigations.empty());
384   const sessions::SerializedNavigationEntry& current_navigation =
385       entry.navigations.at(entry.current_navigation_index);
386   HistoryItem* item = new HistoryItem();
387   item->title = current_navigation.title();
388   item->url = current_navigation.virtual_url();
389   item->session_id = entry.id;
391   // Tab navigations don't come with icons, so we always have to request them.
392   GetFaviconForHistoryItem(item);
394   return item;
397 void HistoryMenuBridge::GetFaviconForHistoryItem(HistoryItem* item) {
398   favicon::FaviconService* service = FaviconServiceFactory::GetForProfile(
399       profile_, ServiceAccessType::EXPLICIT_ACCESS);
400   base::CancelableTaskTracker::TaskId task_id =
401       service->GetFaviconImageForPageURL(
402           item->url,
403           base::Bind(
404               &HistoryMenuBridge::GotFaviconData, base::Unretained(this), item),
405           &cancelable_task_tracker_);
406   item->icon_task_id = task_id;
407   item->icon_requested = true;
410 void HistoryMenuBridge::GotFaviconData(
411     HistoryItem* item,
412     const favicon_base::FaviconImageResult& image_result) {
413   // Since we're going to do Cocoa-y things, make sure this is the main thread.
414   DCHECK([NSThread isMainThread]);
416   DCHECK(item);
417   item->icon_requested = false;
418   item->icon_task_id = base::CancelableTaskTracker::kBadTaskId;
420   NSImage* image = image_result.image.AsNSImage();
421   if (image) {
422     item->icon.reset([image retain]);
423     [item->menu_item setImage:item->icon.get()];
424   }
427 void HistoryMenuBridge::CancelFaviconRequest(HistoryItem* item) {
428   DCHECK(item);
429   if (item->icon_requested) {
430     cancelable_task_tracker_.TryCancel(item->icon_task_id);
431     item->icon_requested = false;
432     item->icon_task_id = base::CancelableTaskTracker::kBadTaskId;
433   }
436 void HistoryMenuBridge::OnURLVisited(history::HistoryService* history_service,
437                                      ui::PageTransition transition,
438                                      const history::URLRow& row,
439                                      const history::RedirectList& redirects,
440                                      base::Time visit_time) {
441   OnHistoryChanged();
444 void HistoryMenuBridge::OnURLsModified(history::HistoryService* history_service,
445                                        const history::URLRows& changed_urls) {
446   OnHistoryChanged();
449 void HistoryMenuBridge::OnURLsDeleted(history::HistoryService* history_service,
450                                       bool all_history,
451                                       bool expired,
452                                       const history::URLRows& deleted_rows,
453                                       const std::set<GURL>& favicon_urls) {
454   OnHistoryChanged();
457 void HistoryMenuBridge::OnHistoryServiceLoaded(
458     history::HistoryService* history_service) {
459   history_service_ = history_service;
460   Init();