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