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