blob: ef00339b97d8f2ca0c921b5b7657b989cb67852e [file] [log] [blame]
// Copyright 2019 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "components/favicon/core/history_ui_favicon_request_handler_impl.h"
#include <utility>
#include "base/functional/bind.h"
#include "base/functional/callback_helpers.h"
#include "base/memory/raw_ptr.h"
#include "base/task/cancelable_task_tracker.h"
#include "base/test/metrics/histogram_tester.h"
#include "base/test/mock_callback.h"
#include "components/favicon/core/large_icon_service.h"
#include "components/favicon/core/test/mock_favicon_service.h"
#include "components/favicon_base/favicon_types.h"
#include "net/traffic_annotation/network_traffic_annotation.h"
#include "testing/gmock/include/gmock/gmock.h"
#include "testing/gtest/include/gtest/gtest.h"
#include "third_party/skia/include/core/SkBitmap.h"
#include "third_party/skia/include/core/SkColor.h"
#include "ui/gfx/codec/png_codec.h"
#include "ui/gfx/favicon_size.h"
#include "ui/gfx/image/image.h"
#include "ui/gfx/image/image_unittest_util.h"
namespace favicon {
namespace {
using testing::_;
using testing::Return;
const base::CancelableTaskTracker::TaskId kTaskId = 1;
favicon_base::FaviconRawBitmapResult CreateTestBitmapResult(
const GURL& icon_url,
int desired_size_in_pixel) {
scoped_refptr<base::RefCountedBytes> data(new base::RefCountedBytes());
gfx::PNGCodec::EncodeBGRASkBitmap(
gfx::test::CreateBitmap(desired_size_in_pixel, SK_ColorRED), false,
&data->as_vector());
favicon_base::FaviconRawBitmapResult result;
result.bitmap_data = data;
result.icon_url = icon_url;
result.pixel_size = gfx::Size(desired_size_in_pixel, desired_size_in_pixel);
return result;
}
favicon_base::FaviconImageResult CreateTestImageResult(const GURL& icon_url) {
favicon_base::FaviconImageResult result;
result.image = gfx::test::CreateImage(gfx::kFaviconSize, SK_ColorRED);
result.icon_url = icon_url;
return result;
}
void StoreBitmap(favicon_base::FaviconRawBitmapResult* destination,
const favicon_base::FaviconRawBitmapResult& result) {
*destination = result;
}
void StoreImage(favicon_base::FaviconImageResult* destination,
const favicon_base::FaviconImageResult& result) {
*destination = result;
}
class MockFaviconServiceWithFake : public MockFaviconService {
public:
MockFaviconServiceWithFake() {
// Fake won't respond with any icons at first.
ON_CALL(*this, GetRawFaviconForPageURL)
.WillByDefault([](auto, auto, auto, auto,
favicon_base::FaviconRawBitmapCallback callback,
auto) {
std::move(callback).Run(favicon_base::FaviconRawBitmapResult());
return kTaskId;
});
ON_CALL(*this, GetFaviconImageForPageURL)
.WillByDefault(
[](auto, favicon_base::FaviconImageCallback callback, auto) {
std::move(callback).Run(favicon_base::FaviconImageResult());
return kTaskId;
});
}
MockFaviconServiceWithFake(const MockFaviconServiceWithFake&) = delete;
MockFaviconServiceWithFake& operator=(const MockFaviconServiceWithFake&) =
delete;
~MockFaviconServiceWithFake() override = default;
// Simulates the service having an icon stored for `page_url`, the URL of the
// image being `icon_url`. The real FaviconService performs resizing if it
// can't find a stored icon matching the requested size, so the same is true
// here: any requested size will return a bitmap of that size.
void StoreMockLocalFavicon(const GURL& page_url, const GURL& icon_url) {
ON_CALL(*this, GetRawFaviconForPageURL(page_url, _, _, _, _, _))
.WillByDefault(
[icon_url](auto, auto, int desired_size_in_pixel, auto,
favicon_base::FaviconRawBitmapCallback callback, auto) {
std::move(callback).Run(
CreateTestBitmapResult(icon_url, desired_size_in_pixel));
return kTaskId;
});
ON_CALL(*this, GetFaviconImageForPageURL(page_url, _, _))
.WillByDefault([icon_url](auto,
favicon_base::FaviconImageCallback callback,
auto) {
std::move(callback).Run(CreateTestImageResult(icon_url));
return kTaskId;
});
}
};
class MockLargeIconServiceWithFake : public LargeIconService {
public:
explicit MockLargeIconServiceWithFake(
MockFaviconServiceWithFake* mock_favicon_service_with_fake)
: mock_favicon_service_with_fake_(mock_favicon_service_with_fake) {
// Fake won't respond with any icons at first (HTTP error 404).
ON_CALL(*this,
GetLargeIconOrFallbackStyleFromGoogleServerSkippingLocalCache(_, _,
_, _))
.WillByDefault(
[](auto, auto, auto,
favicon_base::GoogleFaviconServerCallback server_callback) {
std::move(server_callback)
.Run(favicon_base::GoogleFaviconServerRequestStatus::
FAILURE_HTTP_ERROR);
});
}
MockLargeIconServiceWithFake(const MockLargeIconServiceWithFake&) = delete;
MockLargeIconServiceWithFake& operator=(const MockLargeIconServiceWithFake&) =
delete;
~MockLargeIconServiceWithFake() override = default;
// Simulates the Google Server having an icon stored for `page_url`, of
// associated `icon_url`. Requests will cause the icon to be stored in
// `mock_favicon_service_with_fake_`.
void StoreMockGoogleServerFavicon(const GURL& page_url,
const GURL& icon_url) {
ON_CALL(*this,
GetLargeIconOrFallbackStyleFromGoogleServerSkippingLocalCache(_, _,
_, _))
.WillByDefault(
[=](auto, auto, auto,
favicon_base::GoogleFaviconServerCallback server_callback) {
mock_favicon_service_with_fake_->StoreMockLocalFavicon(page_url,
icon_url);
std::move(server_callback)
.Run(favicon_base::GoogleFaviconServerRequestStatus::SUCCESS);
});
}
// LargeIconService overrides.
MOCK_METHOD(void,
GetLargeIconOrFallbackStyleFromGoogleServerSkippingLocalCache,
(const GURL& page_url,
bool should_trim_page_url_path,
const net::NetworkTrafficAnnotationTag& traffic_annotation,
favicon_base::GoogleFaviconServerCallback callback),
(override));
MOCK_METHOD(base::CancelableTaskTracker::TaskId,
GetLargeIconRawBitmapOrFallbackStyleForPageUrl,
(const GURL& page_url,
int min_source_size_in_pixel,
int desired_size_in_pixel,
favicon_base::LargeIconCallback callback,
base::CancelableTaskTracker* tracker),
(override));
MOCK_METHOD(base::CancelableTaskTracker::TaskId,
GetLargeIconImageOrFallbackStyleForPageUrl,
(const GURL& page_url,
int min_source_size_in_pixel,
int desired_size_in_pixel,
favicon_base::LargeIconImageCallback callback,
base::CancelableTaskTracker* tracker),
(override));
MOCK_METHOD(
base::CancelableTaskTracker::TaskId,
GetLargeIconRawBitmapForPageUrl,
(const GURL& page_url,
int min_source_size_in_pixel,
std::optional<int> size_in_pixel_to_resize_to,
LargeIconService::NoBigEnoughIconBehavior no_big_enough_icon_behavior,
favicon_base::LargeIconCallback callback,
base::CancelableTaskTracker* tracker),
(override));
MOCK_METHOD(base::CancelableTaskTracker::TaskId,
GetLargeIconRawBitmapOrFallbackStyleForIconUrl,
(const GURL& icon_url,
int min_source_size_in_pixel,
int desired_size_in_pixel,
favicon_base::LargeIconCallback callback,
base::CancelableTaskTracker* tracker),
(override));
MOCK_METHOD(base::CancelableTaskTracker::TaskId,
GetIconRawBitmapOrFallbackStyleForPageUrl,
(const GURL& page_url,
int desired_size_in_pixel,
favicon_base::LargeIconCallback callback,
base::CancelableTaskTracker* tracker),
(override));
MOCK_METHOD(void,
TouchIconFromGoogleServer,
(const GURL& icon_url),
(override));
private:
const raw_ptr<MockFaviconServiceWithFake> mock_favicon_service_with_fake_;
};
class HistoryUiFaviconRequestHandlerImplTest : public ::testing::Test {
public:
HistoryUiFaviconRequestHandlerImplTest()
: mock_large_icon_service_(&mock_favicon_service_),
history_ui_favicon_request_handler_(can_send_history_data_getter_.Get(),
&mock_favicon_service_,
&mock_large_icon_service_) {
// Allow sending history data by default.
ON_CALL(can_send_history_data_getter_, Run()).WillByDefault(Return(true));
}
HistoryUiFaviconRequestHandlerImplTest(
const HistoryUiFaviconRequestHandlerImplTest&) = delete;
HistoryUiFaviconRequestHandlerImplTest& operator=(
const HistoryUiFaviconRequestHandlerImplTest&) = delete;
protected:
testing::NiceMock<MockFaviconServiceWithFake> mock_favicon_service_;
testing::NiceMock<MockLargeIconServiceWithFake> mock_large_icon_service_;
testing::NiceMock<base::MockCallback<
HistoryUiFaviconRequestHandlerImpl::CanSendHistoryDataGetter>>
can_send_history_data_getter_;
base::HistogramTester histogram_tester_;
HistoryUiFaviconRequestHandlerImpl history_ui_favicon_request_handler_;
// Convenience constants used in the tests.
const GURL kPageUrl = GURL("https://www.example.com");
const GURL kIconUrl = GURL("https://www.example.com/favicon16.png");
const HistoryUiFaviconRequestOrigin kOrigin =
HistoryUiFaviconRequestOrigin::kHistory;
const std::string kOriginHistogramSuffix = ".HISTORY";
const std::string kAvailabilityHistogramName =
"Sync.SyncedHistoryFaviconAvailability";
};
TEST_F(HistoryUiFaviconRequestHandlerImplTest, ShouldGetEmptyBitmap) {
EXPECT_CALL(mock_favicon_service_,
GetRawFaviconForPageURL(kPageUrl, _,
/*desired_size_in_pixel=*/16, _, _, _));
favicon_base::FaviconRawBitmapResult result;
history_ui_favicon_request_handler_.GetRawFaviconForPageURL(
kPageUrl, /*desired_size_in_pixel=*/16,
base::BindOnce(&StoreBitmap, &result), kOrigin);
EXPECT_FALSE(result.is_valid());
histogram_tester_.ExpectUniqueSample(
kAvailabilityHistogramName + kOriginHistogramSuffix,
FaviconAvailability::kNotAvailable, 1);
}
TEST_F(HistoryUiFaviconRequestHandlerImplTest, ShouldGetLocalBitmap) {
mock_favicon_service_.StoreMockLocalFavicon(kPageUrl, kIconUrl);
EXPECT_CALL(mock_favicon_service_,
GetRawFaviconForPageURL(kPageUrl, _,
/*desired_size_in_pixel=*/16, _, _, _));
EXPECT_CALL(mock_large_icon_service_, TouchIconFromGoogleServer(kIconUrl));
favicon_base::FaviconRawBitmapResult result;
history_ui_favicon_request_handler_.GetRawFaviconForPageURL(
kPageUrl, /*desired_size_in_pixel=*/16,
base::BindOnce(&StoreBitmap, &result), kOrigin);
EXPECT_TRUE(result.is_valid());
histogram_tester_.ExpectUniqueSample(
kAvailabilityHistogramName + kOriginHistogramSuffix,
FaviconAvailability::kLocal, 1);
}
TEST_F(HistoryUiFaviconRequestHandlerImplTest, ShouldGetGoogleServerBitmap) {
mock_large_icon_service_.StoreMockGoogleServerFavicon(kPageUrl, kIconUrl);
EXPECT_CALL(can_send_history_data_getter_, Run());
EXPECT_CALL(mock_favicon_service_,
GetRawFaviconForPageURL(kPageUrl, _,
/*desired_size_in_pixel=*/16, _, _, _))
.Times(2);
EXPECT_CALL(mock_large_icon_service_,
GetLargeIconOrFallbackStyleFromGoogleServerSkippingLocalCache(
kPageUrl,
/*should_trim_page_url_path=*/false, _, _));
favicon_base::FaviconRawBitmapResult result;
history_ui_favicon_request_handler_.GetRawFaviconForPageURL(
kPageUrl, /*desired_size_in_pixel=*/16,
base::BindOnce(&StoreBitmap, &result), kOrigin);
EXPECT_TRUE(result.is_valid());
histogram_tester_.ExpectUniqueSample(
kAvailabilityHistogramName + kOriginHistogramSuffix,
FaviconAvailability::kLocal, 1);
}
TEST_F(HistoryUiFaviconRequestHandlerImplTest, ShouldGetEmptyImage) {
EXPECT_CALL(mock_favicon_service_, GetFaviconImageForPageURL(kPageUrl, _, _));
favicon_base::FaviconImageResult result;
history_ui_favicon_request_handler_.GetFaviconImageForPageURL(
kPageUrl, base::BindOnce(&StoreImage, &result), kOrigin);
EXPECT_TRUE(result.image.IsEmpty());
histogram_tester_.ExpectUniqueSample(
kAvailabilityHistogramName + kOriginHistogramSuffix,
FaviconAvailability::kNotAvailable, 1);
}
TEST_F(HistoryUiFaviconRequestHandlerImplTest, ShouldGetLocalImage) {
mock_favicon_service_.StoreMockLocalFavicon(kPageUrl, kIconUrl);
EXPECT_CALL(mock_favicon_service_, GetFaviconImageForPageURL(kPageUrl, _, _));
EXPECT_CALL(mock_large_icon_service_, TouchIconFromGoogleServer(kIconUrl));
favicon_base::FaviconImageResult result;
history_ui_favicon_request_handler_.GetFaviconImageForPageURL(
kPageUrl, base::BindOnce(&StoreImage, &result), kOrigin);
EXPECT_FALSE(result.image.IsEmpty());
histogram_tester_.ExpectUniqueSample(
kAvailabilityHistogramName + kOriginHistogramSuffix,
FaviconAvailability::kLocal, 1);
}
TEST_F(HistoryUiFaviconRequestHandlerImplTest, ShouldGetGoogleServerImage) {
mock_large_icon_service_.StoreMockGoogleServerFavicon(kPageUrl, kIconUrl);
EXPECT_CALL(can_send_history_data_getter_, Run());
EXPECT_CALL(mock_favicon_service_, GetFaviconImageForPageURL(kPageUrl, _, _))
.Times(2);
EXPECT_CALL(mock_large_icon_service_,
GetLargeIconOrFallbackStyleFromGoogleServerSkippingLocalCache(
kPageUrl,
/*should_trim_page_url_path=*/false, _, _));
favicon_base::FaviconImageResult result;
history_ui_favicon_request_handler_.GetFaviconImageForPageURL(
kPageUrl, base::BindOnce(&StoreImage, &result), kOrigin);
EXPECT_FALSE(result.image.IsEmpty());
histogram_tester_.ExpectUniqueSample(
kAvailabilityHistogramName + kOriginHistogramSuffix,
FaviconAvailability::kLocal, 1);
}
TEST_F(HistoryUiFaviconRequestHandlerImplTest,
ShouldNotQueryGoogleServerIfCannotSendData) {
EXPECT_CALL(can_send_history_data_getter_, Run()).WillOnce([]() {
return false;
});
EXPECT_CALL(mock_favicon_service_,
GetRawFaviconForPageURL(kPageUrl, _,
/*desired_size_in_pixel=*/16, _, _, _))
.WillOnce([](auto, auto, auto, auto,
favicon_base::FaviconRawBitmapCallback callback, auto) {
std::move(callback).Run(favicon_base::FaviconRawBitmapResult());
return kTaskId;
});
EXPECT_CALL(
mock_large_icon_service_,
GetLargeIconOrFallbackStyleFromGoogleServerSkippingLocalCache(_, _, _, _))
.Times(0);
history_ui_favicon_request_handler_.GetRawFaviconForPageURL(
kPageUrl, /*desired_size_in_pixel=*/16, base::DoNothing(), kOrigin);
}
} // namespace
} // namespace favicon