• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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 #include "build/build_config.h"
6 
7 #include "chrome/browser/ui/toolbar/back_forward_menu_model.h"
8 
9 #include "base/string_number_conversions.h"
10 #include "chrome/browser/metrics/user_metrics.h"
11 #include "chrome/browser/prefs/pref_service.h"
12 #include "chrome/browser/profiles/profile.h"
13 #include "chrome/browser/ui/browser.h"
14 #include "chrome/common/pref_names.h"
15 #include "chrome/common/url_constants.h"
16 #include "content/browser/tab_contents/navigation_controller.h"
17 #include "content/browser/tab_contents/navigation_entry.h"
18 #include "content/browser/tab_contents/tab_contents.h"
19 #include "grit/generated_resources.h"
20 #include "grit/theme_resources.h"
21 #include "net/base/registry_controlled_domain.h"
22 #include "ui/base/l10n/l10n_util.h"
23 #include "ui/base/resource/resource_bundle.h"
24 #include "ui/base/text/text_elider.h"
25 #include "ui/gfx/codec/png_codec.h"
26 
27 const int BackForwardMenuModel::kMaxHistoryItems = 12;
28 const int BackForwardMenuModel::kMaxChapterStops = 5;
29 static const int kMaxWidth = 700;
30 
BackForwardMenuModel(Browser * browser,ModelType model_type)31 BackForwardMenuModel::BackForwardMenuModel(Browser* browser,
32                                            ModelType model_type)
33     : browser_(browser),
34       test_tab_contents_(NULL),
35       model_type_(model_type),
36       menu_model_delegate_(NULL) {
37 }
38 
~BackForwardMenuModel()39 BackForwardMenuModel::~BackForwardMenuModel() {
40 }
41 
HasIcons() const42 bool BackForwardMenuModel::HasIcons() const {
43   return true;
44 }
45 
GetItemCount() const46 int BackForwardMenuModel::GetItemCount() const {
47   int items = GetHistoryItemCount();
48 
49   if (items > 0) {
50     int chapter_stops = 0;
51 
52     // Next, we count ChapterStops, if any.
53     if (items == kMaxHistoryItems)
54       chapter_stops = GetChapterStopCount(items);
55 
56     if (chapter_stops)
57       items += chapter_stops + 1;  // Chapter stops also need a separator.
58 
59     // If the menu is not empty, add two positions in the end
60     // for a separator and a "Show Full History" item.
61     items += 2;
62   }
63 
64   return items;
65 }
66 
GetTypeAt(int index) const67 ui::MenuModel::ItemType BackForwardMenuModel::GetTypeAt(int index) const {
68   return IsSeparator(index) ? TYPE_SEPARATOR : TYPE_COMMAND;
69 }
70 
GetCommandIdAt(int index) const71 int BackForwardMenuModel::GetCommandIdAt(int index) const {
72   return index;
73 }
74 
GetLabelAt(int index) const75 string16 BackForwardMenuModel::GetLabelAt(int index) const {
76   // Return label "Show Full History" for the last item of the menu.
77   if (index == GetItemCount() - 1)
78     return l10n_util::GetStringUTF16(IDS_SHOWFULLHISTORY_LINK);
79 
80   // Return an empty string for a separator.
81   if (IsSeparator(index))
82     return string16();
83 
84   // Return the entry title, escaping any '&' characters and eliding it if it's
85   // super long.
86   NavigationEntry* entry = GetNavigationEntry(index);
87   string16 menu_text(entry->GetTitleForDisplay(
88       GetTabContents()->profile()->GetPrefs()->
89           GetString(prefs::kAcceptLanguages)));
90   menu_text = ui::ElideText(menu_text, gfx::Font(), kMaxWidth, false);
91 
92 #if !defined(OS_MACOSX)
93   for (size_t i = menu_text.find('&'); i != string16::npos;
94        i = menu_text.find('&', i + 2)) {
95     menu_text.insert(i, 1, '&');
96   }
97 #endif
98 
99   return menu_text;
100 }
101 
IsItemDynamicAt(int index) const102 bool BackForwardMenuModel::IsItemDynamicAt(int index) const {
103   // This object is only used for a single showing of a menu.
104   return false;
105 }
106 
GetAcceleratorAt(int index,ui::Accelerator * accelerator) const107 bool BackForwardMenuModel::GetAcceleratorAt(
108     int index,
109     ui::Accelerator* accelerator) const {
110   return false;
111 }
112 
IsItemCheckedAt(int index) const113 bool BackForwardMenuModel::IsItemCheckedAt(int index) const {
114   return false;
115 }
116 
GetGroupIdAt(int index) const117 int BackForwardMenuModel::GetGroupIdAt(int index) const {
118   return false;
119 }
120 
GetIconAt(int index,SkBitmap * icon)121 bool BackForwardMenuModel::GetIconAt(int index, SkBitmap* icon) {
122   if (!ItemHasIcon(index))
123     return false;
124 
125   if (index == GetItemCount() - 1) {
126     *icon = *ResourceBundle::GetSharedInstance().GetBitmapNamed(
127         IDR_HISTORY_FAVICON);
128   } else {
129     NavigationEntry* entry = GetNavigationEntry(index);
130     *icon = entry->favicon().bitmap();
131     if (!entry->favicon().is_valid() && menu_model_delegate()) {
132       FetchFavicon(entry);
133     }
134   }
135 
136   return true;
137 }
138 
GetButtonMenuItemAt(int index) const139 ui::ButtonMenuItemModel* BackForwardMenuModel::GetButtonMenuItemAt(
140     int index) const {
141   return NULL;
142 }
143 
IsEnabledAt(int index) const144 bool BackForwardMenuModel::IsEnabledAt(int index) const {
145   return index < GetItemCount() && !IsSeparator(index);
146 }
147 
GetSubmenuModelAt(int index) const148 ui::MenuModel* BackForwardMenuModel::GetSubmenuModelAt(int index) const {
149   return NULL;
150 }
151 
HighlightChangedTo(int index)152 void BackForwardMenuModel::HighlightChangedTo(int index) {
153 }
154 
ActivatedAt(int index)155 void BackForwardMenuModel::ActivatedAt(int index) {
156   ActivatedAtWithDisposition(index, CURRENT_TAB);
157 }
158 
ActivatedAtWithDisposition(int index,int disposition)159 void BackForwardMenuModel::ActivatedAtWithDisposition(
160       int index, int disposition) {
161   Profile* profile = browser_->profile();
162 
163   DCHECK(!IsSeparator(index));
164 
165   // Execute the command for the last item: "Show Full History".
166   if (index == GetItemCount() - 1) {
167     UserMetrics::RecordComputedAction(BuildActionName("ShowFullHistory", -1),
168                                       profile);
169     browser_->ShowSingletonTab(GURL(chrome::kChromeUIHistoryURL));
170     return;
171   }
172 
173   // Log whether it was a history or chapter click.
174   if (index < GetHistoryItemCount()) {
175     UserMetrics::RecordComputedAction(
176         BuildActionName("HistoryClick", index), profile);
177   } else {
178     UserMetrics::RecordComputedAction(
179         BuildActionName("ChapterClick", index - GetHistoryItemCount() - 1),
180         profile);
181   }
182 
183   int controller_index = MenuIndexToNavEntryIndex(index);
184   if (!browser_->NavigateToIndexWithDisposition(
185           controller_index, static_cast<WindowOpenDisposition>(disposition))) {
186     NOTREACHED();
187   }
188 }
189 
MenuWillShow()190 void BackForwardMenuModel::MenuWillShow() {
191   UserMetrics::RecordComputedAction(BuildActionName("Popup", -1),
192                                     browser_->profile());
193   requested_favicons_.clear();
194   load_consumer_.CancelAllRequests();
195 }
196 
IsSeparator(int index) const197 bool BackForwardMenuModel::IsSeparator(int index) const {
198   int history_items = GetHistoryItemCount();
199   // If the index is past the number of history items + separator,
200   // we then consider if it is a chapter-stop entry.
201   if (index > history_items) {
202     // We either are in ChapterStop area, or at the end of the list (the "Show
203     // Full History" link).
204     int chapter_stops = GetChapterStopCount(history_items);
205     if (chapter_stops == 0)
206       return false;  // We must have reached the "Show Full History" link.
207     // Otherwise, look to see if we have reached the separator for the
208     // chapter-stops. If not, this is a chapter stop.
209     return (index == history_items + 1 + chapter_stops);
210   }
211 
212   // Look to see if we have reached the separator for the history items.
213   return index == history_items;
214 }
215 
SetMenuModelDelegate(ui::MenuModelDelegate * menu_model_delegate)216 void BackForwardMenuModel::SetMenuModelDelegate(
217       ui::MenuModelDelegate* menu_model_delegate) {
218   menu_model_delegate_ = menu_model_delegate;
219 }
220 
FetchFavicon(NavigationEntry * entry)221 void BackForwardMenuModel::FetchFavicon(NavigationEntry* entry) {
222   // If the favicon has already been requested for this menu, don't do
223   // anything.
224   if (requested_favicons_.find(entry->unique_id()) !=
225       requested_favicons_.end()) {
226     return;
227   }
228   requested_favicons_.insert(entry->unique_id());
229   FaviconService* favicon_service =
230       browser_->profile()->GetFaviconService(Profile::EXPLICIT_ACCESS);
231   if (!favicon_service)
232     return;
233   FaviconService::Handle handle = favicon_service->GetFaviconForURL(
234       entry->url(), history::FAVICON, &load_consumer_,
235       NewCallback(this, &BackForwardMenuModel::OnFavIconDataAvailable));
236   load_consumer_.SetClientData(favicon_service, handle, entry->unique_id());
237 }
238 
OnFavIconDataAvailable(FaviconService::Handle handle,history::FaviconData favicon)239 void BackForwardMenuModel::OnFavIconDataAvailable(
240     FaviconService::Handle handle,
241     history::FaviconData favicon) {
242   if (favicon.is_valid()) {
243     int unique_id = load_consumer_.GetClientDataForCurrentRequest();
244     // Find the current model_index for the unique_id.
245     NavigationEntry* entry = NULL;
246     int model_index = -1;
247     for (int i = 0; i < GetItemCount() - 1; i++) {
248       if (IsSeparator(i))
249         continue;
250       if (GetNavigationEntry(i)->unique_id() == unique_id) {
251         model_index = i;
252         entry = GetNavigationEntry(i);
253         break;
254       }
255     }
256 
257     if (!entry)
258       // The NavigationEntry wasn't found, this can happen if the user
259       // navigates to another page and a NavigatationEntry falls out of the
260       // range of kMaxHistoryItems.
261       return;
262 
263     // Now that we have a valid NavigationEntry, decode the favicon and assign
264     // it to the NavigationEntry.
265     SkBitmap fav_icon;
266     if (gfx::PNGCodec::Decode(favicon.image_data->front(),
267                               favicon.image_data->size(),
268                               &fav_icon)) {
269       entry->favicon().set_is_valid(true);
270       entry->favicon().set_url(favicon.icon_url);
271       if (fav_icon.empty())
272         return;
273       entry->favicon().set_bitmap(fav_icon);
274       if (menu_model_delegate()) {
275         menu_model_delegate()->OnIconChanged(model_index);
276       }
277     }
278   }
279 }
280 
GetHistoryItemCount() const281 int BackForwardMenuModel::GetHistoryItemCount() const {
282   TabContents* contents = GetTabContents();
283   int items = 0;
284 
285   if (model_type_ == FORWARD_MENU) {
286     // Only count items from n+1 to end (if n is current entry)
287     items = contents->controller().entry_count() -
288             contents->controller().GetCurrentEntryIndex() - 1;
289   } else {
290     items = contents->controller().GetCurrentEntryIndex();
291   }
292 
293   if (items > kMaxHistoryItems)
294     items = kMaxHistoryItems;
295   else if (items < 0)
296     items = 0;
297 
298   return items;
299 }
300 
GetChapterStopCount(int history_items) const301 int BackForwardMenuModel::GetChapterStopCount(int history_items) const {
302   TabContents* contents = GetTabContents();
303 
304   int chapter_stops = 0;
305   int current_entry = contents->controller().GetCurrentEntryIndex();
306 
307   if (history_items == kMaxHistoryItems) {
308     int chapter_id = current_entry;
309     if (model_type_ == FORWARD_MENU) {
310       chapter_id += history_items;
311     } else {
312       chapter_id -= history_items;
313     }
314 
315     do {
316       chapter_id = GetIndexOfNextChapterStop(chapter_id,
317           model_type_ == FORWARD_MENU);
318       if (chapter_id != -1)
319         ++chapter_stops;
320     } while (chapter_id != -1 && chapter_stops < kMaxChapterStops);
321   }
322 
323   return chapter_stops;
324 }
325 
GetIndexOfNextChapterStop(int start_from,bool forward) const326 int BackForwardMenuModel::GetIndexOfNextChapterStop(int start_from,
327                                                     bool forward) const {
328   TabContents* contents = GetTabContents();
329   NavigationController& controller = contents->controller();
330 
331   int max_count = controller.entry_count();
332   if (start_from < 0 || start_from >= max_count)
333     return -1;  // Out of bounds.
334 
335   if (forward) {
336     if (start_from < max_count - 1) {
337       // We want to advance over the current chapter stop, so we add one.
338       // We don't need to do this when direction is backwards.
339       start_from++;
340     } else {
341       return -1;
342     }
343   }
344 
345   NavigationEntry* start_entry = controller.GetEntryAtIndex(start_from);
346   const GURL& url = start_entry->url();
347 
348   if (!forward) {
349     // When going backwards we return the first entry we find that has a
350     // different domain.
351     for (int i = start_from - 1; i >= 0; --i) {
352       if (!net::RegistryControlledDomainService::SameDomainOrHost(url,
353               controller.GetEntryAtIndex(i)->url()))
354         return i;
355     }
356     // We have reached the beginning without finding a chapter stop.
357     return -1;
358   } else {
359     // When going forwards we return the entry before the entry that has a
360     // different domain.
361     for (int i = start_from + 1; i < max_count; ++i) {
362       if (!net::RegistryControlledDomainService::SameDomainOrHost(url,
363               controller.GetEntryAtIndex(i)->url()))
364         return i - 1;
365     }
366     // Last entry is always considered a chapter stop.
367     return max_count - 1;
368   }
369 }
370 
FindChapterStop(int offset,bool forward,int skip) const371 int BackForwardMenuModel::FindChapterStop(int offset,
372                                           bool forward,
373                                           int skip) const {
374   if (offset < 0 || skip < 0)
375     return -1;
376 
377   if (!forward)
378     offset *= -1;
379 
380   TabContents* contents = GetTabContents();
381   int entry = contents->controller().GetCurrentEntryIndex() + offset;
382   for (int i = 0; i < skip + 1; i++)
383     entry = GetIndexOfNextChapterStop(entry, forward);
384 
385   return entry;
386 }
387 
ItemHasCommand(int index) const388 bool BackForwardMenuModel::ItemHasCommand(int index) const {
389   return index < GetItemCount() && !IsSeparator(index);
390 }
391 
ItemHasIcon(int index) const392 bool BackForwardMenuModel::ItemHasIcon(int index) const {
393   return index < GetItemCount() && !IsSeparator(index);
394 }
395 
GetShowFullHistoryLabel() const396 string16 BackForwardMenuModel::GetShowFullHistoryLabel() const {
397   return l10n_util::GetStringUTF16(IDS_SHOWFULLHISTORY_LINK);
398 }
399 
GetTabContents() const400 TabContents* BackForwardMenuModel::GetTabContents() const {
401   // We use the test tab contents if the unit test has specified it.
402   return test_tab_contents_ ? test_tab_contents_ :
403                               browser_->GetSelectedTabContents();
404 }
405 
MenuIndexToNavEntryIndex(int index) const406 int BackForwardMenuModel::MenuIndexToNavEntryIndex(int index) const {
407   TabContents* contents = GetTabContents();
408   int history_items = GetHistoryItemCount();
409 
410   DCHECK_GE(index, 0);
411 
412   // Convert anything above the History items separator.
413   if (index < history_items) {
414     if (model_type_ == FORWARD_MENU) {
415       index += contents->controller().GetCurrentEntryIndex() + 1;
416     } else {
417       // Back menu is reverse.
418       index = contents->controller().GetCurrentEntryIndex() - (index + 1);
419     }
420     return index;
421   }
422   if (index == history_items)
423     return -1;  // Don't translate the separator for history items.
424 
425   if (index >= history_items + 1 + GetChapterStopCount(history_items))
426     return -1;  // This is beyond the last chapter stop so we abort.
427 
428   // This menu item is a chapter stop located between the two separators.
429   index = FindChapterStop(history_items,
430                           model_type_ == FORWARD_MENU,
431                           index - history_items - 1);
432 
433   return index;
434 }
435 
GetNavigationEntry(int index) const436 NavigationEntry* BackForwardMenuModel::GetNavigationEntry(int index) const {
437   int controller_index = MenuIndexToNavEntryIndex(index);
438   NavigationController& controller = GetTabContents()->controller();
439   if (controller_index >= 0 && controller_index < controller.entry_count())
440     return controller.GetEntryAtIndex(controller_index);
441 
442   NOTREACHED();
443   return NULL;
444 }
445 
BuildActionName(const std::string & action,int index) const446 std::string BackForwardMenuModel::BuildActionName(
447     const std::string& action, int index) const {
448   DCHECK(!action.empty());
449   DCHECK(index >= -1);
450   std::string metric_string;
451   if (model_type_ == FORWARD_MENU)
452     metric_string += "ForwardMenu_";
453   else
454     metric_string += "BackMenu_";
455   metric_string += action;
456   if (index != -1) {
457     // +1 is for historical reasons (indices used to start at 1).
458     metric_string += base::IntToString(index + 1);
459   }
460   return metric_string;
461 }
462