| Index: components/favicon/core/favicon_driver_impl_unittest.cc
|
| diff --git a/components/favicon/core/favicon_driver_impl_unittest.cc b/components/favicon/core/favicon_driver_impl_unittest.cc
|
| new file mode 100644
|
| index 0000000000000000000000000000000000000000..e34861d8f557c43e53fe832d996bcd7a1b64dcf3
|
| --- /dev/null
|
| +++ b/components/favicon/core/favicon_driver_impl_unittest.cc
|
| @@ -0,0 +1,346 @@
|
| +// Copyright (c) 2017 The Chromium Authors. All rights reserved.
|
| +// Use of this source code is governed by a BSD-style license that can be
|
| +// found in the LICENSE file.
|
| +
|
| +#include "components/favicon/core/favicon_driver_impl.h"
|
| +
|
| +#include <stddef.h>
|
| +
|
| +#include <map>
|
| +#include <memory>
|
| +#include <set>
|
| +#include <utility>
|
| +#include <vector>
|
| +
|
| +#include "base/files/scoped_temp_dir.h"
|
| +#include "base/message_loop/message_loop.h"
|
| +#include "base/run_loop.h"
|
| +#include "base/strings/stringprintf.h"
|
| +#include "components/favicon/core/favicon_client.h"
|
| +#include "components/favicon/core/favicon_service_impl.h"
|
| +#include "components/favicon/core/favicon_url.h"
|
| +#include "components/favicon/core/test/mock_favicon_service.h"
|
| +#include "components/history/core/browser/history_database_params.h"
|
| +#include "components/history/core/browser/history_service.h"
|
| +#include "components/history/core/test/test_history_database.h"
|
| +#include "testing/gmock/include/gmock/gmock.h"
|
| +#include "testing/gtest/include/gtest/gtest.h"
|
| +#include "third_party/skia/include/core/SkBitmap.h"
|
| +#include "ui/base/layout.h"
|
| +#include "ui/gfx/codec/png_codec.h"
|
| +#include "ui/gfx/color_analysis.h"
|
| +#include "ui/gfx/favicon_size.h"
|
| +#include "ui/gfx/image/image.h"
|
| +
|
| +namespace favicon {
|
| +namespace {
|
| +
|
| +using favicon_base::FAVICON;
|
| +using favicon_base::FaviconRawBitmapResult;
|
| +using favicon_base::TOUCH_ICON;
|
| +using testing::Contains;
|
| +using testing::Eq;
|
| +
|
| +// |arg| is a FaviconRawBitmapResult.
|
| +MATCHER_P(HasColor, expected_color, "") {
|
| + if (arg.bitmap_data == nullptr || arg.bitmap_data->size() == 0) {
|
| + *result_listener << "expected color but no bitmap data available for URL "
|
| + << arg.icon_url;
|
| + return false;
|
| + }
|
| + SkColor actual_color = color_utils::CalculateKMeanColorOfPNG(arg.bitmap_data);
|
| + if (actual_color != expected_color) {
|
| + *result_listener << "expected color "
|
| + << base::StringPrintf("%08X", expected_color)
|
| + << " but actual color is "
|
| + << base::StringPrintf("%08X", actual_color) << " for URL "
|
| + << arg.icon_url;
|
| + return false;
|
| + }
|
| + return true;
|
| +}
|
| +
|
| +SkBitmap CreateBitmap(int w, int h, SkColor color) {
|
| + SkBitmap bmp;
|
| + bmp.allocN32Pixels(w, h);
|
| + bmp.eraseColor(color);
|
| + return bmp;
|
| +}
|
| +
|
| +class FakeImageDownloader {
|
| + public:
|
| + struct Bitmap {
|
| + Bitmap(gfx::Size size, SkColor color) : size(size), color(color) {}
|
| +
|
| + gfx::Size size;
|
| + SkColor color;
|
| + };
|
| +
|
| + FakeImageDownloader() : num_downloads_(0) {}
|
| +
|
| + void SetFakeResponse(const GURL& url, const std::vector<Bitmap>& bitmaps) {
|
| + fake_responses_[url].first.clear();
|
| + fake_responses_[url].second.clear();
|
| + for (const Bitmap& bitmap : bitmaps) {
|
| + fake_responses_[url].second.push_back(bitmap.size);
|
| + fake_responses_[url].first.push_back(CreateBitmap(
|
| + bitmap.size.width(), bitmap.size.height(), bitmap.color));
|
| + }
|
| + }
|
| +
|
| + int DownloadImage(const GURL& url,
|
| + int max_image_size,
|
| + FaviconHandler::Delegate::ImageDownloadCallback callback) {
|
| + ++num_downloads_;
|
| + auto it = fake_responses_.find(url);
|
| + if (it == fake_responses_.end()) {
|
| + base::ThreadTaskRunnerHandle::Get()->PostTask(
|
| + FROM_HERE,
|
| + base::Bind(callback, num_downloads_, 404, url,
|
| + std::vector<SkBitmap>(), std::vector<gfx::Size>()));
|
| + } else {
|
| + base::ThreadTaskRunnerHandle::Get()->PostTask(
|
| + FROM_HERE, base::Bind(callback, num_downloads_, 200, url,
|
| + it->second.first, it->second.second));
|
| + }
|
| + return num_downloads_;
|
| + }
|
| +
|
| + int NumDownloads() { return num_downloads_; }
|
| +
|
| + private:
|
| + std::map<GURL, std::pair<std::vector<SkBitmap>, std::vector<gfx::Size>>>
|
| + fake_responses_;
|
| + int num_downloads_;
|
| +};
|
| +
|
| +class TestFaviconDriver : public FaviconDriverImpl {
|
| + public:
|
| + TestFaviconDriver(FaviconService* favicon_service,
|
| + history::HistoryService* history_service,
|
| + FakeImageDownloader* fake_image_downloader)
|
| + : FaviconDriverImpl(/*enable_touch_icons=*/true,
|
| + favicon_service,
|
| + history_service,
|
| + /*bookmark_model=*/nullptr),
|
| + fake_image_downloader_(fake_image_downloader) {}
|
| +
|
| + // Test method exercising the two events during a page visit.
|
| + void TestFetchFaviconForPage(const GURL& page_url,
|
| + const std::vector<FaviconURL>& candidates) {
|
| + FetchFavicon(page_url);
|
| + OnUpdateFaviconURL(page_url, candidates);
|
| + }
|
| +
|
| + // FaviconDriver implementation.
|
| + gfx::Image GetFavicon() const override { return gfx::Image(); }
|
| +
|
| + // FaviconHandler::Delegate implementation.
|
| + int DownloadImage(const GURL& url,
|
| + int max_image_size,
|
| + ImageDownloadCallback callback) override {
|
| + return fake_image_downloader_->DownloadImage(url, max_image_size, callback);
|
| + }
|
| +
|
| + bool IsOffTheRecord() override { return false; }
|
| +
|
| + void OnFaviconUpdated(
|
| + const GURL& page_url,
|
| + FaviconDriverObserver::NotificationIconType notification_icon_type,
|
| + const GURL& icon_url,
|
| + bool icon_url_changed,
|
| + const gfx::Image& image) override {
|
| + received_favicon_updates_.insert(page_url);
|
| + }
|
| +
|
| + const std::set<GURL>& GetReceivedFaviconUpdates() const {
|
| + return received_favicon_updates_;
|
| + }
|
| +
|
| + private:
|
| + FakeImageDownloader* fake_image_downloader_;
|
| + std::set<GURL> received_favicon_updates_;
|
| +};
|
| +
|
| +// Integration tests that use a test fixture with a real implementation for
|
| +// FaviconService (with an instance of HistoryService behind).
|
| +class FaviconDriverImplIntegrationTest : public testing::Test {
|
| + protected:
|
| + const std::vector<gfx::Size> kEmptyIconSizes;
|
| +
|
| + FaviconDriverImplIntegrationTest()
|
| + : favicon_service_(/*favicon_client=*/{}, &history_service_),
|
| + scoped_set_supported_scale_factors_({ui::SCALE_FACTOR_200P}),
|
| + test_favicon_driver_(&favicon_service_,
|
| + &history_service_,
|
| + &fake_image_downloader_) {
|
| + CHECK(temp_history_dir_.CreateUniqueTempDir());
|
| + // We use the main thread (task runner) for the history thread during
|
| + // testing.
|
| + history_service_.InitForTest(
|
| + history::TestHistoryDatabaseParamsForPath(temp_history_dir_.GetPath()),
|
| + base::ThreadTaskRunnerHandle::Get());
|
| + }
|
| +
|
| + ~FaviconDriverImplIntegrationTest() override { history_service_.Shutdown(); }
|
| +
|
| + // Convenience synchronous API that mimics the async one in FaviconService.
|
| + FaviconRawBitmapResult GetRawFaviconForPageURL(const GURL& page_url,
|
| + int icon_types,
|
| + int desired_size_in_pixel) {
|
| + FaviconRawBitmapResult result;
|
| + base::CancelableTaskTracker tracker;
|
| + base::RunLoop loop;
|
| + favicon_service_.GetRawFaviconForPageURL(
|
| + page_url, icon_types, desired_size_in_pixel,
|
| + base::Bind(
|
| + [](FaviconRawBitmapResult* save_result, base::RunLoop* loop,
|
| + const FaviconRawBitmapResult& result) {
|
| + *save_result = result;
|
| + loop->Quit();
|
| + },
|
| + &result, &loop),
|
| + &tracker);
|
| + loop.Run();
|
| + return result;
|
| + }
|
| +
|
| + base::MessageLoopForUI message_loop_;
|
| + base::ScopedTempDir temp_history_dir_;
|
| + history::HistoryService history_service_;
|
| + FaviconServiceImpl favicon_service_;
|
| + ui::test::ScopedSetSupportedScaleFactors scoped_set_supported_scale_factors_;
|
| + FakeImageDownloader fake_image_downloader_;
|
| + TestFaviconDriver test_favicon_driver_;
|
| +};
|
| +
|
| +TEST_F(FaviconDriverImplIntegrationTest, ShouldCacheSingle32x32) {
|
| + const GURL kPageURL("http://www.google.com");
|
| + const GURL kIconURL("http://www.google.com/googleg_lodp.ico");
|
| +
|
| + fake_image_downloader_.SetFakeResponse(kIconURL,
|
| + {{gfx::Size(32, 32), SK_ColorRED}});
|
| +
|
| + test_favicon_driver_.TestFetchFaviconForPage(
|
| + kPageURL, {FaviconURL(kIconURL, FAVICON, kEmptyIconSizes)});
|
| + base::RunLoop().RunUntilIdle();
|
| +
|
| + EXPECT_FALSE(GetRawFaviconForPageURL(kPageURL, TOUCH_ICON,
|
| + /*desired_size_in_pixel=*/0)
|
| + .is_valid());
|
| +
|
| + const FaviconRawBitmapResult result =
|
| + GetRawFaviconForPageURL(kPageURL, FAVICON, /*desired_size_in_pixel=*/0);
|
| + EXPECT_TRUE(result.is_valid());
|
| + EXPECT_THAT(result.icon_type, Eq(FAVICON));
|
| + EXPECT_THAT(result.pixel_size, Eq(gfx::Size(32, 32)));
|
| + EXPECT_THAT(result, HasColor(SK_ColorRED));
|
| + EXPECT_THAT(fake_image_downloader_.NumDownloads(), Eq(1));
|
| +}
|
| +
|
| +TEST_F(FaviconDriverImplIntegrationTest, ShouldGetExactMatch) {
|
| + const GURL kPageURL("http://www.google.com");
|
| + const GURL kIconURL("http://www.google.com/googleg_lodp.ico");
|
| +
|
| + fake_image_downloader_.SetFakeResponse(
|
| + kIconURL,
|
| + {{gfx::Size(32, 32), SK_ColorRED}, {gfx::Size(64, 64), SK_ColorGREEN}});
|
| +
|
| + test_favicon_driver_.TestFetchFaviconForPage(
|
| + kPageURL, {FaviconURL(kIconURL, FAVICON, kEmptyIconSizes)});
|
| + base::RunLoop().RunUntilIdle();
|
| +
|
| + // TODO(mastiz) / DONOTSUBMIT: Why is the biggest one preferred?
|
| + EXPECT_THAT(
|
| + GetRawFaviconForPageURL(kPageURL, FAVICON, /*desired_size_in_pixel=*/32),
|
| + HasColor(SK_ColorGREEN));
|
| +}
|
| +
|
| +TEST_F(FaviconDriverImplIntegrationTest, ShouldCacheMultipleWithExplicitSize) {
|
| + const GURL kPageURL("http://www.reddit.com");
|
| + const GURL kFaviconURL64("https://www.redditstatic.com/64x64.png");
|
| + const GURL kFaviconURL128("https://www.redditstatic.com/128x128.png");
|
| + const GURL kFaviconURL192("https://www.redditstatic.com/192x192.png");
|
| + const GURL kTouchIconURL76("https://www.redditstatic.com/76x76.png");
|
| + const GURL kTouchIconURL120("https://www.redditstatic.com/120x120.png");
|
| + const GURL kTouchIconURL152("https://www.redditstatic.com/152x152.png");
|
| + const GURL kTouchIconURL180("https://www.redditstatic.com/180x180.png");
|
| +
|
| + fake_image_downloader_.SetFakeResponse(kFaviconURL64,
|
| + {{gfx::Size(64, 64), SK_ColorRED}});
|
| + fake_image_downloader_.SetFakeResponse(
|
| + kFaviconURL128, {{gfx::Size(128, 128), SK_ColorGREEN}});
|
| + fake_image_downloader_.SetFakeResponse(kFaviconURL192,
|
| + {{gfx::Size(192, 192), SK_ColorBLUE}});
|
| + fake_image_downloader_.SetFakeResponse(kTouchIconURL76,
|
| + {{gfx::Size(76, 76), SK_ColorYELLOW}});
|
| + fake_image_downloader_.SetFakeResponse(kTouchIconURL120,
|
| + {{gfx::Size(120, 120), SK_ColorCYAN}});
|
| + fake_image_downloader_.SetFakeResponse(
|
| + kTouchIconURL152, {{gfx::Size(152, 152), SK_ColorMAGENTA}});
|
| + fake_image_downloader_.SetFakeResponse(kTouchIconURL180,
|
| + {{gfx::Size(180, 180), SK_ColorGRAY}});
|
| +
|
| + test_favicon_driver_.TestFetchFaviconForPage(
|
| + kPageURL,
|
| + {
|
| + FaviconURL(kFaviconURL64, FAVICON, {gfx::Size(64, 64)}),
|
| + FaviconURL(kFaviconURL128, FAVICON, {gfx::Size(128, 128)}),
|
| + FaviconURL(kFaviconURL192, FAVICON, {gfx::Size(192, 192)}),
|
| + FaviconURL(kTouchIconURL76, TOUCH_ICON, {gfx::Size(76, 76)}),
|
| + FaviconURL(kTouchIconURL120, TOUCH_ICON, {gfx::Size(120, 120)}),
|
| + FaviconURL(kTouchIconURL152, TOUCH_ICON, {gfx::Size(152, 152)}),
|
| + FaviconURL(kTouchIconURL180, TOUCH_ICON, {gfx::Size(180, 180)}),
|
| + });
|
| + base::RunLoop().RunUntilIdle();
|
| +
|
| + const FaviconRawBitmapResult favicon_result =
|
| + GetRawFaviconForPageURL(kPageURL, FAVICON, /*desired_size_in_pixel=*/128);
|
| + EXPECT_TRUE(favicon_result.is_valid());
|
| + EXPECT_THAT(favicon_result.icon_type, Eq(FAVICON));
|
| + EXPECT_THAT(favicon_result.pixel_size, Eq(gfx::Size(128, 128)));
|
| + // This is the current behavior but not necessarily ideal, since icons with
|
| + // a closer size are published by the site.
|
| + EXPECT_THAT(favicon_result, HasColor(SK_ColorBLUE));
|
| +
|
| + const FaviconRawBitmapResult touch_icon_result =
|
| + GetRawFaviconForPageURL(kPageURL, TOUCH_ICON,
|
| + /*desired_size_in_pixel=*/0);
|
| + EXPECT_TRUE(touch_icon_result.is_valid());
|
| + EXPECT_THAT(touch_icon_result.icon_type, Eq(TOUCH_ICON));
|
| + EXPECT_THAT(touch_icon_result.pixel_size, Eq(gfx::Size(180, 180)));
|
| + EXPECT_THAT(touch_icon_result, HasColor(SK_ColorGRAY));
|
| +
|
| + EXPECT_THAT(fake_image_downloader_.NumDownloads(), Eq(2));
|
| +}
|
| +
|
| +TEST_F(FaviconDriverImplIntegrationTest, ShouldNotifyUpdateDespiteFinal404) {
|
| + const GURL kPage1URL("http://www.foo.com/page1");
|
| + const GURL kPage2URL("http://www.foo.com/page2");
|
| + const GURL kFavicon10x10URL("http://www.foo.com/favicon10x10.png");
|
| + const GURL kFavicon11x11URL("http://www.foo.com/favicon11x11.png");
|
| + const GURL kBadIconURL("http://www.foo.com/404.ico");
|
| +
|
| + fake_image_downloader_.SetFakeResponse(kFavicon10x10URL,
|
| + {{gfx::Size(10, 10), SK_ColorRED}});
|
| + fake_image_downloader_.SetFakeResponse(kFavicon11x11URL,
|
| + {{gfx::Size(11, 11), SK_ColorGREEN}});
|
| +
|
| + test_favicon_driver_.TestFetchFaviconForPage(
|
| + kPage1URL, {FaviconURL(kBadIconURL, FAVICON, kEmptyIconSizes)});
|
| + base::RunLoop().RunUntilIdle();
|
| +
|
| + test_favicon_driver_.TestFetchFaviconForPage(
|
| + kPage2URL, {
|
| + FaviconURL(kFavicon10x10URL, FAVICON, kEmptyIconSizes),
|
| + FaviconURL(kFavicon11x11URL, FAVICON, kEmptyIconSizes),
|
| + FaviconURL(kBadIconURL, FAVICON, kEmptyIconSizes),
|
| + });
|
| + base::RunLoop().RunUntilIdle();
|
| +
|
| + EXPECT_THAT(test_favicon_driver_.GetReceivedFaviconUpdates(),
|
| + Contains(kPage2URL));
|
| +}
|
| +
|
| +} // namespace
|
| +} // namespace favicon
|
|
|