blob: 0c59657336df37e0bb0935238046b33f59941b0d [file] [log] [blame]
// Copyright 2018 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/omnibox/browser/favicon_cache.h"
#include "base/bind.h"
#include "components/favicon/core/test/mock_favicon_service.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/gfx/favicon_size.h"
using testing::_;
using testing::DoAll;
using testing::Return;
using testing::SaveArg;
namespace {
favicon_base::FaviconImageResult GetDummyFaviconResult() {
favicon_base::FaviconImageResult result;
result.icon_url = GURL("http://example.com/favicon.ico");
SkBitmap bitmap;
bitmap.allocN32Pixels(gfx::kFaviconSize, gfx::kFaviconSize);
bitmap.eraseColor(SK_ColorBLUE);
result.image = gfx::Image::CreateFrom1xBitmap(bitmap);
return result;
}
void VerifyFetchedFaviconAndCount(int* count, const gfx::Image& favicon) {
DCHECK(count);
EXPECT_FALSE(favicon.IsEmpty());
++(*count);
}
void VerifyFetchedFavicon(const gfx::Image& favicon) {
EXPECT_FALSE(favicon.IsEmpty());
}
void Fail(const gfx::Image& favicon) {
FAIL() << "This asynchronous callback should never have been called.";
}
} // namespace
class FaviconCacheTest : public testing::Test {
protected:
const GURL kUrlA = GURL("http://www.a.com/");
const GURL kUrlB = GURL("http://www.b.com/");
const GURL kIconUrl = GURL("http://a.com/favicon.ico");
FaviconCacheTest()
: cache_(&favicon_service_, nullptr /* history_service */) {}
testing::NiceMock<favicon::MockFaviconService> favicon_service_;
void ExpectFaviconServiceForPageUrlCalls(int a_site_calls, int b_site_calls) {
if (a_site_calls > 0) {
EXPECT_CALL(
favicon_service_,
GetFaviconImageForPageURL(kUrlA, _ /* callback */, _ /* tracker */))
.Times(a_site_calls)
.WillRepeatedly(
DoAll(SaveArg<1>(&favicon_service_a_site_response_),
Return(base::CancelableTaskTracker::kBadTaskId)));
}
if (b_site_calls > 0) {
EXPECT_CALL(
favicon_service_,
GetFaviconImageForPageURL(kUrlB, _ /* callback */, _ /* tracker */))
.Times(b_site_calls)
.WillRepeatedly(
DoAll(SaveArg<1>(&favicon_service_b_site_response_),
Return(base::CancelableTaskTracker::kBadTaskId)));
}
}
void ExpectFaviconServiceForIconUrlCalls(int calls) {
EXPECT_CALL(favicon_service_,
GetFaviconImage(kIconUrl, _ /* callback */, _ /* tracker */))
.Times(calls);
}
favicon_base::FaviconImageCallback favicon_service_a_site_response_;
favicon_base::FaviconImageCallback favicon_service_b_site_response_;
FaviconCache cache_;
};
TEST_F(FaviconCacheTest, Basic) {
ExpectFaviconServiceForPageUrlCalls(1, 0);
ExpectFaviconServiceForIconUrlCalls(0);
int response_count = 0;
gfx::Image result = cache_.GetFaviconForPageUrl(
kUrlA, base::BindOnce(&VerifyFetchedFaviconAndCount, &response_count));
// Expect the synchronous result to be empty.
EXPECT_TRUE(result.IsEmpty());
favicon_service_a_site_response_.Run(GetDummyFaviconResult());
// Re-request the same favicon and expect a non-empty result now that the
// cache is populated. The above EXPECT_CALL will also verify that the
// backing FaviconService is not hit again.
result = cache_.GetFaviconForPageUrl(kUrlA, base::BindOnce(&Fail));
EXPECT_FALSE(result.IsEmpty());
EXPECT_EQ(1, response_count);
}
TEST_F(FaviconCacheTest, GetFaviconForIconUrl) {
// Verify that the service receives a request by the icon URL.
ExpectFaviconServiceForPageUrlCalls(0, 0);
ExpectFaviconServiceForIconUrlCalls(1);
// Since the other tests are comprehensive, we don't simulate or verify the
// actual result.
gfx::Image result =
cache_.GetFaviconForIconUrl(kIconUrl, base::BindOnce(&Fail));
EXPECT_TRUE(result.IsEmpty());
}
TEST_F(FaviconCacheTest, MultipleRequestsAreCoalesced) {
ExpectFaviconServiceForPageUrlCalls(1, 0);
int response_count = 0;
for (int i = 0; i < 10; ++i) {
cache_.GetFaviconForPageUrl(
kUrlA, base::BindOnce(&VerifyFetchedFaviconAndCount, &response_count));
}
favicon_service_a_site_response_.Run(GetDummyFaviconResult());
EXPECT_EQ(10, response_count);
}
TEST_F(FaviconCacheTest, SeparateOriginsAreCachedSeparately) {
ExpectFaviconServiceForPageUrlCalls(1, 1);
int a_site_response_count = 0;
int b_site_response_count = 0;
gfx::Image a_site_return = cache_.GetFaviconForPageUrl(
kUrlA,
base::BindOnce(&VerifyFetchedFaviconAndCount, &a_site_response_count));
gfx::Image b_site_return = cache_.GetFaviconForPageUrl(
kUrlB,
base::BindOnce(&VerifyFetchedFaviconAndCount, &b_site_response_count));
EXPECT_TRUE(a_site_return.IsEmpty());
EXPECT_TRUE(b_site_return.IsEmpty());
EXPECT_EQ(0, a_site_response_count);
EXPECT_EQ(0, b_site_response_count);
favicon_service_b_site_response_.Run(GetDummyFaviconResult());
EXPECT_EQ(0, a_site_response_count);
EXPECT_EQ(1, b_site_response_count);
a_site_return = cache_.GetFaviconForPageUrl(
kUrlA,
base::BindOnce(&VerifyFetchedFaviconAndCount, &a_site_response_count));
b_site_return = cache_.GetFaviconForPageUrl(kUrlB, base::BindOnce(&Fail));
EXPECT_TRUE(a_site_return.IsEmpty());
EXPECT_FALSE(b_site_return.IsEmpty());
EXPECT_EQ(0, a_site_response_count);
EXPECT_EQ(1, b_site_response_count);
favicon_service_a_site_response_.Run(GetDummyFaviconResult());
EXPECT_EQ(2, a_site_response_count);
EXPECT_EQ(1, b_site_response_count);
a_site_return = cache_.GetFaviconForPageUrl(kUrlA, base::BindOnce(&Fail));
b_site_return = cache_.GetFaviconForPageUrl(kUrlB, base::BindOnce(&Fail));
EXPECT_FALSE(a_site_return.IsEmpty());
EXPECT_FALSE(b_site_return.IsEmpty());
}
TEST_F(FaviconCacheTest, ClearIconsWithHistoryDeletions) {
ExpectFaviconServiceForPageUrlCalls(3, 2);
EXPECT_TRUE(
cache_.GetFaviconForPageUrl(kUrlA, base::BindOnce(&VerifyFetchedFavicon))
.IsEmpty());
EXPECT_TRUE(
cache_.GetFaviconForPageUrl(kUrlB, base::BindOnce(&VerifyFetchedFavicon))
.IsEmpty());
favicon_service_a_site_response_.Run(GetDummyFaviconResult());
favicon_service_b_site_response_.Run(GetDummyFaviconResult());
EXPECT_FALSE(
cache_.GetFaviconForPageUrl(kUrlA, base::BindOnce(&Fail)).IsEmpty());
EXPECT_FALSE(
cache_.GetFaviconForPageUrl(kUrlB, base::BindOnce(&Fail)).IsEmpty());
// Delete just the entry for kUrlA.
history::URLRows a_rows = {history::URLRow(kUrlA)};
cache_.OnURLsDeleted(
nullptr /* history_service */,
history::DeletionInfo::ForUrls(a_rows, {} /* favicon_urls */));
EXPECT_TRUE(
cache_.GetFaviconForPageUrl(kUrlA, base::BindOnce(&VerifyFetchedFavicon))
.IsEmpty());
EXPECT_FALSE(
cache_.GetFaviconForPageUrl(kUrlB, base::BindOnce(&Fail)).IsEmpty());
// Restore the cache entry for kUrlA.
favicon_service_a_site_response_.Run(GetDummyFaviconResult());
// Delete all history.
cache_.OnURLsDeleted(nullptr /* history_service */,
history::DeletionInfo::ForAllHistory());
EXPECT_TRUE(
cache_.GetFaviconForPageUrl(kUrlA, base::BindOnce(&VerifyFetchedFavicon))
.IsEmpty());
EXPECT_TRUE(
cache_.GetFaviconForPageUrl(kUrlB, base::BindOnce(&VerifyFetchedFavicon))
.IsEmpty());
}
TEST_F(FaviconCacheTest, CacheNullFavicons) {
ExpectFaviconServiceForPageUrlCalls(1, 0);
EXPECT_TRUE(
cache_.GetFaviconForPageUrl(kUrlA, base::BindOnce(&Fail)).IsEmpty());
favicon_service_a_site_response_.Run(favicon_base::FaviconImageResult());
// The mock FaviconService's EXPECT_CALL verifies that we do not make another
// call to FaviconService.
EXPECT_TRUE(
cache_.GetFaviconForPageUrl(kUrlA, base::BindOnce(&Fail)).IsEmpty());
}
TEST_F(FaviconCacheTest, ExpireNullFaviconsByHistory) {
ExpectFaviconServiceForPageUrlCalls(2, 0);
EXPECT_TRUE(
cache_.GetFaviconForPageUrl(kUrlA, base::BindOnce(&Fail)).IsEmpty());
favicon_service_a_site_response_.Run(favicon_base::FaviconImageResult());
cache_.OnURLVisited(nullptr /* history_service */, ui::PAGE_TRANSITION_LINK,
history::URLRow(kUrlA), history::RedirectList(),
base::Time::Now());
// Now the empty favicon should have been expired and we expect our second
// call to the mock underlying FaviconService.
EXPECT_TRUE(
cache_.GetFaviconForPageUrl(kUrlA, base::BindOnce(&VerifyFetchedFavicon))
.IsEmpty());
favicon_service_a_site_response_.Run(GetDummyFaviconResult());
EXPECT_FALSE(
cache_.GetFaviconForPageUrl(kUrlA, base::BindOnce(&Fail)).IsEmpty());
}
TEST_F(FaviconCacheTest, ObserveFaviconsChanged) {
ExpectFaviconServiceForPageUrlCalls(2, 1);
EXPECT_TRUE(
cache_.GetFaviconForPageUrl(kUrlA, base::BindOnce(&Fail)).IsEmpty());
EXPECT_TRUE(
cache_.GetFaviconForPageUrl(kUrlB, base::BindOnce(&Fail)).IsEmpty());
// Simulate responses to both requests.
favicon_service_a_site_response_.Run(favicon_base::FaviconImageResult());
favicon_service_b_site_response_.Run(favicon_base::FaviconImageResult());
cache_.OnFaviconsChanged({kUrlA}, GURL());
// Request the two favicons again.
EXPECT_TRUE(
cache_.GetFaviconForPageUrl(kUrlA, base::BindOnce(&Fail)).IsEmpty());
EXPECT_TRUE(
cache_.GetFaviconForPageUrl(kUrlB, base::BindOnce(&Fail)).IsEmpty());
// Our call to |ExpectFaviconServiceForPageUrlCalls(expected A calls, expected
// B calls)| above should verify that we re-request the icon for kUrlA only
// (because the null result has been invalidated by OnFaviconsChanged).
}