Home | History | Annotate | Download | only in tab_contents
      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 
    185 TEST(ThumbnailGeneratorSimpleTest, CalculateBoringScore_Empty) {
    186   SkBitmap bitmap;
    187   EXPECT_DOUBLE_EQ(1.0, ThumbnailGenerator::CalculateBoringScore(&bitmap));
    188 }
    189 
    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 
    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 
    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 
    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 
    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 
    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:
    287   explicit MockTopSites(Profile* profile)
    288       : history::TopSites(profile),
    289         capacity_(1) {
    290   }
    291 
    292   // history::TopSites overrides.
    293   virtual bool IsFull() {
    294     return known_url_map_.size() >= capacity_;
    295   }
    296   virtual bool IsKnownURL(const GURL& url) {
    297     return known_url_map_.find(url.spec()) != known_url_map_.end();
    298   }
    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.
    311   void AddKnownURL(const GURL& url, const ThumbnailScore& score) {
    312     known_url_map_[url.spec()] = score;
    313   }
    314 
    315  private:
    316   virtual ~MockTopSites() {}
    317   size_t capacity_;
    318   std::map<std::string, ThumbnailScore> known_url_map_;
    319 };
    320 
    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