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/path_service.h"
8#include "base/strings/string16.h"
9#include "base/strings/string_util.h"
10#include "base/strings/utf_string_conversions.h"
11#include "base/time/time.h"
12#include "chrome/browser/favicon/favicon_service_factory.h"
13#include "chrome/browser/history/history_service.h"
14#include "chrome/browser/history/history_service_factory.h"
15#include "chrome/browser/profiles/profile_manager.h"
16#include "chrome/browser/ui/browser.h"
17#include "chrome/browser/ui/browser_tabstrip.h"
18#include "chrome/browser/ui/tabs/tab_strip_model.h"
19#include "chrome/common/url_constants.h"
20#include "chrome/test/base/chrome_render_view_host_test_harness.h"
21#include "chrome/test/base/test_browser_window.h"
22#include "chrome/test/base/testing_profile.h"
23#include "content/public/browser/navigation_controller.h"
24#include "content/public/browser/navigation_entry.h"
25#include "content/public/browser/web_contents.h"
26#include "content/public/test/web_contents_tester.h"
27#include "testing/gtest/include/gtest/gtest.h"
28#include "third_party/skia/include/core/SkBitmap.h"
29#include "ui/gfx/codec/png_codec.h"
30
31using base::ASCIIToUTF16;
32using content::WebContentsTester;
33
34namespace {
35
36// Creates a bitmap of the specified color.
37SkBitmap CreateBitmap(SkColor color) {
38  SkBitmap bitmap;
39  bitmap.allocN32Pixels(16, 16);
40  bitmap.eraseColor(color);
41  return bitmap;
42}
43
44class FaviconDelegate : public ui::MenuModelDelegate {
45 public:
46  FaviconDelegate() : was_called_(false) {}
47
48  virtual void OnIconChanged(int model_index) OVERRIDE {
49    was_called_ = true;
50    base::MessageLoop::current()->Quit();
51  }
52
53  bool was_called() const { return was_called_; }
54
55 private:
56  bool was_called_;
57
58  DISALLOW_COPY_AND_ASSIGN(FaviconDelegate);
59};
60
61}  // namespace
62
63class BackFwdMenuModelTest : public ChromeRenderViewHostTestHarness {
64 public:
65  void ValidateModel(BackForwardMenuModel* model, int history_items,
66                     int chapter_stops) {
67    int h = std::min(BackForwardMenuModel::kMaxHistoryItems, history_items);
68    int c = std::min(BackForwardMenuModel::kMaxChapterStops, chapter_stops);
69    EXPECT_EQ(h, model->GetHistoryItemCount());
70    EXPECT_EQ(c, model->GetChapterStopCount(h));
71    if (h > 0)
72      h += 2;  // Separator and View History link.
73    if (c > 0)
74      ++c;
75    EXPECT_EQ(h + c, model->GetItemCount());
76  }
77
78  void LoadURLAndUpdateState(const char* url, const char* title) {
79    NavigateAndCommit(GURL(url));
80    controller().GetLastCommittedEntry()->SetTitle(base::UTF8ToUTF16(title));
81  }
82
83  // Navigate back or forward the given amount and commits the entry (which
84  // will be pending after we ask to navigate there).
85  void NavigateToOffset(int offset) {
86    controller().GoToOffset(offset);
87    WebContentsTester::For(web_contents())->CommitPendingNavigation();
88  }
89
90  // Same as NavigateToOffset but goes to an absolute index.
91  void NavigateToIndex(int index) {
92    controller().GoToIndex(index);
93    WebContentsTester::For(web_contents())->CommitPendingNavigation();
94  }
95
96  // Goes back/forward and commits the load.
97  void GoBack() {
98    controller().GoBack();
99    WebContentsTester::For(web_contents())->CommitPendingNavigation();
100  }
101  void GoForward() {
102    controller().GoForward();
103    WebContentsTester::For(web_contents())->CommitPendingNavigation();
104  }
105};
106
107TEST_F(BackFwdMenuModelTest, BasicCase) {
108  scoped_ptr<BackForwardMenuModel> back_model(new BackForwardMenuModel(
109      NULL, BackForwardMenuModel::BACKWARD_MENU));
110  back_model->set_test_web_contents(web_contents());
111
112  scoped_ptr<BackForwardMenuModel> forward_model(new BackForwardMenuModel(
113      NULL, BackForwardMenuModel::FORWARD_MENU));
114  forward_model->set_test_web_contents(web_contents());
115
116  EXPECT_EQ(0, back_model->GetItemCount());
117  EXPECT_EQ(0, forward_model->GetItemCount());
118  EXPECT_FALSE(back_model->ItemHasCommand(1));
119
120  // Seed the controller with a few URLs
121  LoadURLAndUpdateState("http://www.a.com/1", "A1");
122  LoadURLAndUpdateState("http://www.a.com/2", "A2");
123  LoadURLAndUpdateState("http://www.a.com/3", "A3");
124  LoadURLAndUpdateState("http://www.b.com/1", "B1");
125  LoadURLAndUpdateState("http://www.b.com/2", "B2");
126  LoadURLAndUpdateState("http://www.c.com/1", "C1");
127  LoadURLAndUpdateState("http://www.c.com/2", "C2");
128  LoadURLAndUpdateState("http://www.c.com/3", "C3");
129
130  // There're two more items here: a separator and a "Show Full History".
131  EXPECT_EQ(9, back_model->GetItemCount());
132  EXPECT_EQ(0, forward_model->GetItemCount());
133  EXPECT_EQ(ASCIIToUTF16("C2"), back_model->GetLabelAt(0));
134  EXPECT_EQ(ASCIIToUTF16("A1"), back_model->GetLabelAt(6));
135  EXPECT_EQ(back_model->GetShowFullHistoryLabel(),
136            back_model->GetLabelAt(8));
137
138  EXPECT_TRUE(back_model->ItemHasCommand(0));
139  EXPECT_TRUE(back_model->ItemHasCommand(6));
140  EXPECT_TRUE(back_model->IsSeparator(7));
141  EXPECT_TRUE(back_model->ItemHasCommand(8));
142  EXPECT_FALSE(back_model->ItemHasCommand(9));
143  EXPECT_FALSE(back_model->ItemHasCommand(9));
144
145  NavigateToOffset(-7);
146
147  EXPECT_EQ(0, back_model->GetItemCount());
148  EXPECT_EQ(9, forward_model->GetItemCount());
149  EXPECT_EQ(ASCIIToUTF16("A2"), forward_model->GetLabelAt(0));
150  EXPECT_EQ(ASCIIToUTF16("C3"), forward_model->GetLabelAt(6));
151  EXPECT_EQ(forward_model->GetShowFullHistoryLabel(),
152            forward_model->GetLabelAt(8));
153
154  EXPECT_TRUE(forward_model->ItemHasCommand(0));
155  EXPECT_TRUE(forward_model->ItemHasCommand(6));
156  EXPECT_TRUE(forward_model->IsSeparator(7));
157  EXPECT_TRUE(forward_model->ItemHasCommand(8));
158  EXPECT_FALSE(forward_model->ItemHasCommand(7));
159  EXPECT_FALSE(forward_model->ItemHasCommand(9));
160
161  NavigateToOffset(4);
162
163  EXPECT_EQ(6, back_model->GetItemCount());
164  EXPECT_EQ(5, forward_model->GetItemCount());
165  EXPECT_EQ(ASCIIToUTF16("B1"), back_model->GetLabelAt(0));
166  EXPECT_EQ(ASCIIToUTF16("A1"), back_model->GetLabelAt(3));
167  EXPECT_EQ(back_model->GetShowFullHistoryLabel(),
168            back_model->GetLabelAt(5));
169  EXPECT_EQ(ASCIIToUTF16("C1"), forward_model->GetLabelAt(0));
170  EXPECT_EQ(ASCIIToUTF16("C3"), forward_model->GetLabelAt(2));
171  EXPECT_EQ(forward_model->GetShowFullHistoryLabel(),
172            forward_model->GetLabelAt(4));
173}
174
175TEST_F(BackFwdMenuModelTest, MaxItemsTest) {
176  scoped_ptr<BackForwardMenuModel> back_model(new BackForwardMenuModel(
177      NULL, BackForwardMenuModel::BACKWARD_MENU));
178  back_model->set_test_web_contents(web_contents());
179
180  scoped_ptr<BackForwardMenuModel> forward_model(new BackForwardMenuModel(
181      NULL, BackForwardMenuModel::FORWARD_MENU));
182  forward_model->set_test_web_contents(web_contents());
183
184  // Seed the controller with 32 URLs
185  LoadURLAndUpdateState("http://www.a.com/1", "A1");
186  LoadURLAndUpdateState("http://www.a.com/2", "A2");
187  LoadURLAndUpdateState("http://www.a.com/3", "A3");
188  LoadURLAndUpdateState("http://www.b.com/1", "B1");
189  LoadURLAndUpdateState("http://www.b.com/2", "B2");
190  LoadURLAndUpdateState("http://www.b.com/3", "B3");
191  LoadURLAndUpdateState("http://www.c.com/1", "C1");
192  LoadURLAndUpdateState("http://www.c.com/2", "C2");
193  LoadURLAndUpdateState("http://www.c.com/3", "C3");
194  LoadURLAndUpdateState("http://www.d.com/1", "D1");
195  LoadURLAndUpdateState("http://www.d.com/2", "D2");
196  LoadURLAndUpdateState("http://www.d.com/3", "D3");
197  LoadURLAndUpdateState("http://www.e.com/1", "E1");
198  LoadURLAndUpdateState("http://www.e.com/2", "E2");
199  LoadURLAndUpdateState("http://www.e.com/3", "E3");
200  LoadURLAndUpdateState("http://www.f.com/1", "F1");
201  LoadURLAndUpdateState("http://www.f.com/2", "F2");
202  LoadURLAndUpdateState("http://www.f.com/3", "F3");
203  LoadURLAndUpdateState("http://www.g.com/1", "G1");
204  LoadURLAndUpdateState("http://www.g.com/2", "G2");
205  LoadURLAndUpdateState("http://www.g.com/3", "G3");
206  LoadURLAndUpdateState("http://www.h.com/1", "H1");
207  LoadURLAndUpdateState("http://www.h.com/2", "H2");
208  LoadURLAndUpdateState("http://www.h.com/3", "H3");
209  LoadURLAndUpdateState("http://www.i.com/1", "I1");
210  LoadURLAndUpdateState("http://www.i.com/2", "I2");
211  LoadURLAndUpdateState("http://www.i.com/3", "I3");
212  LoadURLAndUpdateState("http://www.j.com/1", "J1");
213  LoadURLAndUpdateState("http://www.j.com/2", "J2");
214  LoadURLAndUpdateState("http://www.j.com/3", "J3");
215  LoadURLAndUpdateState("http://www.k.com/1", "K1");
216  LoadURLAndUpdateState("http://www.k.com/2", "K2");
217
218  // Also there're two more for a separator and a "Show Full History".
219  int chapter_stop_offset = 6;
220  EXPECT_EQ(BackForwardMenuModel::kMaxHistoryItems + 2 + chapter_stop_offset,
221            back_model->GetItemCount());
222  EXPECT_EQ(0, forward_model->GetItemCount());
223  EXPECT_EQ(ASCIIToUTF16("K1"), back_model->GetLabelAt(0));
224  EXPECT_EQ(back_model->GetShowFullHistoryLabel(),
225      back_model->GetLabelAt(BackForwardMenuModel::kMaxHistoryItems + 1 +
226                               chapter_stop_offset));
227
228  // Test for out of bounds (beyond Show Full History).
229  EXPECT_FALSE(back_model->ItemHasCommand(
230      BackForwardMenuModel::kMaxHistoryItems + chapter_stop_offset + 2));
231
232  EXPECT_TRUE(back_model->ItemHasCommand(
233              BackForwardMenuModel::kMaxHistoryItems - 1));
234  EXPECT_TRUE(back_model->IsSeparator(
235              BackForwardMenuModel::kMaxHistoryItems));
236
237  NavigateToIndex(0);
238
239  EXPECT_EQ(BackForwardMenuModel::kMaxHistoryItems + 2 + chapter_stop_offset,
240            forward_model->GetItemCount());
241  EXPECT_EQ(0, back_model->GetItemCount());
242  EXPECT_EQ(ASCIIToUTF16("A2"), forward_model->GetLabelAt(0));
243  EXPECT_EQ(forward_model->GetShowFullHistoryLabel(),
244      forward_model->GetLabelAt(BackForwardMenuModel::kMaxHistoryItems + 1 +
245                                    chapter_stop_offset));
246
247  // Out of bounds
248  EXPECT_FALSE(forward_model->ItemHasCommand(
249      BackForwardMenuModel::kMaxHistoryItems + 2 + chapter_stop_offset));
250
251  EXPECT_TRUE(forward_model->ItemHasCommand(
252      BackForwardMenuModel::kMaxHistoryItems - 1));
253  EXPECT_TRUE(forward_model->IsSeparator(
254      BackForwardMenuModel::kMaxHistoryItems));
255}
256
257TEST_F(BackFwdMenuModelTest, ChapterStops) {
258  scoped_ptr<BackForwardMenuModel> back_model(new BackForwardMenuModel(
259    NULL, BackForwardMenuModel::BACKWARD_MENU));
260  back_model->set_test_web_contents(web_contents());
261
262  scoped_ptr<BackForwardMenuModel> forward_model(new BackForwardMenuModel(
263      NULL, BackForwardMenuModel::FORWARD_MENU));
264  forward_model->set_test_web_contents(web_contents());
265
266  // Seed the controller with 32 URLs.
267  int i = 0;
268  LoadURLAndUpdateState("http://www.a.com/1", "A1");
269  ValidateModel(back_model.get(), i++, 0);
270  LoadURLAndUpdateState("http://www.a.com/2", "A2");
271  ValidateModel(back_model.get(), i++, 0);
272  LoadURLAndUpdateState("http://www.a.com/3", "A3");
273  ValidateModel(back_model.get(), i++, 0);
274  LoadURLAndUpdateState("http://www.b.com/1", "B1");
275  ValidateModel(back_model.get(), i++, 0);
276  LoadURLAndUpdateState("http://www.b.com/2", "B2");
277  ValidateModel(back_model.get(), i++, 0);
278  // i = 5
279  LoadURLAndUpdateState("http://www.b.com/3", "B3");
280  ValidateModel(back_model.get(), i++, 0);
281  LoadURLAndUpdateState("http://www.c.com/1", "C1");
282  ValidateModel(back_model.get(), i++, 0);
283  LoadURLAndUpdateState("http://www.c.com/2", "C2");
284  ValidateModel(back_model.get(), i++, 0);
285  LoadURLAndUpdateState("http://www.c.com/3", "C3");
286  ValidateModel(back_model.get(), i++, 0);
287  LoadURLAndUpdateState("http://www.d.com/1", "D1");
288  ValidateModel(back_model.get(), i++, 0);
289  // i = 10
290  LoadURLAndUpdateState("http://www.d.com/2", "D2");
291  ValidateModel(back_model.get(), i++, 0);
292  LoadURLAndUpdateState("http://www.d.com/3", "D3");
293  ValidateModel(back_model.get(), i++, 0);
294  LoadURLAndUpdateState("http://www.e.com/1", "E1");
295  ValidateModel(back_model.get(), i++, 0);
296  LoadURLAndUpdateState("http://www.e.com/2", "E2");
297  ValidateModel(back_model.get(), i++, 0);
298  LoadURLAndUpdateState("http://www.e.com/3", "E3");
299  ValidateModel(back_model.get(), i++, 0);
300  // i = 15
301  LoadURLAndUpdateState("http://www.f.com/1", "F1");
302  ValidateModel(back_model.get(), i++, 1);
303  LoadURLAndUpdateState("http://www.f.com/2", "F2");
304  ValidateModel(back_model.get(), i++, 1);
305  LoadURLAndUpdateState("http://www.f.com/3", "F3");
306  ValidateModel(back_model.get(), i++, 1);
307  LoadURLAndUpdateState("http://www.g.com/1", "G1");
308  ValidateModel(back_model.get(), i++, 2);
309  LoadURLAndUpdateState("http://www.g.com/2", "G2");
310  ValidateModel(back_model.get(), i++, 2);
311  // i = 20
312  LoadURLAndUpdateState("http://www.g.com/3", "G3");
313  ValidateModel(back_model.get(), i++, 2);
314  LoadURLAndUpdateState("http://www.h.com/1", "H1");
315  ValidateModel(back_model.get(), i++, 3);
316  LoadURLAndUpdateState("http://www.h.com/2", "H2");
317  ValidateModel(back_model.get(), i++, 3);
318  LoadURLAndUpdateState("http://www.h.com/3", "H3");
319  ValidateModel(back_model.get(), i++, 3);
320  LoadURLAndUpdateState("http://www.i.com/1", "I1");
321  ValidateModel(back_model.get(), i++, 4);
322  // i = 25
323  LoadURLAndUpdateState("http://www.i.com/2", "I2");
324  ValidateModel(back_model.get(), i++, 4);
325  LoadURLAndUpdateState("http://www.i.com/3", "I3");
326  ValidateModel(back_model.get(), i++, 4);
327  LoadURLAndUpdateState("http://www.j.com/1", "J1");
328  ValidateModel(back_model.get(), i++, 5);
329  LoadURLAndUpdateState("http://www.j.com/2", "J2");
330  ValidateModel(back_model.get(), i++, 5);
331  LoadURLAndUpdateState("http://www.j.com/3", "J3");
332  ValidateModel(back_model.get(), i++, 5);
333  // i = 30
334  LoadURLAndUpdateState("http://www.k.com/1", "K1");
335  ValidateModel(back_model.get(), i++, 6);
336  LoadURLAndUpdateState("http://www.k.com/2", "K2");
337  ValidateModel(back_model.get(), i++, 6);
338  // i = 32
339  LoadURLAndUpdateState("http://www.k.com/3", "K3");
340  ValidateModel(back_model.get(), i++, 6);
341
342  // A chapter stop is defined as the last page the user
343  // browsed to within the same domain.
344
345  // Check to see if the chapter stops have the right labels.
346  int index = BackForwardMenuModel::kMaxHistoryItems;
347  // Empty string indicates item is a separator.
348  EXPECT_EQ(base::string16(), back_model->GetLabelAt(index++));
349  EXPECT_EQ(ASCIIToUTF16("F3"), back_model->GetLabelAt(index++));
350  EXPECT_EQ(ASCIIToUTF16("E3"), back_model->GetLabelAt(index++));
351  EXPECT_EQ(ASCIIToUTF16("D3"), back_model->GetLabelAt(index++));
352  EXPECT_EQ(ASCIIToUTF16("C3"), back_model->GetLabelAt(index++));
353  // The menu should only show a maximum of 5 chapter stops.
354  EXPECT_EQ(ASCIIToUTF16("B3"), back_model->GetLabelAt(index));
355  // Empty string indicates item is a separator.
356  EXPECT_EQ(base::string16(), back_model->GetLabelAt(index + 1));
357  EXPECT_EQ(back_model->GetShowFullHistoryLabel(),
358            back_model->GetLabelAt(index + 2));
359
360  // If we go back two we should still see the same chapter stop at the end.
361  GoBack();
362  EXPECT_EQ(ASCIIToUTF16("B3"), back_model->GetLabelAt(index));
363  GoBack();
364  EXPECT_EQ(ASCIIToUTF16("B3"), back_model->GetLabelAt(index));
365  // But if we go back again, it should change.
366  GoBack();
367  EXPECT_EQ(ASCIIToUTF16("A3"), back_model->GetLabelAt(index));
368  GoBack();
369  EXPECT_EQ(ASCIIToUTF16("A3"), back_model->GetLabelAt(index));
370  GoBack();
371  EXPECT_EQ(ASCIIToUTF16("A3"), back_model->GetLabelAt(index));
372  GoBack();
373  // It is now a separator.
374  EXPECT_EQ(base::string16(), back_model->GetLabelAt(index));
375  // Undo our position change.
376  NavigateToOffset(6);
377
378  // Go back enough to make sure no chapter stops should appear.
379  NavigateToOffset(-BackForwardMenuModel::kMaxHistoryItems);
380  ValidateModel(forward_model.get(), BackForwardMenuModel::kMaxHistoryItems, 0);
381  // Go forward (still no chapter stop)
382  GoForward();
383  ValidateModel(forward_model.get(),
384                BackForwardMenuModel::kMaxHistoryItems - 1, 0);
385  // Go back two (one chapter stop should show up)
386  GoBack();
387  GoBack();
388  ValidateModel(forward_model.get(),
389                BackForwardMenuModel::kMaxHistoryItems, 1);
390
391  // Go to beginning.
392  NavigateToIndex(0);
393
394  // Check to see if the chapter stops have the right labels.
395  index = BackForwardMenuModel::kMaxHistoryItems;
396  // Empty string indicates item is a separator.
397  EXPECT_EQ(base::string16(), forward_model->GetLabelAt(index++));
398  EXPECT_EQ(ASCIIToUTF16("E3"), forward_model->GetLabelAt(index++));
399  EXPECT_EQ(ASCIIToUTF16("F3"), forward_model->GetLabelAt(index++));
400  EXPECT_EQ(ASCIIToUTF16("G3"), forward_model->GetLabelAt(index++));
401  EXPECT_EQ(ASCIIToUTF16("H3"), forward_model->GetLabelAt(index++));
402  // The menu should only show a maximum of 5 chapter stops.
403  EXPECT_EQ(ASCIIToUTF16("I3"), forward_model->GetLabelAt(index));
404  // Empty string indicates item is a separator.
405  EXPECT_EQ(base::string16(), forward_model->GetLabelAt(index + 1));
406  EXPECT_EQ(forward_model->GetShowFullHistoryLabel(),
407      forward_model->GetLabelAt(index + 2));
408
409  // If we advance one we should still see the same chapter stop at the end.
410  GoForward();
411  EXPECT_EQ(ASCIIToUTF16("I3"), forward_model->GetLabelAt(index));
412  // But if we advance one again, it should change.
413  GoForward();
414  EXPECT_EQ(ASCIIToUTF16("J3"), forward_model->GetLabelAt(index));
415  GoForward();
416  EXPECT_EQ(ASCIIToUTF16("J3"), forward_model->GetLabelAt(index));
417  GoForward();
418  EXPECT_EQ(ASCIIToUTF16("J3"), forward_model->GetLabelAt(index));
419  GoForward();
420  EXPECT_EQ(ASCIIToUTF16("K3"), forward_model->GetLabelAt(index));
421
422  // Now test the boundary cases by using the chapter stop function directly.
423  // Out of bounds, first too far right (incrementing), then too far left.
424  EXPECT_EQ(-1, back_model->GetIndexOfNextChapterStop(33, false));
425  EXPECT_EQ(-1, back_model->GetIndexOfNextChapterStop(-1, true));
426  // Test being at end and going right, then at beginning going left.
427  EXPECT_EQ(-1, back_model->GetIndexOfNextChapterStop(32, true));
428  EXPECT_EQ(-1, back_model->GetIndexOfNextChapterStop(0, false));
429  // Test success: beginning going right and end going left.
430  EXPECT_EQ(2,  back_model->GetIndexOfNextChapterStop(0, true));
431  EXPECT_EQ(29, back_model->GetIndexOfNextChapterStop(32, false));
432  // Now see when the chapter stops begin to show up.
433  EXPECT_EQ(-1, back_model->GetIndexOfNextChapterStop(1, false));
434  EXPECT_EQ(-1, back_model->GetIndexOfNextChapterStop(2, false));
435  EXPECT_EQ(2,  back_model->GetIndexOfNextChapterStop(3, false));
436  // Now see when the chapter stops end.
437  EXPECT_EQ(32, back_model->GetIndexOfNextChapterStop(30, true));
438  EXPECT_EQ(32, back_model->GetIndexOfNextChapterStop(31, true));
439  EXPECT_EQ(-1, back_model->GetIndexOfNextChapterStop(32, true));
440
441  // Bug found during review (two different sites, but first wasn't considered
442  // a chapter-stop).
443  // Go to A1;
444  NavigateToIndex(0);
445  LoadURLAndUpdateState("http://www.b.com/1", "B1");
446  EXPECT_EQ(0, back_model->GetIndexOfNextChapterStop(1, false));
447  EXPECT_EQ(1, back_model->GetIndexOfNextChapterStop(0, true));
448
449  // Now see if it counts 'www.x.com' and 'mail.x.com' as same domain, which
450  // it should.
451  // Go to A1.
452  NavigateToIndex(0);
453  LoadURLAndUpdateState("http://mail.a.com/2", "A2-mai");
454  LoadURLAndUpdateState("http://www.b.com/1", "B1");
455  LoadURLAndUpdateState("http://mail.b.com/2", "B2-mai");
456  LoadURLAndUpdateState("http://new.site.com", "new");
457  EXPECT_EQ(1, back_model->GetIndexOfNextChapterStop(0, true));
458  EXPECT_EQ(3, back_model->GetIndexOfNextChapterStop(1, true));
459  EXPECT_EQ(3, back_model->GetIndexOfNextChapterStop(2, true));
460  EXPECT_EQ(4, back_model->GetIndexOfNextChapterStop(3, true));
461  // And try backwards as well.
462  EXPECT_EQ(3, back_model->GetIndexOfNextChapterStop(4, false));
463  EXPECT_EQ(1, back_model->GetIndexOfNextChapterStop(3, false));
464  EXPECT_EQ(1, back_model->GetIndexOfNextChapterStop(2, false));
465  EXPECT_EQ(-1, back_model->GetIndexOfNextChapterStop(1, false));
466}
467
468TEST_F(BackFwdMenuModelTest, EscapeLabel) {
469  scoped_ptr<BackForwardMenuModel> back_model(new BackForwardMenuModel(
470      NULL, BackForwardMenuModel::BACKWARD_MENU));
471  back_model->set_test_web_contents(web_contents());
472
473  EXPECT_EQ(0, back_model->GetItemCount());
474  EXPECT_FALSE(back_model->ItemHasCommand(1));
475
476  LoadURLAndUpdateState("http://www.a.com/1", "A B");
477  LoadURLAndUpdateState("http://www.a.com/1", "A & B");
478  LoadURLAndUpdateState("http://www.a.com/2", "A && B");
479  LoadURLAndUpdateState("http://www.a.com/2", "A &&& B");
480  LoadURLAndUpdateState("http://www.a.com/3", "");
481
482  EXPECT_EQ(6, back_model->GetItemCount());
483
484  // On Mac ui::MenuModel::GetLabelAt should return unescaped strings.
485#if defined(OS_MACOSX)
486  EXPECT_EQ(ASCIIToUTF16("A B"), back_model->GetLabelAt(3));
487  EXPECT_EQ(ASCIIToUTF16("A & B"), back_model->GetLabelAt(2));
488  EXPECT_EQ(ASCIIToUTF16("A && B"), back_model->GetLabelAt(1));
489  EXPECT_EQ(ASCIIToUTF16("A &&& B"), back_model->GetLabelAt(0));
490#else
491  EXPECT_EQ(ASCIIToUTF16("A B"), back_model->GetLabelAt(3));
492  EXPECT_EQ(ASCIIToUTF16("A && B"), back_model->GetLabelAt(2));
493  EXPECT_EQ(ASCIIToUTF16("A &&&& B"), back_model->GetLabelAt(1));
494  EXPECT_EQ(ASCIIToUTF16("A &&&&&& B"), back_model->GetLabelAt(0));
495#endif // defined(OS_MACOSX)
496}
497
498// Test asynchronous loading of favicon from history service.
499TEST_F(BackFwdMenuModelTest, FaviconLoadTest) {
500  ASSERT_TRUE(profile()->CreateHistoryService(true, false));
501  profile()->CreateFaviconService();
502  Browser::CreateParams native_params(profile(), chrome::GetActiveDesktop());
503  scoped_ptr<Browser> browser(
504      chrome::CreateBrowserWithTestWindowForParams(&native_params));
505  FaviconDelegate favicon_delegate;
506
507  BackForwardMenuModel back_model(
508      browser.get(), BackForwardMenuModel::BACKWARD_MENU);
509  back_model.set_test_web_contents(controller().GetWebContents());
510  back_model.SetMenuModelDelegate(&favicon_delegate);
511
512  SkBitmap new_icon_bitmap(CreateBitmap(SK_ColorRED));
513
514  GURL url1 = GURL("http://www.a.com/1");
515  GURL url2 = GURL("http://www.a.com/2");
516  GURL url1_favicon("http://www.a.com/1/favicon.ico");
517
518  NavigateAndCommit(url1);
519  // Navigate to a new URL so that url1 will be in the BackForwardMenuModel.
520  NavigateAndCommit(url2);
521
522  // Set the desired favicon for url1.
523  HistoryServiceFactory::GetForProfile(
524      profile(), Profile::EXPLICIT_ACCESS)->AddPage(
525          url1, base::Time::Now(), history::SOURCE_BROWSED);
526  FaviconServiceFactory::GetForProfile(profile(), Profile::EXPLICIT_ACCESS)
527      ->SetFavicons(url1,
528                    url1_favicon,
529                    favicon_base::FAVICON,
530                    gfx::Image::CreateFrom1xBitmap(new_icon_bitmap));
531
532  // Will return the current icon (default) but start an anync call
533  // to retrieve the favicon from the favicon service.
534  gfx::Image default_icon;
535  back_model.GetIconAt(0, &default_icon);
536
537  // Make the favicon service run GetFavIconForURL,
538  // FaviconDelegate.OnIconChanged will be called.
539  base::MessageLoop::current()->Run();
540
541  // Verify that the callback executed.
542  EXPECT_TRUE(favicon_delegate.was_called());
543
544  // Verify the bitmaps match.
545  gfx::Image valid_icon;
546  // This time we will get the new favicon returned.
547  back_model.GetIconAt(0, &valid_icon);
548
549  SkBitmap default_icon_bitmap = *default_icon.ToSkBitmap();
550  SkBitmap valid_icon_bitmap = *valid_icon.ToSkBitmap();
551
552  SkAutoLockPixels a(new_icon_bitmap);
553  SkAutoLockPixels b(valid_icon_bitmap);
554  SkAutoLockPixels c(default_icon_bitmap);
555  // Verify we did not get the default favicon.
556  EXPECT_NE(0, memcmp(default_icon_bitmap.getPixels(),
557                      valid_icon_bitmap.getPixels(),
558                      default_icon_bitmap.getSize()));
559  // Verify we did get the expected favicon.
560  EXPECT_EQ(0, memcmp(new_icon_bitmap.getPixels(),
561                      valid_icon_bitmap.getPixels(),
562                      new_icon_bitmap.getSize()));
563
564  // Make sure the browser deconstructor doesn't have problems.
565  browser->tab_strip_model()->CloseAllTabs();
566  // This is required to prevent the message loop from hanging.
567  profile()->DestroyHistoryService();
568}
569