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