• 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 "base/basictypes.h"
6 #include "base/stringprintf.h"
7 #include "chrome/browser/history/top_sites.h"
8 #include "chrome/browser/tab_contents/thumbnail_generator.h"
9 #include "chrome/common/render_messages.h"
10 #include "chrome/test/testing_profile.h"
11 #include "content/browser/renderer_host/backing_store_manager.h"
12 #include "content/browser/renderer_host/mock_render_process_host.h"
13 #include "content/browser/renderer_host/test_render_view_host.h"
14 #include "content/common/notification_service.h"
15 #include "skia/ext/platform_canvas.h"
16 #include "testing/gtest/include/gtest/gtest.h"
17 #include "third_party/skia/include/core/SkColorPriv.h"
18 #include "ui/gfx/canvas_skia.h"
19 #include "ui/gfx/surface/transport_dib.h"
20 
21 static const int kBitmapWidth = 100;
22 static const int kBitmapHeight = 100;
23 
24 // TODO(brettw) enable this when GetThumbnailForBackingStore is implemented
25 // for other platforms in thumbnail_generator.cc
26 // #if defined(OS_WIN)
27 // TODO(brettw) enable this on Windows after we clobber a build to see if the
28 // failures of this on the buildbot can be resolved.
29 #if 0
30 
31 class ThumbnailGeneratorTest : public testing::Test {
32  public:
33   ThumbnailGeneratorTest()
34       : profile_(),
35         process_(new MockRenderProcessHost(&profile_)),
36         widget_(process_, 1),
37         view_(&widget_) {
38     // Paiting will be skipped if there's no view.
39     widget_.set_view(&view_);
40 
41     // Need to send out a create notification for the RWH to get hooked. This is
42     // a little scary in that we don't have a RenderView, but the only listener
43     // will want a RenderWidget, so it works out OK.
44     NotificationService::current()->Notify(
45         NotificationType::RENDER_VIEW_HOST_CREATED_FOR_TAB,
46         Source<RenderViewHostManager>(NULL),
47         Details<RenderViewHost>(reinterpret_cast<RenderViewHost*>(&widget_)));
48 
49     transport_dib_.reset(TransportDIB::Create(kBitmapWidth * kBitmapHeight * 4,
50                                               1));
51 
52     // We don't want to be sensitive to timing.
53     generator_.StartThumbnailing();
54     generator_.set_no_timeout(true);
55   }
56 
57  protected:
58   // Indicates what bitmap should be sent with the paint message. _OTHER will
59   // only be retrned by CheckFirstPixel if the pixel is none of the others.
60   enum TransportType { TRANSPORT_BLACK, TRANSPORT_WHITE, TRANSPORT_OTHER };
61 
62   void SendPaint(TransportType type) {
63     ViewHostMsg_PaintRect_Params params;
64     params.bitmap_rect = gfx::Rect(0, 0, kBitmapWidth, kBitmapHeight);
65     params.view_size = params.bitmap_rect.size();
66     params.flags = 0;
67 
68     scoped_ptr<skia::PlatformCanvas> canvas(
69         transport_dib_->GetPlatformCanvas(kBitmapWidth, kBitmapHeight));
70     switch (type) {
71       case TRANSPORT_BLACK:
72         canvas->getTopPlatformDevice().accessBitmap(true).eraseARGB(
73             0xFF, 0, 0, 0);
74         break;
75       case TRANSPORT_WHITE:
76         canvas->getTopPlatformDevice().accessBitmap(true).eraseARGB(
77             0xFF, 0xFF, 0xFF, 0xFF);
78         break;
79       case TRANSPORT_OTHER:
80       default:
81         NOTREACHED();
82         break;
83     }
84 
85     params.bitmap = transport_dib_->id();
86 
87     ViewHostMsg_PaintRect msg(1, params);
88     widget_.OnMessageReceived(msg);
89   }
90 
91   TransportType ClassifyFirstPixel(const SkBitmap& bitmap) {
92     // Returns the color of the first pixel of the bitmap. The bitmap must be
93     // non-empty.
94     SkAutoLockPixels lock(bitmap);
95     uint32 pixel = *bitmap.getAddr32(0, 0);
96 
97     if (SkGetPackedA32(pixel) != 0xFF)
98       return TRANSPORT_OTHER;  // All values expect an opqaue alpha channel
99 
100     if (SkGetPackedR32(pixel) == 0 &&
101         SkGetPackedG32(pixel) == 0 &&
102         SkGetPackedB32(pixel) == 0)
103       return TRANSPORT_BLACK;
104 
105     if (SkGetPackedR32(pixel) == 0xFF &&
106         SkGetPackedG32(pixel) == 0xFF &&
107         SkGetPackedB32(pixel) == 0xFF)
108       return TRANSPORT_WHITE;
109 
110     EXPECT_TRUE(false) << "Got weird color: " << pixel;
111     return TRANSPORT_OTHER;
112   }
113 
114   MessageLoopForUI message_loop_;
115 
116   TestingProfile profile_;
117 
118   // This will get deleted when the last RHWH associated with it is destroyed.
119   MockRenderProcessHost* process_;
120 
121   RenderWidgetHost widget_;
122   TestRenderWidgetHostView view_;
123   ThumbnailGenerator generator_;
124 
125   scoped_ptr<TransportDIB> transport_dib_;
126 
127  private:
128   // testing::Test implementation.
129   void SetUp() {
130   }
131   void TearDown() {
132   }
133 };
134 
135 TEST_F(ThumbnailGeneratorTest, NoThumbnail) {
136   // This is the case where there is no thumbnail available on the tab and
137   // there is no backing store. There should be no image returned.
138   SkBitmap result = generator_.GetThumbnailForRenderer(&widget_);
139   EXPECT_TRUE(result.isNull());
140 }
141 
142 // Tests basic thumbnail generation when a backing store is discarded.
143 TEST_F(ThumbnailGeneratorTest, DiscardBackingStore) {
144   // First set up a backing store and then discard it.
145   SendPaint(TRANSPORT_BLACK);
146   widget_.WasHidden();
147   ASSERT_TRUE(BackingStoreManager::ExpireBackingStoreForTest(&widget_));
148   ASSERT_FALSE(widget_.GetBackingStore(false, false));
149 
150   // The thumbnail generator should have stashed a thumbnail of the page.
151   SkBitmap result = generator_.GetThumbnailForRenderer(&widget_);
152   ASSERT_FALSE(result.isNull());
153   EXPECT_EQ(TRANSPORT_BLACK, ClassifyFirstPixel(result));
154 }
155 
156 TEST_F(ThumbnailGeneratorTest, QuickShow) {
157   // Set up a hidden widget with a black cached thumbnail and an expired
158   // backing store.
159   SendPaint(TRANSPORT_BLACK);
160   widget_.WasHidden();
161   ASSERT_TRUE(BackingStoreManager::ExpireBackingStoreForTest(&widget_));
162   ASSERT_FALSE(widget_.GetBackingStore(false, false));
163 
164   // Now show the widget and paint white.
165   widget_.WasRestored();
166   SendPaint(TRANSPORT_WHITE);
167 
168   // The black thumbnail should still be cached because it hasn't processed the
169   // timer message yet.
170   SkBitmap result = generator_.GetThumbnailForRenderer(&widget_);
171   ASSERT_FALSE(result.isNull());
172   EXPECT_EQ(TRANSPORT_BLACK, ClassifyFirstPixel(result));
173 
174   // Running the message loop will process the timer, which should expire the
175   // cached thumbnail. Asking again should give us a new one computed from the
176   // backing store.
177   message_loop_.RunAllPending();
178   result = generator_.GetThumbnailForRenderer(&widget_);
179   ASSERT_FALSE(result.isNull());
180   EXPECT_EQ(TRANSPORT_WHITE, ClassifyFirstPixel(result));
181 }
182 
183 #endif
184 
TEST(ThumbnailGeneratorSimpleTest,CalculateBoringScore_Empty)185 TEST(ThumbnailGeneratorSimpleTest, CalculateBoringScore_Empty) {
186   SkBitmap bitmap;
187   EXPECT_DOUBLE_EQ(1.0, ThumbnailGenerator::CalculateBoringScore(&bitmap));
188 }
189 
TEST(ThumbnailGeneratorSimpleTest,CalculateBoringScore_SingleColor)190 TEST(ThumbnailGeneratorSimpleTest, CalculateBoringScore_SingleColor) {
191   const SkColor kBlack = SkColorSetRGB(0, 0, 0);
192   const gfx::Size kSize(20, 10);
193   gfx::CanvasSkia canvas(kSize.width(), kSize.height(), true);
194   // Fill all pixesl in black.
195   canvas.FillRectInt(kBlack, 0, 0, kSize.width(), kSize.height());
196 
197   SkBitmap bitmap = canvas.getTopPlatformDevice().accessBitmap(false);
198   // The thumbnail should deserve the highest boring score.
199   EXPECT_DOUBLE_EQ(1.0, ThumbnailGenerator::CalculateBoringScore(&bitmap));
200 }
201 
TEST(ThumbnailGeneratorSimpleTest,CalculateBoringScore_TwoColors)202 TEST(ThumbnailGeneratorSimpleTest, CalculateBoringScore_TwoColors) {
203   const SkColor kBlack = SkColorSetRGB(0, 0, 0);
204   const SkColor kWhite = SkColorSetRGB(0xFF, 0xFF, 0xFF);
205   const gfx::Size kSize(20, 10);
206 
207   gfx::CanvasSkia canvas(kSize.width(), kSize.height(), true);
208   // Fill all pixesl in black.
209   canvas.FillRectInt(kBlack, 0, 0, kSize.width(), kSize.height());
210   // Fill the left half pixels in white.
211   canvas.FillRectInt(kWhite, 0, 0, kSize.width() / 2, kSize.height());
212 
213   SkBitmap bitmap = canvas.getTopPlatformDevice().accessBitmap(false);
214   ASSERT_EQ(kSize.width(), bitmap.width());
215   ASSERT_EQ(kSize.height(), bitmap.height());
216   // The thumbnail should be less boring because two colors are used.
217   EXPECT_DOUBLE_EQ(0.5, ThumbnailGenerator::CalculateBoringScore(&bitmap));
218 }
219 
TEST(ThumbnailGeneratorSimpleTest,GetClippedBitmap_TallerThanWide)220 TEST(ThumbnailGeneratorSimpleTest, GetClippedBitmap_TallerThanWide) {
221   // The input bitmap is vertically long.
222   gfx::CanvasSkia canvas(40, 90, true);
223   const SkBitmap bitmap = canvas.getTopPlatformDevice().accessBitmap(false);
224 
225   // The desired size is square.
226   ThumbnailGenerator::ClipResult clip_result = ThumbnailGenerator::kNotClipped;
227   SkBitmap clipped_bitmap = ThumbnailGenerator::GetClippedBitmap(
228       bitmap, 10, 10, &clip_result);
229   // The clipped bitmap should be square.
230   EXPECT_EQ(40, clipped_bitmap.width());
231   EXPECT_EQ(40, clipped_bitmap.height());
232   // The input was taller than wide.
233   EXPECT_EQ(ThumbnailGenerator::kTallerThanWide, clip_result);
234 }
235 
TEST(ThumbnailGeneratorSimpleTest,GetClippedBitmap_WiderThanTall)236 TEST(ThumbnailGeneratorSimpleTest, GetClippedBitmap_WiderThanTall) {
237   // The input bitmap is horizontally long.
238   gfx::CanvasSkia canvas(90, 40, true);
239   const SkBitmap bitmap = canvas.getTopPlatformDevice().accessBitmap(false);
240 
241   // The desired size is square.
242   ThumbnailGenerator::ClipResult clip_result = ThumbnailGenerator::kNotClipped;
243   SkBitmap clipped_bitmap = ThumbnailGenerator::GetClippedBitmap(
244       bitmap, 10, 10, &clip_result);
245   // The clipped bitmap should be square.
246   EXPECT_EQ(40, clipped_bitmap.width());
247   EXPECT_EQ(40, clipped_bitmap.height());
248   // The input was wider than tall.
249   EXPECT_EQ(ThumbnailGenerator::kWiderThanTall, clip_result);
250 }
251 
TEST(ThumbnailGeneratorSimpleTest,GetClippedBitmap_NotClipped)252 TEST(ThumbnailGeneratorSimpleTest, GetClippedBitmap_NotClipped) {
253   // The input bitmap is square.
254   gfx::CanvasSkia canvas(40, 40, true);
255   const SkBitmap bitmap = canvas.getTopPlatformDevice().accessBitmap(false);
256 
257   // The desired size is square.
258   ThumbnailGenerator::ClipResult clip_result = ThumbnailGenerator::kNotClipped;
259   SkBitmap clipped_bitmap = ThumbnailGenerator::GetClippedBitmap(
260       bitmap, 10, 10, &clip_result);
261   // The clipped bitmap should be square.
262   EXPECT_EQ(40, clipped_bitmap.width());
263   EXPECT_EQ(40, clipped_bitmap.height());
264   // There was no need to clip.
265   EXPECT_EQ(ThumbnailGenerator::kNotClipped, clip_result);
266 }
267 
TEST(ThumbnailGeneratorSimpleTest,GetClippedBitmap_NonSquareOutput)268 TEST(ThumbnailGeneratorSimpleTest, GetClippedBitmap_NonSquareOutput) {
269   // The input bitmap is square.
270   gfx::CanvasSkia canvas(40, 40, true);
271   const SkBitmap bitmap = canvas.getTopPlatformDevice().accessBitmap(false);
272 
273   // The desired size is horizontally long.
274   ThumbnailGenerator::ClipResult clip_result = ThumbnailGenerator::kNotClipped;
275   SkBitmap clipped_bitmap = ThumbnailGenerator::GetClippedBitmap(
276       bitmap, 20, 10, &clip_result);
277   // The clipped bitmap should have the same aspect ratio of the desired size.
278   EXPECT_EQ(40, clipped_bitmap.width());
279   EXPECT_EQ(20, clipped_bitmap.height());
280   // The input was taller than wide.
281   EXPECT_EQ(ThumbnailGenerator::kTallerThanWide, clip_result);
282 }
283 
284 // A mock version of TopSites, used for testing ShouldUpdateThumbnail().
285 class MockTopSites : public history::TopSites {
286  public:
MockTopSites(Profile * profile)287   explicit MockTopSites(Profile* profile)
288       : history::TopSites(profile),
289         capacity_(1) {
290   }
291 
292   // history::TopSites overrides.
IsFull()293   virtual bool IsFull() {
294     return known_url_map_.size() >= capacity_;
295   }
IsKnownURL(const GURL & url)296   virtual bool IsKnownURL(const GURL& url) {
297     return known_url_map_.find(url.spec()) != known_url_map_.end();
298   }
GetPageThumbnailScore(const GURL & url,ThumbnailScore * score)299   virtual bool GetPageThumbnailScore(const GURL& url, ThumbnailScore* score) {
300     std::map<std::string, ThumbnailScore>::const_iterator iter =
301         known_url_map_.find(url.spec());
302     if (iter == known_url_map_.end()) {
303       return false;
304     } else {
305       *score = iter->second;
306       return true;
307     }
308   }
309 
310   // Adds a known URL with the associated thumbnail score.
AddKnownURL(const GURL & url,const ThumbnailScore & score)311   void AddKnownURL(const GURL& url, const ThumbnailScore& score) {
312     known_url_map_[url.spec()] = score;
313   }
314 
315  private:
~MockTopSites()316   virtual ~MockTopSites() {}
317   size_t capacity_;
318   std::map<std::string, ThumbnailScore> known_url_map_;
319 };
320 
TEST(ThumbnailGeneratorSimpleTest,ShouldUpdateThumbnail)321 TEST(ThumbnailGeneratorSimpleTest, ShouldUpdateThumbnail) {
322   const GURL kGoodURL("http://www.google.com/");
323   const GURL kBadURL("chrome://newtab");
324 
325   // Set up the profile.
326   TestingProfile profile;
327 
328   // Set up the top sites service.
329   ScopedTempDir temp_dir;
330   ASSERT_TRUE(temp_dir.CreateUniqueTempDir());
331   scoped_refptr<MockTopSites> top_sites(new MockTopSites(&profile));
332 
333   // Should be false because it's a bad URL.
334   EXPECT_FALSE(ThumbnailGenerator::ShouldUpdateThumbnail(
335       &profile, top_sites.get(), kBadURL));
336 
337   // Should be true, as it's a good URL.
338   EXPECT_TRUE(ThumbnailGenerator::ShouldUpdateThumbnail(
339       &profile, top_sites.get(), kGoodURL));
340 
341   // Should be false, if it's in the incognito mode.
342   profile.set_incognito(true);
343   EXPECT_FALSE(ThumbnailGenerator::ShouldUpdateThumbnail(
344       &profile, top_sites.get(), kGoodURL));
345 
346   // Should be true again, once turning off the incognito mode.
347   profile.set_incognito(false);
348   EXPECT_TRUE(ThumbnailGenerator::ShouldUpdateThumbnail(
349       &profile, top_sites.get(), kGoodURL));
350 
351   // Add a known URL. This makes the top sites data full.
352   ThumbnailScore bad_score;
353   bad_score.time_at_snapshot = base::Time::UnixEpoch();  // Ancient time stamp.
354   top_sites->AddKnownURL(kGoodURL, bad_score);
355   ASSERT_TRUE(top_sites->IsFull());
356 
357   // Should be false, as the top sites data is full, and the new URL is
358   // not known.
359   const GURL kAnotherGoodURL("http://www.youtube.com/");
360   EXPECT_FALSE(ThumbnailGenerator::ShouldUpdateThumbnail(
361       &profile, top_sites.get(), kAnotherGoodURL));
362 
363   // Should be true, as the existing thumbnail is bad (i.e need a better one).
364   EXPECT_TRUE(ThumbnailGenerator::ShouldUpdateThumbnail(
365       &profile, top_sites.get(), kGoodURL));
366 
367   // Replace the thumbnail score with a really good one.
368   ThumbnailScore good_score;
369   good_score.time_at_snapshot = base::Time::Now();  // Very new.
370   good_score.at_top = true;
371   good_score.good_clipping = true;
372   good_score.boring_score = 0.0;
373   top_sites->AddKnownURL(kGoodURL, good_score);
374 
375   // Should be false, as the existing thumbnail is good enough (i.e. don't
376   // need to replace the existing thumbnail which is new and good).
377   EXPECT_FALSE(ThumbnailGenerator::ShouldUpdateThumbnail(
378       &profile, top_sites.get(), kGoodURL));
379 }
380