| OLD | NEW |
| (Empty) | |
| 1 // Copyright (c) 2017 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 "components/favicon/core/favicon_driver_impl.h" |
| 6 |
| 7 #include <stddef.h> |
| 8 |
| 9 #include <map> |
| 10 #include <memory> |
| 11 #include <set> |
| 12 #include <utility> |
| 13 #include <vector> |
| 14 |
| 15 #include "base/files/scoped_temp_dir.h" |
| 16 #include "base/message_loop/message_loop.h" |
| 17 #include "base/run_loop.h" |
| 18 #include "base/strings/stringprintf.h" |
| 19 #include "components/favicon/core/favicon_client.h" |
| 20 #include "components/favicon/core/favicon_service_impl.h" |
| 21 #include "components/favicon/core/favicon_url.h" |
| 22 #include "components/favicon/core/test/mock_favicon_service.h" |
| 23 #include "components/history/core/browser/history_database_params.h" |
| 24 #include "components/history/core/browser/history_service.h" |
| 25 #include "components/history/core/test/test_history_database.h" |
| 26 #include "testing/gmock/include/gmock/gmock.h" |
| 27 #include "testing/gtest/include/gtest/gtest.h" |
| 28 #include "third_party/skia/include/core/SkBitmap.h" |
| 29 #include "ui/base/layout.h" |
| 30 #include "ui/gfx/codec/png_codec.h" |
| 31 #include "ui/gfx/color_analysis.h" |
| 32 #include "ui/gfx/favicon_size.h" |
| 33 #include "ui/gfx/image/image.h" |
| 34 |
| 35 namespace favicon { |
| 36 namespace { |
| 37 |
| 38 using favicon_base::FAVICON; |
| 39 using favicon_base::FaviconRawBitmapResult; |
| 40 using favicon_base::TOUCH_ICON; |
| 41 using testing::Contains; |
| 42 using testing::Eq; |
| 43 |
| 44 // |arg| is a FaviconRawBitmapResult. |
| 45 MATCHER_P(HasColor, expected_color, "") { |
| 46 if (arg.bitmap_data == nullptr || arg.bitmap_data->size() == 0) { |
| 47 *result_listener << "expected color but no bitmap data available for URL " |
| 48 << arg.icon_url; |
| 49 return false; |
| 50 } |
| 51 SkColor actual_color = color_utils::CalculateKMeanColorOfPNG(arg.bitmap_data); |
| 52 if (actual_color != expected_color) { |
| 53 *result_listener << "expected color " |
| 54 << base::StringPrintf("%08X", expected_color) |
| 55 << " but actual color is " |
| 56 << base::StringPrintf("%08X", actual_color) << " for URL " |
| 57 << arg.icon_url; |
| 58 return false; |
| 59 } |
| 60 return true; |
| 61 } |
| 62 |
| 63 SkBitmap CreateBitmap(int w, int h, SkColor color) { |
| 64 SkBitmap bmp; |
| 65 bmp.allocN32Pixels(w, h); |
| 66 bmp.eraseColor(color); |
| 67 return bmp; |
| 68 } |
| 69 |
| 70 class FakeImageDownloader { |
| 71 public: |
| 72 struct Bitmap { |
| 73 Bitmap(gfx::Size size, SkColor color) : size(size), color(color) {} |
| 74 |
| 75 gfx::Size size; |
| 76 SkColor color; |
| 77 }; |
| 78 |
| 79 FakeImageDownloader() : num_downloads_(0) {} |
| 80 |
| 81 void SetFakeResponse(const GURL& url, const std::vector<Bitmap>& bitmaps) { |
| 82 fake_responses_[url].first.clear(); |
| 83 fake_responses_[url].second.clear(); |
| 84 for (const Bitmap& bitmap : bitmaps) { |
| 85 fake_responses_[url].second.push_back(bitmap.size); |
| 86 fake_responses_[url].first.push_back(CreateBitmap( |
| 87 bitmap.size.width(), bitmap.size.height(), bitmap.color)); |
| 88 } |
| 89 } |
| 90 |
| 91 int DownloadImage(const GURL& url, |
| 92 int max_image_size, |
| 93 FaviconHandler::Delegate::ImageDownloadCallback callback) { |
| 94 ++num_downloads_; |
| 95 auto it = fake_responses_.find(url); |
| 96 if (it == fake_responses_.end()) { |
| 97 base::ThreadTaskRunnerHandle::Get()->PostTask( |
| 98 FROM_HERE, |
| 99 base::Bind(callback, num_downloads_, 404, url, |
| 100 std::vector<SkBitmap>(), std::vector<gfx::Size>())); |
| 101 } else { |
| 102 base::ThreadTaskRunnerHandle::Get()->PostTask( |
| 103 FROM_HERE, base::Bind(callback, num_downloads_, 200, url, |
| 104 it->second.first, it->second.second)); |
| 105 } |
| 106 return num_downloads_; |
| 107 } |
| 108 |
| 109 int NumDownloads() { return num_downloads_; } |
| 110 |
| 111 private: |
| 112 std::map<GURL, std::pair<std::vector<SkBitmap>, std::vector<gfx::Size>>> |
| 113 fake_responses_; |
| 114 int num_downloads_; |
| 115 }; |
| 116 |
| 117 class TestFaviconDriver : public FaviconDriverImpl { |
| 118 public: |
| 119 TestFaviconDriver(FaviconService* favicon_service, |
| 120 history::HistoryService* history_service, |
| 121 FakeImageDownloader* fake_image_downloader) |
| 122 : FaviconDriverImpl(/*enable_touch_icons=*/true, |
| 123 favicon_service, |
| 124 history_service, |
| 125 /*bookmark_model=*/nullptr), |
| 126 fake_image_downloader_(fake_image_downloader) {} |
| 127 |
| 128 // Test method exercising the two events during a page visit. |
| 129 void TestFetchFaviconForPage(const GURL& page_url, |
| 130 const std::vector<FaviconURL>& candidates) { |
| 131 FetchFavicon(page_url); |
| 132 OnUpdateFaviconURL(page_url, candidates); |
| 133 } |
| 134 |
| 135 // FaviconDriver implementation. |
| 136 gfx::Image GetFavicon() const override { return gfx::Image(); } |
| 137 |
| 138 // FaviconHandler::Delegate implementation. |
| 139 int DownloadImage(const GURL& url, |
| 140 int max_image_size, |
| 141 ImageDownloadCallback callback) override { |
| 142 return fake_image_downloader_->DownloadImage(url, max_image_size, callback); |
| 143 } |
| 144 |
| 145 bool IsOffTheRecord() override { return false; } |
| 146 |
| 147 void OnFaviconUpdated( |
| 148 const GURL& page_url, |
| 149 FaviconDriverObserver::NotificationIconType notification_icon_type, |
| 150 const GURL& icon_url, |
| 151 bool icon_url_changed, |
| 152 const gfx::Image& image) override { |
| 153 received_favicon_updates_.insert(page_url); |
| 154 } |
| 155 |
| 156 const std::set<GURL>& GetReceivedFaviconUpdates() const { |
| 157 return received_favicon_updates_; |
| 158 } |
| 159 |
| 160 private: |
| 161 FakeImageDownloader* fake_image_downloader_; |
| 162 std::set<GURL> received_favicon_updates_; |
| 163 }; |
| 164 |
| 165 // Integration tests that use a test fixture with a real implementation for |
| 166 // FaviconService (with an instance of HistoryService behind). |
| 167 class FaviconDriverImplIntegrationTest : public testing::Test { |
| 168 protected: |
| 169 const std::vector<gfx::Size> kEmptyIconSizes; |
| 170 |
| 171 FaviconDriverImplIntegrationTest() |
| 172 : favicon_service_(/*favicon_client=*/{}, &history_service_), |
| 173 scoped_set_supported_scale_factors_({ui::SCALE_FACTOR_200P}), |
| 174 test_favicon_driver_(&favicon_service_, |
| 175 &history_service_, |
| 176 &fake_image_downloader_) { |
| 177 CHECK(temp_history_dir_.CreateUniqueTempDir()); |
| 178 // We use the main thread (task runner) for the history thread during |
| 179 // testing. |
| 180 history_service_.InitForTest( |
| 181 history::TestHistoryDatabaseParamsForPath(temp_history_dir_.GetPath()), |
| 182 base::ThreadTaskRunnerHandle::Get()); |
| 183 } |
| 184 |
| 185 ~FaviconDriverImplIntegrationTest() override { history_service_.Shutdown(); } |
| 186 |
| 187 // Convenience synchronous API that mimics the async one in FaviconService. |
| 188 FaviconRawBitmapResult GetRawFaviconForPageURL(const GURL& page_url, |
| 189 int icon_types, |
| 190 int desired_size_in_pixel) { |
| 191 FaviconRawBitmapResult result; |
| 192 base::CancelableTaskTracker tracker; |
| 193 base::RunLoop loop; |
| 194 favicon_service_.GetRawFaviconForPageURL( |
| 195 page_url, icon_types, desired_size_in_pixel, |
| 196 base::Bind( |
| 197 [](FaviconRawBitmapResult* save_result, base::RunLoop* loop, |
| 198 const FaviconRawBitmapResult& result) { |
| 199 *save_result = result; |
| 200 loop->Quit(); |
| 201 }, |
| 202 &result, &loop), |
| 203 &tracker); |
| 204 loop.Run(); |
| 205 return result; |
| 206 } |
| 207 |
| 208 base::MessageLoopForUI message_loop_; |
| 209 base::ScopedTempDir temp_history_dir_; |
| 210 history::HistoryService history_service_; |
| 211 FaviconServiceImpl favicon_service_; |
| 212 ui::test::ScopedSetSupportedScaleFactors scoped_set_supported_scale_factors_; |
| 213 FakeImageDownloader fake_image_downloader_; |
| 214 TestFaviconDriver test_favicon_driver_; |
| 215 }; |
| 216 |
| 217 TEST_F(FaviconDriverImplIntegrationTest, ShouldCacheSingle32x32) { |
| 218 const GURL kPageURL("http://www.google.com"); |
| 219 const GURL kIconURL("http://www.google.com/googleg_lodp.ico"); |
| 220 |
| 221 fake_image_downloader_.SetFakeResponse(kIconURL, |
| 222 {{gfx::Size(32, 32), SK_ColorRED}}); |
| 223 |
| 224 test_favicon_driver_.TestFetchFaviconForPage( |
| 225 kPageURL, {FaviconURL(kIconURL, FAVICON, kEmptyIconSizes)}); |
| 226 base::RunLoop().RunUntilIdle(); |
| 227 |
| 228 EXPECT_FALSE(GetRawFaviconForPageURL(kPageURL, TOUCH_ICON, |
| 229 /*desired_size_in_pixel=*/0) |
| 230 .is_valid()); |
| 231 |
| 232 const FaviconRawBitmapResult result = |
| 233 GetRawFaviconForPageURL(kPageURL, FAVICON, /*desired_size_in_pixel=*/0); |
| 234 EXPECT_TRUE(result.is_valid()); |
| 235 EXPECT_THAT(result.icon_type, Eq(FAVICON)); |
| 236 EXPECT_THAT(result.pixel_size, Eq(gfx::Size(32, 32))); |
| 237 EXPECT_THAT(result, HasColor(SK_ColorRED)); |
| 238 EXPECT_THAT(fake_image_downloader_.NumDownloads(), Eq(1)); |
| 239 } |
| 240 |
| 241 TEST_F(FaviconDriverImplIntegrationTest, ShouldGetExactMatch) { |
| 242 const GURL kPageURL("http://www.google.com"); |
| 243 const GURL kIconURL("http://www.google.com/googleg_lodp.ico"); |
| 244 |
| 245 fake_image_downloader_.SetFakeResponse( |
| 246 kIconURL, |
| 247 {{gfx::Size(32, 32), SK_ColorRED}, {gfx::Size(64, 64), SK_ColorGREEN}}); |
| 248 |
| 249 test_favicon_driver_.TestFetchFaviconForPage( |
| 250 kPageURL, {FaviconURL(kIconURL, FAVICON, kEmptyIconSizes)}); |
| 251 base::RunLoop().RunUntilIdle(); |
| 252 |
| 253 // TODO(mastiz) / DONOTSUBMIT: Why is the biggest one preferred? |
| 254 EXPECT_THAT( |
| 255 GetRawFaviconForPageURL(kPageURL, FAVICON, /*desired_size_in_pixel=*/32), |
| 256 HasColor(SK_ColorGREEN)); |
| 257 } |
| 258 |
| 259 TEST_F(FaviconDriverImplIntegrationTest, ShouldCacheMultipleWithExplicitSize) { |
| 260 const GURL kPageURL("http://www.reddit.com"); |
| 261 const GURL kFaviconURL64("https://www.redditstatic.com/64x64.png"); |
| 262 const GURL kFaviconURL128("https://www.redditstatic.com/128x128.png"); |
| 263 const GURL kFaviconURL192("https://www.redditstatic.com/192x192.png"); |
| 264 const GURL kTouchIconURL76("https://www.redditstatic.com/76x76.png"); |
| 265 const GURL kTouchIconURL120("https://www.redditstatic.com/120x120.png"); |
| 266 const GURL kTouchIconURL152("https://www.redditstatic.com/152x152.png"); |
| 267 const GURL kTouchIconURL180("https://www.redditstatic.com/180x180.png"); |
| 268 |
| 269 fake_image_downloader_.SetFakeResponse(kFaviconURL64, |
| 270 {{gfx::Size(64, 64), SK_ColorRED}}); |
| 271 fake_image_downloader_.SetFakeResponse( |
| 272 kFaviconURL128, {{gfx::Size(128, 128), SK_ColorGREEN}}); |
| 273 fake_image_downloader_.SetFakeResponse(kFaviconURL192, |
| 274 {{gfx::Size(192, 192), SK_ColorBLUE}}); |
| 275 fake_image_downloader_.SetFakeResponse(kTouchIconURL76, |
| 276 {{gfx::Size(76, 76), SK_ColorYELLOW}}); |
| 277 fake_image_downloader_.SetFakeResponse(kTouchIconURL120, |
| 278 {{gfx::Size(120, 120), SK_ColorCYAN}}); |
| 279 fake_image_downloader_.SetFakeResponse( |
| 280 kTouchIconURL152, {{gfx::Size(152, 152), SK_ColorMAGENTA}}); |
| 281 fake_image_downloader_.SetFakeResponse(kTouchIconURL180, |
| 282 {{gfx::Size(180, 180), SK_ColorGRAY}}); |
| 283 |
| 284 test_favicon_driver_.TestFetchFaviconForPage( |
| 285 kPageURL, |
| 286 { |
| 287 FaviconURL(kFaviconURL64, FAVICON, {gfx::Size(64, 64)}), |
| 288 FaviconURL(kFaviconURL128, FAVICON, {gfx::Size(128, 128)}), |
| 289 FaviconURL(kFaviconURL192, FAVICON, {gfx::Size(192, 192)}), |
| 290 FaviconURL(kTouchIconURL76, TOUCH_ICON, {gfx::Size(76, 76)}), |
| 291 FaviconURL(kTouchIconURL120, TOUCH_ICON, {gfx::Size(120, 120)}), |
| 292 FaviconURL(kTouchIconURL152, TOUCH_ICON, {gfx::Size(152, 152)}), |
| 293 FaviconURL(kTouchIconURL180, TOUCH_ICON, {gfx::Size(180, 180)}), |
| 294 }); |
| 295 base::RunLoop().RunUntilIdle(); |
| 296 |
| 297 const FaviconRawBitmapResult favicon_result = |
| 298 GetRawFaviconForPageURL(kPageURL, FAVICON, /*desired_size_in_pixel=*/128); |
| 299 EXPECT_TRUE(favicon_result.is_valid()); |
| 300 EXPECT_THAT(favicon_result.icon_type, Eq(FAVICON)); |
| 301 EXPECT_THAT(favicon_result.pixel_size, Eq(gfx::Size(128, 128))); |
| 302 // This is the current behavior but not necessarily ideal, since icons with |
| 303 // a closer size are published by the site. |
| 304 EXPECT_THAT(favicon_result, HasColor(SK_ColorBLUE)); |
| 305 |
| 306 const FaviconRawBitmapResult touch_icon_result = |
| 307 GetRawFaviconForPageURL(kPageURL, TOUCH_ICON, |
| 308 /*desired_size_in_pixel=*/0); |
| 309 EXPECT_TRUE(touch_icon_result.is_valid()); |
| 310 EXPECT_THAT(touch_icon_result.icon_type, Eq(TOUCH_ICON)); |
| 311 EXPECT_THAT(touch_icon_result.pixel_size, Eq(gfx::Size(180, 180))); |
| 312 EXPECT_THAT(touch_icon_result, HasColor(SK_ColorGRAY)); |
| 313 |
| 314 EXPECT_THAT(fake_image_downloader_.NumDownloads(), Eq(2)); |
| 315 } |
| 316 |
| 317 TEST_F(FaviconDriverImplIntegrationTest, ShouldNotifyUpdateDespiteFinal404) { |
| 318 const GURL kPage1URL("http://www.foo.com/page1"); |
| 319 const GURL kPage2URL("http://www.foo.com/page2"); |
| 320 const GURL kFavicon10x10URL("http://www.foo.com/favicon10x10.png"); |
| 321 const GURL kFavicon11x11URL("http://www.foo.com/favicon11x11.png"); |
| 322 const GURL kBadIconURL("http://www.foo.com/404.ico"); |
| 323 |
| 324 fake_image_downloader_.SetFakeResponse(kFavicon10x10URL, |
| 325 {{gfx::Size(10, 10), SK_ColorRED}}); |
| 326 fake_image_downloader_.SetFakeResponse(kFavicon11x11URL, |
| 327 {{gfx::Size(11, 11), SK_ColorGREEN}}); |
| 328 |
| 329 test_favicon_driver_.TestFetchFaviconForPage( |
| 330 kPage1URL, {FaviconURL(kBadIconURL, FAVICON, kEmptyIconSizes)}); |
| 331 base::RunLoop().RunUntilIdle(); |
| 332 |
| 333 test_favicon_driver_.TestFetchFaviconForPage( |
| 334 kPage2URL, { |
| 335 FaviconURL(kFavicon10x10URL, FAVICON, kEmptyIconSizes), |
| 336 FaviconURL(kFavicon11x11URL, FAVICON, kEmptyIconSizes), |
| 337 FaviconURL(kBadIconURL, FAVICON, kEmptyIconSizes), |
| 338 }); |
| 339 base::RunLoop().RunUntilIdle(); |
| 340 |
| 341 EXPECT_THAT(test_favicon_driver_.GetReceivedFaviconUpdates(), |
| 342 Contains(kPage2URL)); |
| 343 } |
| 344 |
| 345 } // namespace |
| 346 } // namespace favicon |
| OLD | NEW |