blob: 318675dd80b174a914e2e8a0302e4eb11c55ed7b [file] [log] [blame]
// Copyright (c) 2012 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_handler.h"
#include <stddef.h>
#include <map>
#include <memory>
#include <utility>
#include <vector>
#include "base/bind_helpers.h"
#include "base/macros.h"
#include "base/run_loop.h"
#include "base/strings/stringprintf.h"
#include "base/test/histogram_tester.h"
#include "base/test/scoped_feature_list.h"
#include "base/test/scoped_task_environment.h"
#include "base/test/test_simple_task_runner.h"
#include "components/favicon/core/favicon_driver.h"
#include "components/favicon/core/features.h"
#include "components/favicon/core/test/mock_favicon_service.h"
#include "skia/ext/image_operations.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/base/layout.h"
#include "ui/gfx/codec/png_codec.h"
#include "ui/gfx/favicon_size.h"
#include "ui/gfx/image/image.h"
namespace favicon {
namespace {
using favicon_base::FaviconRawBitmapResult;
using testing::AnyNumber;
using testing::Assign;
using testing::Contains;
using testing::ElementsAre;
using testing::InSequence;
using testing::Invoke;
using testing::IsEmpty;
using testing::Not;
using testing::Return;
using testing::_;
using IntVector = std::vector<int>;
using URLVector = std::vector<GURL>;
using BitmapVector = std::vector<SkBitmap>;
using SizeVector = std::vector<gfx::Size>;
constexpr favicon_base::IconType kFavicon = favicon_base::IconType::kFavicon;
constexpr favicon_base::IconType kTouchIcon =
favicon_base::IconType::kTouchIcon;
constexpr favicon_base::IconType kTouchPrecomposedIcon =
favicon_base::IconType::kTouchPrecomposedIcon;
constexpr favicon_base::IconType kWebManifestIcon =
favicon_base::IconType::kWebManifestIcon;
MATCHER_P2(ImageSizeIs, width, height, "") {
*result_listener << "where size is " << arg.Width() << "x" << arg.Height();
return arg.Size() == gfx::Size(width, height);
}
// |arg| is a gfx::Image.
MATCHER_P(ImageColorIs, expected_color, "") {
SkBitmap bitmap = arg.AsBitmap();
if (bitmap.empty()) {
*result_listener << "expected color but no bitmap data available";
return false;
}
SkColor actual_color = bitmap.getColor(1, 1);
if (actual_color != expected_color) {
*result_listener << "expected color "
<< base::StringPrintf("%08X", expected_color)
<< " but actual color is "
<< base::StringPrintf("%08X", actual_color);
return false;
}
return true;
}
SkBitmap CreateBitmapWithEdgeSize(int size, SkColor color) {
SkBitmap bmp;
bmp.allocN32Pixels(size, size);
bmp.eraseColor(color);
return bmp;
}
// Fill the given data buffer with valid png data.
std::vector<unsigned char> FillBitmapWithEdgeSize(int size, SkColor color) {
SkBitmap bitmap = CreateBitmapWithEdgeSize(size, color);
std::vector<unsigned char> output;
gfx::PNGCodec::EncodeBGRASkBitmap(bitmap, false, &output);
return output;
}
std::vector<FaviconRawBitmapResult> CreateRawBitmapResult(
const GURL& icon_url,
favicon_base::IconType icon_type = kFavicon,
bool expired = false,
int edge_size = gfx::kFaviconSize,
SkColor color = SK_ColorRED) {
scoped_refptr<base::RefCountedBytes> data(new base::RefCountedBytes());
data->data() = FillBitmapWithEdgeSize(edge_size, color);
FaviconRawBitmapResult bitmap_result;
bitmap_result.expired = expired;
bitmap_result.bitmap_data = data;
// Use a pixel size other than (0,0) as (0,0) has a special meaning.
bitmap_result.pixel_size = gfx::Size(edge_size, edge_size);
bitmap_result.icon_type = icon_type;
bitmap_result.icon_url = icon_url;
return {bitmap_result};
}
// Fake that implements the calls to FaviconHandler::Delegate's DownloadImage(),
// delegated to this class through MockDelegate.
class FakeImageDownloader {
public:
struct Response {
int http_status_code = 404;
BitmapVector bitmaps;
SizeVector original_bitmap_sizes;
};
// |downloads| must not be nullptr and must outlive this object.
explicit FakeImageDownloader(URLVector* downloads)
: downloads_(downloads), next_download_id_(1) {}
// Implementation of FaviconHalder::Delegate's DownloadImage(). If a given
// URL is not known (i.e. not previously added via Add()), it produces 404s.
int DownloadImage(const GURL& url,
int max_image_size,
FaviconHandler::Delegate::ImageDownloadCallback callback) {
downloads_->push_back(url);
Response response = responses_[url];
DCHECK_EQ(response.bitmaps.size(), response.original_bitmap_sizes.size());
// Apply maximum image size.
for (size_t i = 0; i < response.bitmaps.size(); ++i) {
if (response.bitmaps[i].width() > max_image_size ||
response.bitmaps[i].height() > max_image_size) {
response.bitmaps[i] = skia::ImageOperations::Resize(
response.bitmaps[i], skia::ImageOperations::RESIZE_LANCZOS3,
max_image_size, max_image_size);
}
}
int download_id = next_download_id_++;
base::OnceClosure bound_callback = base::BindOnce(
std::move(callback), download_id, response.http_status_code, url,
response.bitmaps, response.original_bitmap_sizes);
if (url == manual_callback_url_)
manual_callbacks_.push_back(std::move(bound_callback));
else
base::ThreadTaskRunnerHandle::Get()->PostTask(FROM_HERE,
std::move(bound_callback));
return download_id;
}
void Add(const GURL& icon_url,
const IntVector& original_sizes,
SkColor color = SK_ColorRED) {
Response response;
response.http_status_code = 200;
for (int size : original_sizes) {
response.original_bitmap_sizes.push_back(gfx::Size(size, size));
response.bitmaps.push_back(CreateBitmapWithEdgeSize(size, color));
}
responses_[icon_url] = response;
}
void AddError(const GURL& icon_url, int http_status_code) {
Response response;
response.http_status_code = http_status_code;
responses_[icon_url] = response;
}
// Disables automatic callback for |url|. This is useful for emulating a
// download taking a long time. The callback for DownloadImage() will be
// stored in |manual_callbacks_|.
void SetRunCallbackManuallyForUrl(const GURL& url) {
manual_callback_url_ = url;
}
// Returns whether an ongoing download exists for a url previously selected
// via SetRunCallbackManuallyForUrl().
bool HasPendingManualCallback() { return !manual_callbacks_.empty(); }
// Triggers responses for downloads previously selected for manual triggering
// via SetRunCallbackManuallyForUrl().
bool RunCallbackManually() {
if (!HasPendingManualCallback())
return false;
for (auto& callback : std::move(manual_callbacks_))
std::move(callback).Run();
return true;
}
private:
URLVector* downloads_;
int next_download_id_;
// URL to disable automatic callbacks for.
GURL manual_callback_url_;
// Callback for DownloadImage() request for |manual_callback_url_|.
std::vector<base::OnceClosure> manual_callbacks_;
// Registered responses.
std::map<GURL, Response> responses_;
DISALLOW_COPY_AND_ASSIGN(FakeImageDownloader);
};
// Fake that implements the calls to FaviconHandler::Delegate's
// DownloadManifest(), delegated to this class through MockDelegate.
class FakeManifestDownloader {
public:
struct Response {
std::vector<favicon::FaviconURL> favicon_urls;
};
// |downloads| must not be nullptr and must outlive this object.
explicit FakeManifestDownloader(URLVector* downloads)
: downloads_(downloads) {}
// Implementation of FaviconHalder::Delegate's DownloadManifest(). If a given
// URL is not known (i.e. not previously added via Add()), it produces 404s.
void DownloadManifest(
const GURL& url,
FaviconHandler::Delegate::ManifestDownloadCallback callback) {
downloads_->push_back(url);
const Response& response = responses_[url];
base::OnceClosure bound_callback =
base::BindOnce(std::move(callback), response.favicon_urls);
if (url == manual_callback_url_)
manual_callbacks_.push_back(std::move(bound_callback));
else
base::ThreadTaskRunnerHandle::Get()->PostTask(FROM_HERE,
std::move(bound_callback));
}
void Add(const GURL& manifest_url,
const std::vector<favicon::FaviconURL>& favicon_urls) {
Response response;
response.favicon_urls = favicon_urls;
responses_[manifest_url] = response;
}
void AddError(const GURL& manifest_url) {
responses_[manifest_url] = Response();
}
// Disables automatic callback for |url|. This is useful for emulating a
// download taking a long time. The callback for DownloadManifest() will be
// stored in |manual_callback_|.
void SetRunCallbackManuallyForUrl(const GURL& url) {
manual_callback_url_ = url;
}
// Returns whether an ongoing download exists for a url previously selected
// via SetRunCallbackManuallyForUrl().
bool HasPendingManualCallback() { return !manual_callbacks_.empty(); }
// Triggers responses for downloads previously selected for manual triggering
// via SetRunCallbackManuallyForUrl().
bool RunCallbackManually() {
if (!HasPendingManualCallback())
return false;
for (auto& callback : std::move(manual_callbacks_))
std::move(callback).Run();
return true;
}
private:
URLVector* downloads_;
// URL to disable automatic callbacks for.
GURL manual_callback_url_;
// Callback for DownloadManifest() request for |manual_callback_url_|.
std::vector<base::OnceClosure> manual_callbacks_;
// Registered responses.
std::map<GURL, Response> responses_;
DISALLOW_COPY_AND_ASSIGN(FakeManifestDownloader);
};
class MockDelegate : public FaviconHandler::Delegate {
public:
MockDelegate()
: fake_image_downloader_(&downloads_),
fake_manifest_downloader_(&downloads_) {
}
int DownloadImage(const GURL& url,
int max_image_size,
ImageDownloadCallback callback) override {
return fake_image_downloader_.DownloadImage(url, max_image_size,
std::move(callback));
}
void DownloadManifest(const GURL& url,
ManifestDownloadCallback callback) override {
fake_manifest_downloader_.DownloadManifest(url, std::move(callback));
}
MOCK_METHOD0(IsOffTheRecord, bool());
MOCK_METHOD1(IsBookmarked, bool(const GURL& url));
MOCK_METHOD5(OnFaviconUpdated,
void(const GURL& page_url,
FaviconDriverObserver::NotificationIconType type,
const GURL& icon_url,
bool icon_url_changed,
const gfx::Image& image));
MOCK_METHOD2(OnFaviconDeleted,
void(const GURL& page_url,
FaviconDriverObserver::NotificationIconType type));
FakeImageDownloader& fake_image_downloader() {
return fake_image_downloader_;
}
FakeManifestDownloader& fake_manifest_downloader() {
return fake_manifest_downloader_;
}
// Returns pending and completed download URLs.
const URLVector& downloads() const { return downloads_; }
void ClearDownloads() { downloads_.clear(); }
private:
// Pending and completed download URLs.
URLVector downloads_;
FakeImageDownloader fake_image_downloader_;
FakeManifestDownloader fake_manifest_downloader_;
};
// FakeFaviconService mimics a FaviconService backend that allows setting up
// test data stored via Store(). If Store() has not been called for a
// particular URL, the callback is called with empty database results.
class FakeFaviconService {
public:
FakeFaviconService()
: manual_callback_task_runner_(new base::TestSimpleTaskRunner()) {}
// Stores favicon with bitmap data in |results| at |page_url| and |icon_url|.
void Store(const GURL& page_url,
const GURL& icon_url,
const std::vector<favicon_base::FaviconRawBitmapResult>& result) {
results_[icon_url] = result;
results_[page_url] = result;
}
// Returns pending and completed database request URLs.
const URLVector& db_requests() const { return db_requests_; }
void ClearDbRequests() { db_requests_.clear(); }
base::CancelableTaskTracker::TaskId GetFavicon(
const GURL& icon_url,
favicon_base::IconType icon_type,
int desired_size_in_dip,
const favicon_base::FaviconResultsCallback& callback,
base::CancelableTaskTracker* tracker) {
return GetFaviconForPageOrIconURL(icon_url, callback, tracker);
}
base::CancelableTaskTracker::TaskId GetFaviconForPageURL(
const GURL& page_url,
const favicon_base::IconTypeSet& icon_types,
int desired_size_in_dip,
const favicon_base::FaviconResultsCallback& callback,
base::CancelableTaskTracker* tracker) {
return GetFaviconForPageOrIconURL(page_url, callback, tracker);
}
base::CancelableTaskTracker::TaskId UpdateFaviconMappingsAndFetch(
const base::flat_set<GURL>& page_urls,
const GURL& icon_url,
favicon_base::IconType icon_type,
int desired_size_in_dip,
const favicon_base::FaviconResultsCallback& callback,
base::CancelableTaskTracker* tracker) {
return GetFaviconForPageOrIconURL(icon_url, callback, tracker);
}
// Disables automatic callback for |url|. This is useful for emulating a
// DB lookup taking a long time. The callback for
// GetFaviconForPageOrIconURL() will be stored in |manual_callback_|.
void SetRunCallbackManuallyForUrl(const GURL& url) {
manual_callback_url_ = url;
}
// Returns whether an ongoing lookup exists for a url previously selected
// via SetRunCallbackManuallyForUrl().
bool HasPendingManualCallback() {
return manual_callback_task_runner_->HasPendingTask();
}
// Triggers the response for a lookup previously selected for manual
// triggering via SetRunCallbackManuallyForUrl().
bool RunCallbackManually() {
if (!HasPendingManualCallback())
return false;
manual_callback_task_runner_->RunPendingTasks();
return true;
}
private:
base::CancelableTaskTracker::TaskId GetFaviconForPageOrIconURL(
const GURL& page_or_icon_url,
const favicon_base::FaviconResultsCallback& callback,
base::CancelableTaskTracker* tracker) {
db_requests_.push_back(page_or_icon_url);
base::Closure bound_callback =
base::Bind(callback, results_[page_or_icon_url]);
if (page_or_icon_url != manual_callback_url_) {
return tracker->PostTask(base::ThreadTaskRunnerHandle::Get().get(),
FROM_HERE, bound_callback);
}
// We use PostTaskAndReply() to cause |callback| being run in the current
// TaskRunner.
return tracker->PostTaskAndReply(manual_callback_task_runner_.get(),
FROM_HERE, base::DoNothing(),
bound_callback);
}
std::map<GURL, std::vector<favicon_base::FaviconRawBitmapResult>> results_;
URLVector db_requests_;
// URL to disable automatic callbacks for.
GURL manual_callback_url_;
// Callback for GetFaviconForPageOrIconURL() request for
// |manual_callback_url_|.
scoped_refptr<base::TestSimpleTaskRunner> manual_callback_task_runner_;
DISALLOW_COPY_AND_ASSIGN(FakeFaviconService);
};
// MockFaviconService subclass that delegates DB reads to FakeFaviconService.
class MockFaviconServiceWithFake : public MockFaviconService {
public:
MockFaviconServiceWithFake() {
// Delegate the various methods that read from the DB.
ON_CALL(*this, GetFavicon(_, _, _, _, _))
.WillByDefault(Invoke(&fake_, &FakeFaviconService::GetFavicon));
ON_CALL(*this, GetFaviconForPageURL(_, _, _, _, _))
.WillByDefault(
Invoke(&fake_, &FakeFaviconService::GetFaviconForPageURL));
ON_CALL(*this, UpdateFaviconMappingsAndFetch(_, _, _, _, _, _))
.WillByDefault(
Invoke(&fake_, &FakeFaviconService::UpdateFaviconMappingsAndFetch));
}
FakeFaviconService* fake() { return &fake_; }
private:
FakeFaviconService fake_;
DISALLOW_COPY_AND_ASSIGN(MockFaviconServiceWithFake);
};
class FaviconHandlerTest : public testing::Test {
protected:
const std::vector<gfx::Size> kEmptySizes;
// Some known icons for which download will succeed.
const GURL kPageURL = GURL("http://www.google.com");
const GURL kIconURL10x10 = GURL("http://www.google.com/favicon10x10");
const GURL kIconURL12x12 = GURL("http://www.google.com/favicon12x12");
const GURL kIconURL16x16 = GURL("http://www.google.com/favicon16x16");
const GURL kIconURL64x64 = GURL("http://www.google.com/favicon64x64");
FaviconHandlerTest()
: scoped_task_environment_(
base::test::ScopedTaskEnvironment::MainThreadType::UI) {
// Register various known icon URLs.
delegate_.fake_image_downloader().Add(kIconURL10x10, IntVector{10});
delegate_.fake_image_downloader().Add(kIconURL12x12, IntVector{12});
delegate_.fake_image_downloader().Add(kIconURL16x16, IntVector{16});
delegate_.fake_image_downloader().Add(kIconURL64x64, IntVector{64});
// The score computed by SelectFaviconFrames() is dependent on the supported
// scale factors of the platform. It is used for determining the goodness of
// a downloaded bitmap in FaviconHandler::OnDidDownloadFavicon().
// Force the values of the scale factors so that the tests produce the same
// results on all platforms.
scoped_set_supported_scale_factors_.reset(
new ui::test::ScopedSetSupportedScaleFactors({ui::SCALE_FACTOR_100P}));
}
bool VerifyAndClearExpectations() {
base::RunLoop().RunUntilIdle();
favicon_service_.fake()->ClearDbRequests();
delegate_.ClearDownloads();
return testing::Mock::VerifyAndClearExpectations(&favicon_service_) &&
testing::Mock::VerifyAndClearExpectations(&delegate_);
}
// Creates a new handler and feeds in the page URL and the candidates.
// Returns the handler in case tests want to exercise further steps.
std::unique_ptr<FaviconHandler> RunHandlerWithCandidates(
FaviconDriverObserver::NotificationIconType handler_type,
const std::vector<FaviconURL>& candidates,
const GURL& manifest_url = GURL()) {
auto handler = std::make_unique<FaviconHandler>(&favicon_service_,
&delegate_, handler_type);
handler->FetchFavicon(kPageURL, /*is_same_document=*/false);
// The first RunUntilIdle() causes the FaviconService lookups be faster than
// OnUpdateCandidates(), which is the most likely scenario.
base::RunLoop().RunUntilIdle();
handler->OnUpdateCandidates(kPageURL, candidates, manifest_url);
base::RunLoop().RunUntilIdle();
return handler;
}
// Same as above, but for the simplest case where all types are kFavicon and
// no sizes are provided, using a FaviconHandler of type NON_TOUCH_16_DIP.
std::unique_ptr<FaviconHandler> RunHandlerWithSimpleFaviconCandidates(
const std::vector<GURL>& urls,
const GURL& manifest_url = GURL()) {
std::vector<favicon::FaviconURL> candidates;
for (const GURL& url : urls) {
candidates.emplace_back(url, kFavicon, kEmptySizes);
}
return RunHandlerWithCandidates(FaviconDriverObserver::NON_TOUCH_16_DIP,
candidates, manifest_url);
}
base::test::ScopedTaskEnvironment scoped_task_environment_;
std::unique_ptr<ui::test::ScopedSetSupportedScaleFactors>
scoped_set_supported_scale_factors_;
testing::NiceMock<MockFaviconServiceWithFake> favicon_service_;
testing::NiceMock<MockDelegate> delegate_;
};
TEST_F(FaviconHandlerTest, GetFaviconFromHistory) {
base::HistogramTester histogram_tester;
const GURL kIconURL("http://www.google.com/favicon");
favicon_service_.fake()->Store(kPageURL, kIconURL,
CreateRawBitmapResult(kIconURL));
EXPECT_CALL(delegate_, OnFaviconUpdated(
kPageURL, FaviconDriverObserver::NON_TOUCH_16_DIP,
kIconURL, /*icon_url_changed=*/true, _));
RunHandlerWithSimpleFaviconCandidates({kIconURL});
EXPECT_THAT(delegate_.downloads(), IsEmpty());
}
// Test that UpdateFaviconsAndFetch() is called with the appropriate parameters
// when there is no data in the database for the page URL.
TEST_F(FaviconHandlerTest, UpdateFaviconMappingsAndFetch) {
EXPECT_CALL(favicon_service_,
UpdateFaviconMappingsAndFetch(base::flat_set<GURL>{kPageURL},
kIconURL16x16, kFavicon,
/*desired_size_in_dip=*/16, _, _));
RunHandlerWithSimpleFaviconCandidates({kIconURL16x16});
}
// Test that we don't try to delete favicon mappings when a page URL is not in
// history even if the page lists no favicons.
TEST_F(FaviconHandlerTest, DoNotDeleteFaviconMappingsIfNotInHistory) {
const GURL kIconURL("http://www.google.com/favicon");
EXPECT_CALL(favicon_service_, DeleteFaviconMappings(_, _)).Times(0);
EXPECT_CALL(delegate_, OnFaviconDeleted(_, _)).Times(0);
RunHandlerWithSimpleFaviconCandidates(URLVector());
}
// Test that favicon mappings are deleted when:
// - There is data in the favicon database for the page URL.
// - The page lists no candidates.
// AND
// - FaviconService::OnFaviconDataForManifestFromFaviconService() runs before
// FaviconHandler::OnUpdateCandidates() is called.
TEST_F(FaviconHandlerTest, DeleteFaviconMappingsIfCandidatesSlower) {
const GURL kIconURL("http://www.google.com/favicon");
favicon_service_.fake()->Store(kPageURL, kIconURL,
CreateRawBitmapResult(kIconURL));
// Defer the database lookup completion to control the exact timing.
favicon_service_.fake()->SetRunCallbackManuallyForUrl(kPageURL);
EXPECT_CALL(delegate_, OnFaviconDeleted(_, _)).Times(0);
EXPECT_CALL(favicon_service_, DeleteFaviconMappings(_, _)).Times(0);
FaviconHandler handler(&favicon_service_, &delegate_,
FaviconDriverObserver::NON_TOUCH_16_DIP);
handler.FetchFavicon(kPageURL, /*is_same_document=*/false);
base::RunLoop().RunUntilIdle();
// Database lookup for |kPageURL| is ongoing.
ASSERT_TRUE(favicon_service_.fake()->HasPendingManualCallback());
// Causes FaviconService lookups be faster than OnUpdateCandidates().
ASSERT_TRUE(favicon_service_.fake()->RunCallbackManually());
ASSERT_TRUE(VerifyAndClearExpectations());
EXPECT_CALL(
delegate_,
OnFaviconDeleted(kPageURL, FaviconDriverObserver::NON_TOUCH_16_DIP));
EXPECT_CALL(favicon_service_,
DeleteFaviconMappings(base::flat_set<GURL>{kPageURL}, kFavicon));
// Feed in (zero) candidates now that the database lookup is completed.
handler.OnUpdateCandidates(kPageURL, std::vector<FaviconURL>(),
/*manifest_url=*/GURL());
base::RunLoop().RunUntilIdle();
}
// Test that favicon mappings are deleted when:
// - There is data in the favicon database for the page URL.
// - The page lists no candidates.
// AND
// - FaviconHandler::OnUpdateCandidates() is called before
// FaviconService::OnFaviconDataForManifestFromFaviconService() runs.
TEST_F(FaviconHandlerTest, DeleteFaviconMappingsIfCandidatesFaster) {
const GURL kIconURL("http://www.google.com/favicon");
favicon_service_.fake()->Store(kPageURL, kIconURL,
CreateRawBitmapResult(kIconURL));
// Defer the database lookup completion to control the exact timing.
favicon_service_.fake()->SetRunCallbackManuallyForUrl(kPageURL);
EXPECT_CALL(delegate_, OnFaviconDeleted(_, _)).Times(0);
EXPECT_CALL(favicon_service_, DeleteFaviconMappings(_, _)).Times(0);
FaviconHandler handler(&favicon_service_, &delegate_,
FaviconDriverObserver::NON_TOUCH_16_DIP);
handler.FetchFavicon(kPageURL, /*is_same_document=*/false);
base::RunLoop().RunUntilIdle();
// Feed in (zero) candidates before completing the database lookup.
handler.OnUpdateCandidates(kPageURL, std::vector<FaviconURL>(),
/*manifest_url=*/GURL());
ASSERT_TRUE(VerifyAndClearExpectations());
// Database lookup for |kPageURL| is ongoing.
ASSERT_TRUE(favicon_service_.fake()->HasPendingManualCallback());
EXPECT_CALL(
delegate_,
OnFaviconDeleted(kPageURL, FaviconDriverObserver::NON_TOUCH_16_DIP));
EXPECT_CALL(favicon_service_,
DeleteFaviconMappings(base::flat_set<GURL>{kPageURL}, kFavicon));
// Complete the lookup for |kPageURL|.
ASSERT_TRUE(favicon_service_.fake()->RunCallbackManually());
base::RunLoop().RunUntilIdle();
}
// Test that favicon mappings are deleted when a page in history lists a
// candidate that is expired and is known to return a 404.
TEST_F(FaviconHandlerTest, DeleteFaviconMappingsDespitePrior404) {
const GURL kIconURL("http://www.google.com/favicon");
favicon_service_.fake()->Store(
kPageURL, kIconURL,
CreateRawBitmapResult(kIconURL, kFavicon, /*expired=*/true));
ON_CALL(favicon_service_, WasUnableToDownloadFavicon(kIconURL))
.WillByDefault(Return(true));
EXPECT_CALL(
delegate_,
OnFaviconDeleted(kPageURL, FaviconDriverObserver::NON_TOUCH_16_DIP));
EXPECT_CALL(favicon_service_,
DeleteFaviconMappings(base::flat_set<GURL>{kPageURL}, kFavicon));
RunHandlerWithSimpleFaviconCandidates({kIconURL});
}
// Test that favicon mappings are deleted for a page in history, when all icons
// listed in the page return a 404.
TEST_F(FaviconHandlerTest, DeleteFaviconMappingsDueTo404) {
const GURL kIconURLInHistory("http://www.google.com/favicon-in-history");
const GURL k404IconURL("http://www.google.com/404.png");
favicon_service_.fake()->Store(kPageURL, kIconURLInHistory,
CreateRawBitmapResult(kIconURLInHistory));
EXPECT_CALL(favicon_service_,
DeleteFaviconMappings(base::flat_set<GURL>{kPageURL}, kFavicon));
RunHandlerWithSimpleFaviconCandidates({k404IconURL});
}
// Test that we don't try to delete favicon mappings when a page URL is not in
// history even if all icons listed in the page return a 404.
TEST_F(FaviconHandlerTest, DoNotDeleteFaviconMappingsIfNotInHistoryDespite404) {
const GURL k404IconURL("http://www.google.com/404.png");
EXPECT_CALL(favicon_service_, DeleteFaviconMappings(_, _)).Times(0);
RunHandlerWithSimpleFaviconCandidates({k404IconURL});
}
// Test that favicon mappings are not deleted for a page in history when all
// icons listed in the page return a 503.
TEST_F(FaviconHandlerTest, DoNotDeleteFaviconMappingsDueTo503) {
const GURL kIconURLInHistory("http://www.google.com/favicon-in-history");
const GURL k503IconURL("http://www.google.com/503.png");
delegate_.fake_image_downloader().AddError(k503IconURL, 503);
favicon_service_.fake()->Store(kPageURL, kIconURLInHistory,
CreateRawBitmapResult(kIconURLInHistory));
EXPECT_CALL(favicon_service_, DeleteFaviconMappings(_, _)).Times(0);
RunHandlerWithSimpleFaviconCandidates({k503IconURL});
}
// Test that UpdateFaviconsAndFetch() is called with the appropriate parameters
// when there is no data in the database for the page URL, for the case where
// multiple page URLs exist due to a quick in-same-document navigation (e.g.
// fragment navigation).
TEST_F(FaviconHandlerTest, UpdateFaviconMappingsAndFetchWithMultipleURLs) {
const GURL kDifferentPageURL = GURL("http://www.google.com/other");
EXPECT_CALL(favicon_service_,
UpdateFaviconMappingsAndFetch(
base::flat_set<GURL>{kPageURL, kDifferentPageURL},
kIconURL16x16, _, _, _, _));
std::unique_ptr<FaviconHandler> handler = std::make_unique<FaviconHandler>(
&favicon_service_, &delegate_, FaviconDriverObserver::NON_TOUCH_16_DIP);
handler->FetchFavicon(kPageURL, /*is_same_document=*/false);
base::RunLoop().RunUntilIdle();
// Load a new URL (same document) without feeding any candidates for the first
// URL.
handler->FetchFavicon(kDifferentPageURL, /*is_same_document=*/true);
base::RunLoop().RunUntilIdle();
// Feed in candidates for the second URL.
handler->OnUpdateCandidates(
kDifferentPageURL, {FaviconURL(kIconURL16x16, kFavicon, kEmptySizes)},
/*manifest_url=*/GURL());
base::RunLoop().RunUntilIdle();
}
// Test that CloneFaviconMappingsForPages() is called for the simplest case,
// i.e. a single page without redirect URLs to update mappings for (no known
// in-same-document navigation). This is important in case there are server-side
// redirects to update (that are only known within HistoryService).
TEST_F(FaviconHandlerTest, CloneFaviconMappingsForPageInHistory) {
favicon_service_.fake()->Store(kPageURL, kIconURL16x16,
CreateRawBitmapResult(kIconURL16x16));
EXPECT_CALL(favicon_service_,
CloneFaviconMappingsForPages(
kPageURL, favicon_base::IconTypeSet({kFavicon}),
base::flat_set<GURL>({kPageURL})));
std::unique_ptr<FaviconHandler> handler = std::make_unique<FaviconHandler>(
&favicon_service_, &delegate_, FaviconDriverObserver::NON_TOUCH_16_DIP);
handler->FetchFavicon(kPageURL, /*is_same_document=*/false);
base::RunLoop().RunUntilIdle();
}
// Test that CloneFaviconMappingsForPages() is called when there is data in the
// database for the page URL, for the case where multiple page URLs exist due to
// a quick in-same-document navigation (e.g. fragment navigation).
// FaviconService should be told to propagate the mappings from the last page
// URL (lookup hit) to the rest of the URLs.
TEST_F(FaviconHandlerTest, CloneFaviconMappingsWithMultipleURLs) {
const GURL kPageURLInHistory = GURL("http://www.google.com/other");
favicon_service_.fake()->Store(kPageURLInHistory, kIconURL16x16,
CreateRawBitmapResult(kIconURL16x16));
std::unique_ptr<FaviconHandler> handler = std::make_unique<FaviconHandler>(
&favicon_service_, &delegate_, FaviconDriverObserver::NON_TOUCH_16_DIP);
handler->FetchFavicon(kPageURL, /*is_same_document=*/false);
base::RunLoop().RunUntilIdle();
EXPECT_CALL(favicon_service_,
CloneFaviconMappingsForPages(
kPageURLInHistory, favicon_base::IconTypeSet({kFavicon}),
base::flat_set<GURL>({kPageURL, kPageURLInHistory})));
handler->FetchFavicon(kPageURLInHistory, /*is_same_document=*/true);
base::RunLoop().RunUntilIdle();
}
// Test that CloneFaviconMappingsForPages() is not called for incognito tabs.
TEST_F(FaviconHandlerTest, NotCloneFaviconMappingsInIncognito) {
ON_CALL(delegate_, IsOffTheRecord()).WillByDefault(Return(true));
favicon_service_.fake()->Store(kPageURL, kIconURL16x16,
CreateRawBitmapResult(kIconURL16x16));
EXPECT_CALL(favicon_service_, CloneFaviconMappingsForPages(_, _, _)).Times(0);
std::unique_ptr<FaviconHandler> handler = std::make_unique<FaviconHandler>(
&favicon_service_, &delegate_, FaviconDriverObserver::NON_TOUCH_16_DIP);
handler->FetchFavicon(kPageURL, /*is_same_document=*/false);
base::RunLoop().RunUntilIdle();
}
// Test that the FaviconHandler process finishes when:
// - There is data in the database for neither the page URL nor the icon URL.
// AND
// - FaviconService::GetFaviconForPageURL() callback returns before
// FaviconHandler::OnUpdateCandidates() is called.
TEST_F(FaviconHandlerTest, DownloadUnknownFaviconIfCandidatesSlower) {
// Defer the database lookup completion to control the exact timing.
favicon_service_.fake()->SetRunCallbackManuallyForUrl(kPageURL);
EXPECT_CALL(favicon_service_, SetFavicons(_, _, _, _)).Times(0);
EXPECT_CALL(delegate_, OnFaviconUpdated(_, _, _, _, _)).Times(0);
FaviconHandler handler(&favicon_service_, &delegate_,
FaviconDriverObserver::NON_TOUCH_16_DIP);
handler.FetchFavicon(kPageURL, /*is_same_document=*/false);
base::RunLoop().RunUntilIdle();
// Database lookup for |kPageURL| is ongoing.
ASSERT_TRUE(favicon_service_.fake()->HasPendingManualCallback());
// Causes FaviconService lookups be faster than OnUpdateCandidates().
ASSERT_TRUE(favicon_service_.fake()->RunCallbackManually());
ASSERT_TRUE(VerifyAndClearExpectations());
EXPECT_CALL(favicon_service_,
SetFavicons(base::flat_set<GURL>{kPageURL}, kIconURL16x16,
kFavicon, ImageSizeIs(16, 16)));
EXPECT_CALL(delegate_, OnFaviconUpdated(
kPageURL, FaviconDriverObserver::NON_TOUCH_16_DIP,
kIconURL16x16, /*icon_url_changed=*/true, _));
// Feed in favicons now that the database lookup is completed.
handler.OnUpdateCandidates(
kPageURL, {FaviconURL(kIconURL16x16, kFavicon, kEmptySizes)}, GURL());
base::RunLoop().RunUntilIdle();
EXPECT_THAT(favicon_service_.fake()->db_requests(),
ElementsAre(kIconURL16x16));
EXPECT_THAT(delegate_.downloads(), ElementsAre(kIconURL16x16));
}
// Test that the FaviconHandler process finishes when:
// - There is data in the database for neither the page URL nor the icon URL.
// AND
// - FaviconService::GetFaviconForPageURL() callback returns after
// FaviconHandler::OnUpdateCandidates() is called.
TEST_F(FaviconHandlerTest, DownloadUnknownFaviconIfCandidatesFaster) {
// Defer the database lookup completion to control the exact timing.
favicon_service_.fake()->SetRunCallbackManuallyForUrl(kPageURL);
EXPECT_CALL(favicon_service_, SetFavicons(_, _, _, _)).Times(0);
EXPECT_CALL(delegate_, OnFaviconUpdated(_, _, _, _, _)).Times(0);
FaviconHandler handler(&favicon_service_, &delegate_,
FaviconDriverObserver::NON_TOUCH_16_DIP);
handler.FetchFavicon(kPageURL, /*is_same_document=*/false);
base::RunLoop().RunUntilIdle();
// Feed in favicons before completing the database lookup.
handler.OnUpdateCandidates(
kPageURL, {FaviconURL(kIconURL16x16, kFavicon, kEmptySizes)}, GURL());
ASSERT_TRUE(VerifyAndClearExpectations());
// Database lookup for |kPageURL| is ongoing.
ASSERT_TRUE(favicon_service_.fake()->HasPendingManualCallback());
EXPECT_CALL(favicon_service_,
SetFavicons(base::flat_set<GURL>{kPageURL}, kIconURL16x16,
kFavicon, ImageSizeIs(16, 16)));
EXPECT_CALL(delegate_, OnFaviconUpdated(_, _, kIconURL16x16, _, _));
// Complete the lookup for |kPageURL|.
ASSERT_TRUE(favicon_service_.fake()->RunCallbackManually());
base::RunLoop().RunUntilIdle();
EXPECT_THAT(favicon_service_.fake()->db_requests(),
ElementsAre(kIconURL16x16));
EXPECT_THAT(delegate_.downloads(), ElementsAre(kIconURL16x16));
}
// Test that the FaviconHandler process does not save anything to the database
// for incognito tabs.
TEST_F(FaviconHandlerTest, DownloadUnknownFaviconInIncognito) {
ON_CALL(delegate_, IsOffTheRecord()).WillByDefault(Return(true));
// No writes expected.
EXPECT_CALL(favicon_service_, UpdateFaviconMappingsAndFetch(_, _, _, _, _, _))
.Times(0);
EXPECT_CALL(favicon_service_, SetFavicons(_, _, _, _)).Times(0);
EXPECT_CALL(delegate_, OnFaviconUpdated(_, _, kIconURL16x16, _, _));
RunHandlerWithSimpleFaviconCandidates({kIconURL16x16});
EXPECT_THAT(delegate_.downloads(), ElementsAre(kIconURL16x16));
EXPECT_THAT(favicon_service_.fake()->db_requests(),
ElementsAre(kPageURL, kIconURL16x16));
}
// Test that favicon mappings are not deleted in incognito even if the page
// lists no candidates.
TEST_F(FaviconHandlerTest, DoNotDeleteFaviconMappingsInIncognito) {
const GURL kIconURL("http://www.google.com/favicon");
ON_CALL(delegate_, IsOffTheRecord()).WillByDefault(Return(true));
favicon_service_.fake()->Store(kPageURL, kIconURL,
CreateRawBitmapResult(kIconURL));
EXPECT_CALL(favicon_service_, DeleteFaviconMappings(_, _)).Times(0);
EXPECT_CALL(
delegate_,
OnFaviconDeleted(kPageURL, FaviconDriverObserver::NON_TOUCH_16_DIP));
RunHandlerWithSimpleFaviconCandidates(URLVector());
}
// Test that the icon is redownloaded if the icon cached for the page URL
// expired.
TEST_F(FaviconHandlerTest, RedownloadExpiredPageUrlFavicon) {
const GURL kIconURL("http://www.google.com/favicon");
const SkColor kOldColor = SK_ColorBLUE;
const SkColor kNewColor = SK_ColorGREEN;
favicon_service_.fake()->Store(
kPageURL, kIconURL,
CreateRawBitmapResult(kIconURL, kFavicon, /*expired=*/true,
gfx::kFaviconSize, kOldColor));
delegate_.fake_image_downloader().Add(kIconURL, IntVector{gfx::kFaviconSize},
kNewColor);
EXPECT_CALL(favicon_service_,
SetFavicons(_, kIconURL, _, ImageColorIs(kNewColor)));
InSequence seq;
EXPECT_CALL(delegate_,
OnFaviconUpdated(_, _, kIconURL, _, ImageColorIs(kOldColor)));
EXPECT_CALL(delegate_,
OnFaviconUpdated(_, _, kIconURL, _, ImageColorIs(kNewColor)));
RunHandlerWithSimpleFaviconCandidates({kIconURL});
// We know from the |kPageUrl| database request that |kIconURL| has expired. A
// second request for |kIconURL| should not have been made because it is
// redundant.
EXPECT_THAT(favicon_service_.fake()->db_requests(), ElementsAre(kPageURL));
EXPECT_THAT(delegate_.downloads(), ElementsAre(kIconURL));
}
// Test that FaviconHandler requests the new data when:
// - There is valid data in the database for the page URL.
// AND
// - The icon URL used by the page has changed.
// AND
// - There is no data in database for the new icon URL.
TEST_F(FaviconHandlerTest, UpdateAndDownloadFavicon) {
const GURL kOldIconURL("http://www.google.com/old_favicon");
const GURL kNewIconURL = kIconURL16x16;
favicon_service_.fake()->Store(kPageURL, kOldIconURL,
CreateRawBitmapResult(kOldIconURL));
InSequence seq;
EXPECT_CALL(delegate_, OnFaviconUpdated(_, _, kOldIconURL, _, _));
EXPECT_CALL(favicon_service_, SetFavicons(_, kNewIconURL, _, _));
EXPECT_CALL(delegate_, OnFaviconUpdated(_, _, kNewIconURL, _, _));
RunHandlerWithSimpleFaviconCandidates({kNewIconURL});
EXPECT_THAT(delegate_.downloads(), ElementsAre(kNewIconURL));
}
// If there is data for the page URL in history which is invalid, test that:
// - The invalid data is not sent to the UI.
// - The icon is redownloaded.
TEST_F(FaviconHandlerTest, FaviconInHistoryInvalid) {
const GURL kIconURL("http://www.google.com/favicon");
delegate_.fake_image_downloader().Add(kIconURL, IntVector{gfx::kFaviconSize},
SK_ColorBLUE);
// Set non-empty but invalid data.
std::vector<FaviconRawBitmapResult> bitmap_result =
CreateRawBitmapResult(kIconURL);
// Empty bitmap data is invalid.
bitmap_result[0].bitmap_data = new base::RefCountedBytes();
favicon_service_.fake()->Store(kPageURL, kIconURL, bitmap_result);
EXPECT_CALL(delegate_,
OnFaviconUpdated(_, _, kIconURL, _, ImageColorIs(SK_ColorBLUE)));
RunHandlerWithSimpleFaviconCandidates({kIconURL});
EXPECT_THAT(favicon_service_.fake()->db_requests(), ElementsAre(kPageURL));
EXPECT_THAT(delegate_.downloads(), ElementsAre(kIconURL));
}
// Test that no downloads are done if a user visits a page which changed its
// favicon URL to a favicon URL which is already cached in the database.
TEST_F(FaviconHandlerTest, UpdateFavicon) {
const GURL kSomePreviousPageURL("https://www.google.com/previous");
const GURL kIconURL("http://www.google.com/favicon");
const GURL kNewIconURL("http://www.google.com/new_favicon");
favicon_service_.fake()->Store(kPageURL, kIconURL,
CreateRawBitmapResult(kIconURL));
favicon_service_.fake()->Store(kSomePreviousPageURL, kNewIconURL,
CreateRawBitmapResult(kNewIconURL));
EXPECT_CALL(favicon_service_, SetFavicons(_, _, _, _)).Times(0);
InSequence seq;
EXPECT_CALL(delegate_, OnFaviconUpdated(_, _, kIconURL, _, _));
EXPECT_CALL(delegate_, OnFaviconUpdated(_, _, kNewIconURL, _, _));
RunHandlerWithSimpleFaviconCandidates({kNewIconURL});
EXPECT_THAT(favicon_service_.fake()->db_requests(),
ElementsAre(kPageURL, kNewIconURL));
EXPECT_THAT(delegate_.downloads(), IsEmpty());
}
TEST_F(FaviconHandlerTest, Download2ndFaviconURLCandidate) {
const GURL kIconURLReturning500("http://www.google.com/500.png");
delegate_.fake_image_downloader().AddError(kIconURLReturning500, 500);
favicon_service_.fake()->Store(
kPageURL, kIconURL64x64,
CreateRawBitmapResult(kIconURL64x64, kTouchIcon,
/*expired=*/true));
EXPECT_CALL(delegate_,
OnFaviconUpdated(kPageURL, FaviconDriverObserver::TOUCH_LARGEST,
kIconURL64x64, /*icon_url_changed=*/true, _));
EXPECT_CALL(delegate_,
OnFaviconUpdated(kPageURL, FaviconDriverObserver::TOUCH_LARGEST,
kIconURL64x64, /*icon_url_changed=*/false, _));
RunHandlerWithCandidates(
FaviconDriverObserver::TOUCH_LARGEST,
{
FaviconURL(kIconURLReturning500, kTouchPrecomposedIcon, kEmptySizes),
FaviconURL(kIconURL64x64, kTouchIcon, kEmptySizes),
});
// First download fails, second succeeds.
EXPECT_THAT(delegate_.downloads(),
ElementsAre(kIconURLReturning500, kIconURL64x64));
}
// Test that download data for icon URLs other than the current favicon
// candidate URLs is ignored. This test tests the scenario where a download is
// in flight when FaviconHandler::OnUpdateCandidates() is called.
// TODO(mastiz): Make this test deal with FaviconURLs of type
// favicon_base::IconType::kFavicon and add new ones like
// OnlyDownloadMatchingIconType and CallSetFaviconsWithCorrectIconType.
TEST_F(FaviconHandlerTest, UpdateDuringDownloading) {
const GURL kIconURL1("http://www.google.com/favicon");
const GURL kIconURL2 = kIconURL16x16;
const GURL kIconURL3 = kIconURL12x12;
// Defer the download completion such that RunUntilIdle() doesn't complete
// the download.
delegate_.fake_image_downloader().SetRunCallbackManuallyForUrl(kIconURL1);
delegate_.fake_image_downloader().Add(kIconURL1, IntVector{16});
delegate_.fake_image_downloader().Add(kIconURL3, IntVector{12});
std::unique_ptr<FaviconHandler> handler =
RunHandlerWithSimpleFaviconCandidates({kIconURL1, kIconURL2});
ASSERT_TRUE(VerifyAndClearExpectations());
ASSERT_TRUE(delegate_.fake_image_downloader().HasPendingManualCallback());
// Favicon update should invalidate the ongoing download.
EXPECT_CALL(favicon_service_, SetFavicons(_, kIconURL3, _, _));
EXPECT_CALL(delegate_, OnFaviconUpdated(_, _, kIconURL3, _, _));
handler->OnUpdateCandidates(
kPageURL, {FaviconURL(kIconURL3, kFavicon, kEmptySizes)}, GURL());
// Finalizes download, which should be thrown away as the favicon URLs were
// updated.
EXPECT_TRUE(delegate_.fake_image_downloader().RunCallbackManually());
base::RunLoop().RunUntilIdle();
EXPECT_THAT(favicon_service_.fake()->db_requests(), ElementsAre(kIconURL3));
EXPECT_THAT(delegate_.downloads(), ElementsAre(kIconURL3));
}
// Test that sending an icon URL update different to the previous icon URL
// update during a database lookup ignores the first icon URL and processes the
// second.
TEST_F(FaviconHandlerTest, UpdateDuringDatabaseLookup) {
const GURL kIconURL1 = kIconURL16x16;
const GURL kIconURL2 = kIconURL12x12;
// Defer the lookup completion such that RunUntilIdle() doesn't complete the
// lookup.
favicon_service_.fake()->SetRunCallbackManuallyForUrl(kIconURL1);
delegate_.fake_image_downloader().Add(kIconURL1, IntVector{16});
delegate_.fake_image_downloader().Add(kIconURL2, IntVector{12});
std::unique_ptr<FaviconHandler> handler =
RunHandlerWithSimpleFaviconCandidates(URLVector{kIconURL1});
ASSERT_TRUE(VerifyAndClearExpectations());
ASSERT_TRUE(favicon_service_.fake()->HasPendingManualCallback());
// SetFavicons() and OnFaviconUpdated() should be called for the new icon URL
// and not |kIconURL1|.
EXPECT_CALL(favicon_service_, SetFavicons(_, kIconURL2, _, _));
EXPECT_CALL(delegate_, OnFaviconUpdated(_, _, kIconURL2, _, _));
handler->OnUpdateCandidates(
kPageURL, {FaviconURL(kIconURL2, kFavicon, kEmptySizes)}, GURL());
// Finalizes the DB lookup, which should be thrown away as the favicon URLs
// were updated.
EXPECT_TRUE(favicon_service_.fake()->RunCallbackManually());
base::RunLoop().RunUntilIdle();
EXPECT_THAT(favicon_service_.fake()->db_requests(), ElementsAre(kIconURL2));
EXPECT_THAT(delegate_.downloads(), ElementsAre(kIconURL2));
}
// Test that sending an icon URL update identical to the previous icon URL
// update during image download is a no-op.
TEST_F(FaviconHandlerTest, UpdateSameIconURLsWhileDownloadingShouldBeNoop) {
const GURL kSlowLoadingIconURL("http://www.google.com/slow_favicon");
const std::vector<FaviconURL> favicon_urls = {
FaviconURL(kIconURL12x12, kFavicon, kEmptySizes),
FaviconURL(kSlowLoadingIconURL, kFavicon, kEmptySizes),
};
// Defer the download completion such that RunUntilIdle() doesn't complete
// the download.
delegate_.fake_image_downloader().SetRunCallbackManuallyForUrl(
kSlowLoadingIconURL);
delegate_.fake_image_downloader().Add(kSlowLoadingIconURL, IntVector{16});
std::unique_ptr<FaviconHandler> handler = RunHandlerWithCandidates(
FaviconDriverObserver::NON_TOUCH_16_DIP, favicon_urls);
ASSERT_THAT(favicon_service_.fake()->db_requests(),
ElementsAre(kPageURL, kIconURL12x12, kSlowLoadingIconURL));
ASSERT_TRUE(VerifyAndClearExpectations());
ASSERT_TRUE(delegate_.fake_image_downloader().HasPendingManualCallback());
// Calling OnUpdateCandidates() with the same icon URLs should have no effect,
// despite the ongoing download.
handler->OnUpdateCandidates(kPageURL, favicon_urls, GURL());
base::RunLoop().RunUntilIdle();
EXPECT_THAT(favicon_service_.fake()->db_requests(), IsEmpty());
EXPECT_THAT(delegate_.downloads(), IsEmpty());
// Complete the download.
EXPECT_CALL(favicon_service_, SetFavicons(_, _, _, _));
EXPECT_CALL(delegate_, OnFaviconUpdated(_, _, _, _, _));
EXPECT_TRUE(delegate_.fake_image_downloader().RunCallbackManually());
base::RunLoop().RunUntilIdle();
EXPECT_THAT(delegate_.downloads(), IsEmpty());
}
// Test that sending an icon URL update identical to the previous icon URL
// update during a database lookup is a no-op.
TEST_F(FaviconHandlerTest, UpdateSameIconURLsWhileDatabaseLookupShouldBeNoop) {
const std::vector<FaviconURL> favicon_urls = {
FaviconURL(kIconURL12x12, kFavicon, kEmptySizes),
};
favicon_service_.fake()->SetRunCallbackManuallyForUrl(kIconURL12x12);
std::unique_ptr<FaviconHandler> handler = RunHandlerWithCandidates(
FaviconDriverObserver::NON_TOUCH_16_DIP, favicon_urls);
// Ongoing database lookup.
ASSERT_THAT(favicon_service_.fake()->db_requests(),
ElementsAre(kPageURL, kIconURL12x12));
ASSERT_THAT(delegate_.downloads(), IsEmpty());
ASSERT_TRUE(VerifyAndClearExpectations());
ASSERT_TRUE(favicon_service_.fake()->HasPendingManualCallback());
// Calling OnUpdateCandidates() with the same icon URLs should have no effect,
// despite the ongoing DB lookup.
handler->OnUpdateCandidates(kPageURL, favicon_urls, GURL());
base::RunLoop().RunUntilIdle();
EXPECT_THAT(favicon_service_.fake()->db_requests(), IsEmpty());
EXPECT_THAT(delegate_.downloads(), IsEmpty());
// Complete the lookup.
EXPECT_CALL(favicon_service_, SetFavicons(_, _, _, _));
EXPECT_CALL(delegate_, OnFaviconUpdated(_, _, _, _, _));
EXPECT_TRUE(favicon_service_.fake()->RunCallbackManually());
base::RunLoop().RunUntilIdle();
EXPECT_THAT(delegate_.downloads(), ElementsAre(kIconURL12x12));
}
// Test that calling OnUpdateFaviconUrl() with the same icon URLs as before is a
// no-op. This is important because OnUpdateFaviconUrl() is called when the page
// finishes loading. This can occur several times for pages with iframes.
TEST_F(FaviconHandlerTest, UpdateSameIconURLsAfterFinishedShouldBeNoop) {
const std::vector<FaviconURL> favicon_urls = {
FaviconURL(kIconURL10x10, kFavicon, kEmptySizes),
FaviconURL(kIconURL16x16, kFavicon, kEmptySizes),
};
std::unique_ptr<FaviconHandler> handler = RunHandlerWithCandidates(
FaviconDriverObserver::NON_TOUCH_16_DIP, favicon_urls);
ASSERT_TRUE(VerifyAndClearExpectations());
// Calling OnUpdateCandidates() with identical data should be a no-op.
EXPECT_CALL(delegate_, OnFaviconUpdated(_, _, _, _, _)).Times(0);
EXPECT_CALL(favicon_service_, SetFavicons(_, _, _, _)).Times(0);
handler->OnUpdateCandidates(kPageURL, favicon_urls, GURL());
base::RunLoop().RunUntilIdle();
EXPECT_THAT(favicon_service_.fake()->db_requests(), IsEmpty());
EXPECT_THAT(delegate_.downloads(), IsEmpty());
}
// Fixes crbug.com/544560
// Tests that Delegate::OnFaviconUpdated() is called if:
// - The best icon on the initial page is not the last icon.
// - All of the initial page's icons are downloaded.
// AND
// - JavaScript modifies the page's <link rel="icon"> tags to contain only the
// last icon.
TEST_F(FaviconHandlerTest,
OnFaviconAvailableNotificationSentAfterIconURLChange) {
const GURL kIconURL1(
"http://wwww.page_which_animates_favicon.com/frame1.png");
const GURL kIconURL2(
"http://wwww.page_which_animates_favicon.com/frame2.png");
// |kIconURL1| is the better match.
delegate_.fake_image_downloader().Add(kIconURL1, IntVector{15});
delegate_.fake_image_downloader().Add(kIconURL2, IntVector{10});
// Two FaviconDriver::OnFaviconUpdated() notifications should be sent for
// |kIconURL1|, one before and one after the download.
EXPECT_CALL(delegate_, OnFaviconUpdated(_, _, kIconURL1, _, _));
std::unique_ptr<FaviconHandler> handler =
RunHandlerWithSimpleFaviconCandidates({kIconURL1, kIconURL2});
// Both |kIconURL1| and |kIconURL2| should have been requested from the
// database and downloaded. |kIconURL2| should have been fetched from the
// database and downloaded last.
ASSERT_THAT(delegate_.downloads(), ElementsAre(kIconURL1, kIconURL2));
ASSERT_THAT(favicon_service_.fake()->db_requests(),
ElementsAre(kPageURL, kIconURL1, kIconURL2));
ASSERT_TRUE(VerifyAndClearExpectations());
// Simulate the page changing it's icon URL to just |kIconURL2| via
// Javascript.
EXPECT_CALL(delegate_, OnFaviconUpdated(_, _, kIconURL2, _, _));
handler->OnUpdateCandidates(
kPageURL, {FaviconURL(kIconURL2, kFavicon, kEmptySizes)}, GURL());
base::RunLoop().RunUntilIdle();
}
// Test that favicon mappings are removed if the page initially lists a favicon
// and later uses Javascript to remove it.
TEST_F(FaviconHandlerTest, RemoveFaviconViaJavascript) {
EXPECT_CALL(favicon_service_, SetFavicons(base::flat_set<GURL>{kPageURL},
kIconURL16x16, kFavicon, _));
// Setup: the page initially lists a favicon.
std::unique_ptr<FaviconHandler> handler =
RunHandlerWithSimpleFaviconCandidates(URLVector{kIconURL16x16});
ASSERT_TRUE(VerifyAndClearExpectations());
// Simulate the page removing its icon URL via Javascript.
EXPECT_CALL(favicon_service_,
DeleteFaviconMappings(base::flat_set<GURL>{kPageURL}, kFavicon));
handler->OnUpdateCandidates(kPageURL, std::vector<FaviconURL>(), GURL());
base::RunLoop().RunUntilIdle();
}
// Test the favicon which is selected when the web page provides several
// favicons and none of the favicons are cached in history.
// The goal of this test is to be more of an integration test than
// SelectFaviconFramesTest.*.
class FaviconHandlerMultipleFaviconsTest : public FaviconHandlerTest {
protected:
FaviconHandlerMultipleFaviconsTest() {
// Set the supported scale factors to 1x and 2x. This affects the behavior
// of SelectFaviconFrames().
scoped_set_supported_scale_factors_.reset(); // Need to delete first.
scoped_set_supported_scale_factors_.reset(
new ui::test::ScopedSetSupportedScaleFactors(
{ui::SCALE_FACTOR_100P, ui::SCALE_FACTOR_200P}));
}
// Simulates requesting a favicon for |page_url| given:
// - We have not previously cached anything in history for |page_url| or for
// any of candidates.
// - The page provides favicons with edge pixel sizes of
// |candidate_icon_sizes|.
// - Candidates are assumed of type kFavicon and the URLs are generated
// internally for testing purposes.
//
// Returns the chosen size among |candidate_icon_sizes| or -1 if none was
// chosen.
int DownloadTillDoneIgnoringHistory(const IntVector& candidate_icon_sizes) {
std::vector<FaviconURL> candidate_icons;
int chosen_icon_size = -1;
for (int icon_size : candidate_icon_sizes) {
const GURL icon_url(base::StringPrintf(
"https://www.google.com/generated/%dx%d", icon_size, icon_size));
// Set up 200 responses for all images, and the corresponding size.
delegate_.fake_image_downloader().Add(icon_url, IntVector{icon_size});
// Create test candidates of type kFavicon and a fake URL.
candidate_icons.emplace_back(icon_url, kFavicon, kEmptySizes);
ON_CALL(delegate_, OnFaviconUpdated(_, _, icon_url, _, _))
.WillByDefault(Assign(&chosen_icon_size, icon_size));
}
RunHandlerWithCandidates(FaviconDriverObserver::NON_TOUCH_16_DIP,
candidate_icons);
return chosen_icon_size;
}
};
// Tests that running FaviconHandler
// - On an OS which supports the 1x and 2x scale factor
// - On a page with <link rel="icon"> tags with no "sizes" information.
// Selects the largest exact match. Note that a 32x32 PNG image is not a "true
// exact match" on an OS which supports an 1x and 2x. A "true exact match" is
// a .ico file with 16x16 and 32x32 bitmaps.
TEST_F(FaviconHandlerMultipleFaviconsTest, ChooseLargestExactMatch) {
EXPECT_EQ(32,
DownloadTillDoneIgnoringHistory(IntVector{16, 24, 32, 48, 256}));
}
// Test that if there are several single resolution favicons to choose
// from, the exact match is preferred even if it results in upsampling.
TEST_F(FaviconHandlerMultipleFaviconsTest, ChooseExactMatchDespiteUpsampling) {
EXPECT_EQ(16, DownloadTillDoneIgnoringHistory(IntVector{16, 24, 48, 256}));
}
// Test that favicons which need to be upsampled a little or downsampled
// a little are preferred over huge favicons.
TEST_F(FaviconHandlerMultipleFaviconsTest,
ChooseMinorDownsamplingOverHugeIcon) {
EXPECT_EQ(48, DownloadTillDoneIgnoringHistory(IntVector{256, 48}));
}
TEST_F(FaviconHandlerMultipleFaviconsTest, ChooseMinorUpsamplingOverHugeIcon) {
EXPECT_EQ(17, DownloadTillDoneIgnoringHistory(IntVector{17, 256}));
}
TEST_F(FaviconHandlerTest, Report404) {
const GURL k404IconURL("http://www.google.com/404.png");
EXPECT_CALL(favicon_service_, UnableToDownloadFavicon(k404IconURL));
RunHandlerWithSimpleFaviconCandidates({k404IconURL});
EXPECT_THAT(delegate_.downloads(), ElementsAre(k404IconURL));
}
// Test that WasUnableToDownloadFavicon() is not called if a download returns
// HTTP status 503.
TEST_F(FaviconHandlerTest, NotReport503) {
const GURL k503IconURL("http://www.google.com/503.png");
delegate_.fake_image_downloader().AddError(k503IconURL, 503);
EXPECT_CALL(favicon_service_, UnableToDownloadFavicon(_)).Times(0);
RunHandlerWithSimpleFaviconCandidates({k503IconURL});
EXPECT_THAT(delegate_.downloads(), ElementsAre(k503IconURL));
}
// Test that the best favicon is selected when:
// - The page provides several favicons.
// - Downloading one of the page's icon URLs previously returned a 404.
// - None of the favicons are cached in the Favicons database.
TEST_F(FaviconHandlerTest, MultipleFavicons404) {
const GURL k404IconURL("http://www.google.com/404.png");
ON_CALL(favicon_service_, WasUnableToDownloadFavicon(k404IconURL))
.WillByDefault(Return(true));
EXPECT_CALL(delegate_, OnFaviconUpdated(_, _, kIconURL12x12, _, _));
RunHandlerWithSimpleFaviconCandidates({k404IconURL, kIconURL12x12});
EXPECT_THAT(delegate_.downloads(), ElementsAre(kIconURL12x12));
}
// Test that the best favicon is selected when:
// - The page provides several favicons.
// - Downloading the last page icon URL previously returned a 404.
// - None of the favicons are cached in the Favicons database.
// - All of the icons are downloaded because none of the icons have the ideal
// size.
// - The 404 icon is last.
TEST_F(FaviconHandlerTest, MultipleFaviconsLast404) {
const GURL k404IconURL("http://www.google.com/404.png");
ON_CALL(favicon_service_, WasUnableToDownloadFavicon(k404IconURL))
.WillByDefault(Return(true));
EXPECT_CALL(delegate_, OnFaviconUpdated(_, _, kIconURL12x12, _, _));
RunHandlerWithSimpleFaviconCandidates({kIconURL12x12, k404IconURL});
EXPECT_THAT(delegate_.downloads(), ElementsAre(kIconURL12x12));
}
// Test that no favicon is selected when:
// - The page provides several favicons.
// - Downloading the page's icons has previously returned a 404.
// - None of the favicons are cached in the Favicons database.
TEST_F(FaviconHandlerTest, MultipleFaviconsAll404) {
const GURL k404IconURL1("http://www.google.com/a/404.png");
const GURL k404IconURL2("http://www.google.com/b/404.png");
ON_CALL(favicon_service_, WasUnableToDownloadFavicon(k404IconURL1))
.WillByDefault(Return(true));
ON_CALL(favicon_service_, WasUnableToDownloadFavicon(k404IconURL2))
.WillByDefault(Return(true));
EXPECT_CALL(delegate_, OnFaviconUpdated(_, _, _, _, _)).Times(0);
RunHandlerWithSimpleFaviconCandidates({k404IconURL1, k404IconURL2});
EXPECT_THAT(delegate_.downloads(), IsEmpty());
}
// Test that favicon mappings are removed if the page initially lists a favicon
// and later uses Javascript to change it to another icon that returns a 404.
TEST_F(FaviconHandlerTest, ChangeFaviconViaJavascriptTo404) {
const GURL k404IconURL("http://www.google.com/404.png");
EXPECT_CALL(favicon_service_, SetFavicons(base::flat_set<GURL>{kPageURL},
kIconURL16x16, kFavicon, _));
// Setup: the page initially lists a favicon.
std::unique_ptr<FaviconHandler> handler =
RunHandlerWithSimpleFaviconCandidates(URLVector{kIconURL16x16});
ASSERT_TRUE(VerifyAndClearExpectations());
// Simulate the page changing its icon URL via Javascript, using a URL that
// returns a 404 (the most likely scenario for this is the implicit
// /favicon.ico path that the site didn't actually list).
EXPECT_CALL(favicon_service_,
DeleteFaviconMappings(base::flat_set<GURL>{kPageURL}, kFavicon));
handler->OnUpdateCandidates(
kPageURL, {FaviconURL(k404IconURL, kFavicon, kEmptySizes)}, GURL());
base::RunLoop().RunUntilIdle();
}
// Test that favicon mappings are not removed in incognito if the page initially
// lists a favicon and later uses Javascript to change it to another icon that
// returns a 404.
TEST_F(FaviconHandlerTest, ChangeFaviconViaJavascriptTo404InIncognito) {
const GURL k404IconURL("http://www.google.com/404.png");
ON_CALL(delegate_, IsOffTheRecord()).WillByDefault(Return(true));
favicon_service_.fake()->Store(kPageURL, kIconURL16x16,
CreateRawBitmapResult(kIconURL16x16));
EXPECT_CALL(favicon_service_, DeleteFaviconMappings(_, _)).Times(0);
// Setup: the page initially lists a favicon.
std::unique_ptr<FaviconHandler> handler =
RunHandlerWithSimpleFaviconCandidates(URLVector{kIconURL16x16});
// Simulate the page changing its icon URL via Javascript, using a URL that
// returns a 404 (the most likely scenario for this is the implicit
// /favicon.ico path that the site didn't actually list).
handler->OnUpdateCandidates(
kPageURL, {FaviconURL(k404IconURL, kFavicon, kEmptySizes)}, GURL());
base::RunLoop().RunUntilIdle();
}
// Test that no favicon is selected when the page's only icon uses an invalid
// URL syntax.
TEST_F(FaviconHandlerTest, FaviconInvalidURL) {
const GURL kInvalidFormatURL("invalid");
ASSERT_TRUE(kInvalidFormatURL.is_empty());
EXPECT_CALL(delegate_, OnFaviconUpdated(_, _, _, _, _)).Times(0);
RunHandlerWithSimpleFaviconCandidates({kInvalidFormatURL});
EXPECT_THAT(delegate_.downloads(), IsEmpty());
}
TEST_F(FaviconHandlerTest, TestSortFavicon) {
// Names represent the bitmap sizes per icon.
const GURL kIconURL1_17("http://www.google.com/a");
const GURL kIconURL1024_512("http://www.google.com/b");
const GURL kIconURL16_14("http://www.google.com/c");
const GURL kIconURLWithoutSize1("http://www.google.com/d");
const GURL kIconURLWithoutSize2("http://www.google.com/e");
const std::vector<favicon::FaviconURL> kSourceIconURLs{
FaviconURL(kIconURL1_17, kFavicon, {gfx::Size(1, 1), gfx::Size(17, 17)}),
FaviconURL(kIconURL1024_512, kFavicon,
{gfx::Size(1024, 1024), gfx::Size(512, 512)}),
FaviconURL(kIconURL16_14, kFavicon,
{gfx::Size(16, 16), gfx::Size(14, 14)}),
FaviconURL(kIconURLWithoutSize1, kFavicon, kEmptySizes),
FaviconURL(kIconURLWithoutSize2, kFavicon, kEmptySizes)};
std::unique_ptr<FaviconHandler> handler = RunHandlerWithCandidates(
FaviconDriverObserver::NON_TOUCH_LARGEST, kSourceIconURLs);
EXPECT_THAT(
handler->GetIconURLs(),
ElementsAre(
// The 512x512 bitmap is the best match for the desired size.
kIconURL1024_512, kIconURL1_17, kIconURL16_14,
// The rest of bitmaps come in order, there is no "sizes" attribute.
kIconURLWithoutSize1, kIconURLWithoutSize2));
}
TEST_F(FaviconHandlerTest, TestSortTouchIconLargest) {
const GURL kIconURLWithoutSize("http://www.google.com/touchicon-nosize");
const GURL kIconURL144x144("http://www.google.com/touchicon144x144");
const GURL kIconURL192x192("http://www.google.com/touchicon192x192");
const std::vector<favicon::FaviconURL> kSourceIconURLs{
FaviconURL(kIconURLWithoutSize, kTouchIcon, kEmptySizes),
FaviconURL(kIconURL144x144, kTouchIcon,
SizeVector(1U, gfx::Size(144, 144))),
FaviconURL(kIconURL192x192, kTouchIcon,
SizeVector(1U, gfx::Size(192, 192))),
};
std::unique_ptr<FaviconHandler> handler = RunHandlerWithCandidates(
FaviconDriverObserver::TOUCH_LARGEST, kSourceIconURLs);
EXPECT_THAT(
handler->GetIconURLs(),
ElementsAre(kIconURL192x192, kIconURL144x144, kIconURLWithoutSize));
}
TEST_F(FaviconHandlerTest, TestDownloadLargestFavicon) {
// Names represent the bitmap sizes per icon.
const GURL kIconURL1024_512("http://www.google.com/a");
const GURL kIconURL15_14("http://www.google.com/b");
const GURL kIconURL16_512("http://www.google.com/c");
const GURL kIconURLWithoutSize1("http://www.google.com/d");
const GURL kIconURLWithoutSize2("http://www.google.com/e");
RunHandlerWithCandidates(
FaviconDriverObserver::NON_TOUCH_LARGEST,
{FaviconURL(kIconURL1024_512, kFavicon,
{gfx::Size(1024, 1024), gfx::Size(512, 512)}),
FaviconURL(kIconURL15_14, kFavicon,
{gfx::Size(15, 15), gfx::Size(14, 14)}),
FaviconURL(kIconURL16_512, kFavicon,
{gfx::Size(16, 16), gfx::Size(512, 512)}),
FaviconURL(kIconURLWithoutSize1, kFavicon, kEmptySizes),
FaviconURL(kIconURLWithoutSize2, kFavicon, kEmptySizes)});
// Icon URLs are not registered and hence 404s will be produced, which
// allows checking whether the icons were requested according to their size.
// The favicons should have been requested in decreasing order of their sizes.
// Favicons without any <link sizes=""> attribute should have been downloaded
// last.
EXPECT_THAT(delegate_.downloads(),
ElementsAre(kIconURL1024_512, kIconURL16_512, kIconURL15_14,
kIconURLWithoutSize1, kIconURLWithoutSize2));
}
TEST_F(FaviconHandlerTest, TestSelectLargestFavicon) {
const GURL kIconURL1("http://www.google.com/b");
const GURL kIconURL2("http://www.google.com/c");
delegate_.fake_image_downloader().Add(kIconURL1, IntVector{15});
delegate_.fake_image_downloader().Add(kIconURL2, IntVector{14, 16});
// Verify NotifyFaviconAvailable().
EXPECT_CALL(delegate_,
OnFaviconUpdated(_, FaviconDriverObserver::NON_TOUCH_LARGEST,
kIconURL2, _, _));
RunHandlerWithCandidates(
FaviconDriverObserver::NON_TOUCH_LARGEST,
{FaviconURL(kIconURL1, kFavicon, {gfx::Size(15, 15)}),
FaviconURL(kIconURL2, kFavicon,
{gfx::Size(14, 14), gfx::Size(16, 16)})});
EXPECT_THAT(delegate_.downloads(), ElementsAre(kIconURL2));
}
TEST_F(FaviconHandlerTest, TestFaviconWasScaledAfterDownload) {
const int kMaximalSize = FaviconHandler::GetMaximalIconSize(
FaviconDriverObserver::NON_TOUCH_LARGEST,
/*candidates_from_web_manifest=*/false);
const GURL kIconURL1("http://www.google.com/b");
const GURL kIconURL2("http://www.google.com/c");
const int kOriginalSize1 = kMaximalSize + 1;
const int kOriginalSize2 = kMaximalSize + 2;
delegate_.fake_image_downloader().Add(kIconURL1, IntVector{kOriginalSize1},
SK_ColorBLUE);
delegate_.fake_image_downloader().Add(kIconURL2, IntVector{kOriginalSize2},
SK_ColorBLUE);
// Verify the best bitmap was selected (although smaller than |kIconURL2|)
// and that it was scaled down to |kMaximalSize|.
EXPECT_CALL(delegate_,
OnFaviconUpdated(_, _, kIconURL1, _,
ImageSizeIs(kMaximalSize, kMaximalSize)));
RunHandlerWithCandidates(
FaviconDriverObserver::NON_TOUCH_LARGEST,
{FaviconURL(kIconURL1, kFavicon,
SizeVector{gfx::Size(kOriginalSize1, kOriginalSize1)}),
FaviconURL(kIconURL2, kFavicon,
SizeVector{gfx::Size(kOriginalSize2, kOriginalSize2)})});
EXPECT_THAT(delegate_.downloads(), ElementsAre(kIconURL1));
}
// Test that if several icons are downloaded because the icons are smaller than
// expected that OnFaviconUpdated() is called with the largest downloaded
// bitmap.
TEST_F(FaviconHandlerTest, TestKeepDownloadedLargestFavicon) {
EXPECT_CALL(delegate_,
OnFaviconUpdated(_, _, kIconURL12x12, _, ImageSizeIs(12, 12)));
RunHandlerWithCandidates(
FaviconDriverObserver::NON_TOUCH_LARGEST,
{FaviconURL(kIconURL10x10, kFavicon, SizeVector{gfx::Size(16, 16)}),
FaviconURL(kIconURL12x12, kFavicon, SizeVector{gfx::Size(15, 15)}),
FaviconURL(kIconURL16x16, kFavicon, kEmptySizes)});
}
// Test that if a page URL is followed by another page URL which is not
// considered the same document, favicon candidates listed in the second page
// get associated to that second page only.
TEST_F(FaviconHandlerTest, SetFaviconsForLastPageUrlOnly) {
const GURL kDifferentPageURL = GURL("http://www.google.com/other");
EXPECT_CALL(favicon_service_,
SetFavicons(base::flat_set<GURL>{kDifferentPageURL},
kIconURL12x12, _, _));
EXPECT_CALL(delegate_,
OnFaviconUpdated(kDifferentPageURL,
FaviconDriverObserver::NON_TOUCH_16_DIP,
kIconURL12x12, _, _));
std::unique_ptr<FaviconHandler> handler = std::make_unique<FaviconHandler>(
&favicon_service_, &delegate_, FaviconDriverObserver::NON_TOUCH_16_DIP);
handler->FetchFavicon(kPageURL, /*is_same_document=*/false);
base::RunLoop().RunUntilIdle();
// Load a new URL (different document) without feeding any candidates for the
// first URL.
handler->FetchFavicon(kDifferentPageURL, /*is_same_document=*/false);
base::RunLoop().RunUntilIdle();
handler->OnUpdateCandidates(
kDifferentPageURL, {FaviconURL(kIconURL12x12, kFavicon, kEmptySizes)},
/*manifest_url=*/GURL());
base::RunLoop().RunUntilIdle();
}
// Test that if a page URL is followed by another page URL which is considered
// the same document (e.g. fragment navigation), favicon candidates listed in
// the second page get associated to both page URLs.
TEST_F(FaviconHandlerTest, SetFaviconsForMultipleUrlsWithinDocument) {
const GURL kDifferentPageURL = GURL("http://www.google.com/other");
EXPECT_CALL(favicon_service_,
SetFavicons(base::flat_set<GURL>{kPageURL, kDifferentPageURL},
kIconURL12x12, _, _));
EXPECT_CALL(delegate_,
OnFaviconUpdated(kDifferentPageURL,
FaviconDriverObserver::NON_TOUCH_16_DIP,
kIconURL12x12, _, _));
std::unique_ptr<FaviconHandler> handler = std::make_unique<FaviconHandler>(
&favicon_service_, &delegate_, FaviconDriverObserver::NON_TOUCH_16_DIP);
handler->FetchFavicon(kPageURL, /*is_same_document=*/false);
base::RunLoop().RunUntilIdle();
// Load a new URL (same document) without feeding any candidates for the first
// URL.
handler->FetchFavicon(kDifferentPageURL, /*is_same_document=*/true);
base::RunLoop().RunUntilIdle();
handler->OnUpdateCandidates(
kDifferentPageURL, {FaviconURL(kIconURL12x12, kFavicon, kEmptySizes)},
/*manifest_url=*/GURL());
base::RunLoop().RunUntilIdle();
}
// Manifests are currently enabled by default. Leaving this fixture for
// logical grouping and blame layer.
class FaviconHandlerManifestsEnabledTest : public FaviconHandlerTest {
protected:
const GURL kManifestURL = GURL("http://www.google.com/manifest.json");
FaviconHandlerManifestsEnabledTest() = default;
// Exercises the handler for the simplest case where all types are kTouchIcon
// and no sizes are provided, using a FaviconHandler of type TOUCH_LARGETS.
std::unique_ptr<FaviconHandler> RunHandlerWithSimpleTouchIconCandidates(
const std::vector<GURL>& urls,
const GURL& manifest_url) {
std::vector<favicon::FaviconURL> candidates;
for (const GURL& url : urls) {
candidates.emplace_back(url, kTouchIcon, kEmptySizes);
}
return RunHandlerWithCandidates(FaviconDriverObserver::TOUCH_LARGEST,
candidates, manifest_url);
}
private:
// Avoid accidental use of kFavicon type, since Web Manifests are handled by
// the FaviconHandler of type TOUCH_LARGEST.
using FaviconHandlerTest::RunHandlerWithSimpleFaviconCandidates;
DISALLOW_COPY_AND_ASSIGN(FaviconHandlerManifestsEnabledTest);
};
// Test that favicon mappings are deleted when a manifest previously cached in
// the DB is no longer referenced by the page and the page lists no regular
// icons.
TEST_F(FaviconHandlerManifestsEnabledTest,
RemovedWebManifestAndNoRegularIcons) {
favicon_service_.fake()->Store(
kPageURL, kManifestURL,
CreateRawBitmapResult(kManifestURL, kWebManifestIcon));
EXPECT_CALL(
favicon_service_,
DeleteFaviconMappings(base::flat_set<GURL>{kPageURL}, kWebManifestIcon));
RunHandlerWithSimpleTouchIconCandidates(URLVector(), /*manifest_url=*/GURL());
}
// Test that favicon mappings are updated (but not deleted) when a manifest
// previously cached in the DB is no longer referenced by the page and the page
// lists regular icons.
TEST_F(FaviconHandlerManifestsEnabledTest, RemovedWebManifestAndRegularIcons) {
favicon_service_.fake()->Store(
kPageURL, kManifestURL,
CreateRawBitmapResult(kManifestURL, kWebManifestIcon));
EXPECT_CALL(favicon_service_, DeleteFaviconMappings(_, _)).Times(0);
EXPECT_CALL(favicon_service_, SetFavicons(_, kIconURL12x12, kTouchIcon, _));
RunHandlerWithSimpleTouchIconCandidates({kIconURL12x12},
/*manifest_url=*/GURL());
}
// Test that favicon mappings are updated (but not deleted) when a manifest
// previously cached in the DB (but expired) is no longer referenced by the page
// and the page lists regular icons.
TEST_F(FaviconHandlerManifestsEnabledTest,
ExpiredAndRemovedWebManifestAndRegularIcons) {
favicon_service_.fake()->Store(
kPageURL, kManifestURL,
CreateRawBitmapResult(kManifestURL, kWebManifestIcon, /*expired=*/true));
EXPECT_CALL(favicon_service_, DeleteFaviconMappings(_, _)).Times(0);
EXPECT_CALL(favicon_service_, SetFavicons(_, kIconURL12x12, kTouchIcon, _));
RunHandlerWithSimpleTouchIconCandidates({kIconURL12x12},
/*manifest_url=*/GURL());
}
// Test that a favicon corresponding to a web manifest is reported when:
// - There is data in the favicon database for the manifest URL.
// AND
// - FaviconService::OnFaviconDataForManifestFromFaviconService() runs before
// FaviconHandler::OnUpdateCandidates() is called.
TEST_F(FaviconHandlerManifestsEnabledTest,
GetFaviconFromManifestInHistoryIfCandidatesSlower) {
favicon_service_.fake()->Store(
kPageURL, kManifestURL,
CreateRawBitmapResult(kManifestURL, kWebManifestIcon));
EXPECT_CALL(favicon_service_, UnableToDownloadFavicon(_)).Times(0);
EXPECT_CALL(favicon_service_,
UpdateFaviconMappingsAndFetch(_, kManifestURL, kWebManifestIcon,
/*desired_size_in_dip=*/0, _, _));
EXPECT_CALL(delegate_,
OnFaviconUpdated(_, FaviconDriverObserver::TOUCH_LARGEST,
kManifestURL, _, _));
RunHandlerWithSimpleTouchIconCandidates({kIconURL12x12}, kManifestURL);
EXPECT_THAT(favicon_service_.fake()->db_requests(),
ElementsAre(kPageURL, kManifestURL));
EXPECT_THAT(delegate_.downloads(), IsEmpty());
}
// Test that a favicon corresponding to a web manifest is reported when:
// - There is data in the favicon database for the manifest URL.
// AND
// - FaviconHandler::OnUpdateCandidates() is called before
// FaviconService::OnFaviconDataForManifestFromFaviconService() runs.
TEST_F(FaviconHandlerManifestsEnabledTest,
GetFaviconFromManifestInHistoryIfCandidatesFaster) {
favicon_service_.fake()->Store(
kPageURL, kManifestURL,
CreateRawBitmapResult(kManifestURL, kWebManifestIcon));
// Defer the database lookup completion to control the exact timing.
favicon_service_.fake()->SetRunCallbackManuallyForUrl(kManifestURL);
EXPECT_CALL(favicon_service_, UnableToDownloadFavicon(_)).Times(0);
EXPECT_CALL(favicon_service_,
UpdateFaviconMappingsAndFetch(_, kManifestURL, kWebManifestIcon,
/*desired_size_in_dip=*/0, _, _));
EXPECT_CALL(delegate_,
OnFaviconUpdated(_, FaviconDriverObserver::TOUCH_LARGEST,
kManifestURL, _, _));
std::unique_ptr<FaviconHandler> handler =
RunHandlerWithSimpleTouchIconCandidates({kIconURL12x12}, kManifestURL);
ASSERT_TRUE(favicon_service_.fake()->HasPendingManualCallback());
// Complete the lookup.
EXPECT_TRUE(favicon_service_.fake()->RunCallbackManually());
base::RunLoop().RunUntilIdle();
EXPECT_THAT(favicon_service_.fake()->db_requests(),
ElementsAre(kPageURL, kManifestURL));
EXPECT_THAT(delegate_.downloads(), IsEmpty());
}
// Test that a favicon corresponding to a web manifest is reported when there is
// data in the database for neither the page URL nor the manifest URL.
TEST_F(FaviconHandlerManifestsEnabledTest, GetFaviconFromUnknownManifest) {
const std::vector<favicon::FaviconURL> kManifestIcons = {
FaviconURL(kIconURL16x16, kWebManifestIcon, kEmptySizes),
};
delegate_.fake_manifest_downloader().Add(kManifestURL, kManifestIcons);
EXPECT_CALL(favicon_service_, UnableToDownloadFavicon(_)).Times(0);
EXPECT_CALL(favicon_service_,
SetFavicons(_, kManifestURL, kWebManifestIcon, _));
EXPECT_CALL(delegate_,
OnFaviconUpdated(_, FaviconDriverObserver::TOUCH_LARGEST,
kManifestURL, _, _));
RunHandlerWithSimpleTouchIconCandidates({kIconURL12x12}, kManifestURL);
EXPECT_THAT(favicon_service_.fake()->db_requests(),
ElementsAre(kPageURL, kManifestURL));
EXPECT_THAT(delegate_.downloads(), ElementsAre(kManifestURL, kIconURL16x16));
}
// Test that icons from a web manifest use a desired size of 192x192.
TEST_F(FaviconHandlerManifestsEnabledTest, Prefer192x192IconFromManifest) {
const GURL kIconURL144x144 = GURL("http://www.google.com/favicon144x144");
const GURL kIconURL192x192 = GURL("http://www.google.com/favicon192x192");
delegate_.fake_image_downloader().Add(kIconURL144x144, IntVector{144});
delegate_.fake_image_downloader().Add(kIconURL192x192, IntVector{192});
const std::vector<favicon::FaviconURL> kManifestIcons = {
FaviconURL(kIconURL144x144, kWebManifestIcon,
SizeVector(1U, gfx::Size(144, 144))),
FaviconURL(kIconURL192x192, kWebManifestIcon,
SizeVector(1U, gfx::Size(192, 192))),
};
delegate_.fake_manifest_downloader().Add(kManifestURL, kManifestIcons);
RunHandlerWithSimpleTouchIconCandidates(URLVector(), kManifestURL);
EXPECT_THAT(delegate_.downloads(),
ElementsAre(kManifestURL, kIconURL192x192));
}
// Test that a 192x192 favicon corresponding to a web manifest is reported with
// the appropriate size when there is data in the database for neither the page
// URL nor the manifest URL.
TEST_F(FaviconHandlerManifestsEnabledTest,
GetNonResized192x192FaviconFromUnknownManifest) {
const GURL kIconURL192x192 = GURL("http://www.google.com/favicon192x192");
const std::vector<favicon::FaviconURL> kManifestIcons = {
FaviconURL(kIconURL192x192, kWebManifestIcon, kEmptySizes),
};
delegate_.fake_image_downloader().Add(kIconURL192x192, IntVector{192});
delegate_.fake_manifest_downloader().Add(kManifestURL, kManifestIcons);
EXPECT_CALL(favicon_service_, SetFavicons(_, kManifestURL, kWebManifestIcon,
ImageSizeIs(192, 192)));
RunHandlerWithSimpleTouchIconCandidates(URLVector(), kManifestURL);
}
// Test that the manifest and icon are redownloaded if the icon cached for the
// page URL expired.
TEST_F(FaviconHandlerManifestsEnabledTest, GetFaviconFromExpiredManifest) {
const std::vector<favicon::FaviconURL> kManifestIcons = {
FaviconURL(kIconURL64x64, kWebManifestIcon, kEmptySizes),
};
favicon_service_.fake()->Store(
kPageURL, kManifestURL,
CreateRawBitmapResult(kManifestURL, kWebManifestIcon,
/*expired=*/true));
delegate_.fake_manifest_downloader().Add(kManifestURL, kManifestIcons);
EXPECT_CALL(delegate_, OnFaviconUpdated(_, _, kManifestURL, _, _)).Times(2);
EXPECT_CALL(favicon_service_, SetFavicons(_, kManifestURL, _, _));
RunHandlerWithSimpleTouchIconCandidates({kIconURL12x12}, kManifestURL);
EXPECT_THAT(favicon_service_.fake()->db_requests(),
ElementsAre(kPageURL, kManifestURL));
EXPECT_THAT(delegate_.downloads(), ElementsAre(kManifestURL, kIconURL64x64));
}
// Test that the manifest and icon are redownloaded if the icon cached for the
// manifest URL expired, which was observed during a visit to a different page
// URL.
TEST_F(FaviconHandlerManifestsEnabledTest,
GetFaviconFromExpiredManifestLinkedFromOtherPage) {
const GURL kSomePreviousPageURL("https://www.google.com/previous");
const std::vector<favicon::FaviconURL> kManifestIcons = {
FaviconURL(kIconURL64x64, kWebManifestIcon, kEmptySizes),
};
favicon_service_.fake()->Store(
kSomePreviousPageURL, kManifestURL,
CreateRawBitmapResult(kManifestURL, kWebManifestIcon,
/*expired=*/true));
delegate_.fake_manifest_downloader().Add(kManifestURL, kManifestIcons);
EXPECT_CALL(delegate_, OnFaviconUpdated(_, _, kManifestURL, _, _)).Times(2);
EXPECT_CALL(favicon_service_, SetFavicons(_, kManifestURL, _, _));
RunHandlerWithSimpleTouchIconCandidates({kIconURL12x12}, kManifestURL);
EXPECT_THAT(favicon_service_.fake()->db_requests(),
ElementsAre(kPageURL, kManifestURL));
EXPECT_THAT(delegate_.downloads(), ElementsAre(kManifestURL, kIconURL64x64));
}
// Test that a favicon corresponding to a web manifest is reported when:
// - There is data in the database for neither the page URL nor the manifest
// URL.
// - There is data in the database for the icon URL listed in the manifest.
TEST_F(FaviconHandlerManifestsEnabledTest,
GetFaviconFromUnknownManifestButKnownIcon) {
const GURL kSomePreviousPageURL("https://www.google.com/previous");
const std::vector<favicon::FaviconURL> kManifestIcons = {
FaviconURL(kIconURL16x16, kWebManifestIcon, kEmptySizes),
};
favicon_service_.fake()->Store(
kSomePreviousPageURL, kIconURL16x16,
CreateRawBitmapResult(kIconURL16x16, kTouchIcon));
delegate_.fake_manifest_downloader().Add(kManifestURL, kManifestIcons);
EXPECT_CALL(favicon_service_, SetFavicons(_, kManifestURL, _, _));
EXPECT_CALL(delegate_, OnFaviconUpdated(_, _, kManifestURL, _, _));
RunHandlerWithSimpleTouchIconCandidates(URLVector(), kManifestURL);
EXPECT_THAT(favicon_service_.fake()->db_requests(),
ElementsAre(kPageURL, kManifestURL));
// This is because, in the current implementation, FaviconHandler only checks
// whether there is an icon cached with the manifest URL as the "icon URL"
// when a page has a non-empty Web Manifest.
EXPECT_THAT(delegate_.downloads(), ElementsAre(kManifestURL, kIconURL16x16));
}
// Test a manifest that returns a 404 gets blacklisted via
// UnableToDownloadFavicon() AND that the regular favicon is selected as
// fallback.
TEST_F(FaviconHandlerManifestsEnabledTest, UnknownManifestReturning404) {
EXPECT_CALL(favicon_service_, DeleteFaviconMappings(_, _)).Times(0);
EXPECT_CALL(favicon_service_, UnableToDownloadFavicon(kManifestURL));
EXPECT_CALL(favicon_service_, SetFavicons(_, kIconURL12x12, _, _));
RunHandlerWithSimpleTouchIconCandidates({kIconURL12x12}, kManifestURL);
EXPECT_THAT(favicon_service_.fake()->db_requests(),
ElementsAre(kPageURL, kManifestURL, kIconURL12x12));
EXPECT_THAT(delegate_.downloads(), ElementsAre(kManifestURL, kIconURL12x12));
}
// Test that a manifest that was previously blacklisted via
// UnableToDownloadFavicon() is ignored and that the regular favicon is selected
// as fallback.
TEST_F(FaviconHandlerManifestsEnabledTest, IgnoreManifestWithPrior404) {
ON_CALL(favicon_service_, WasUnableToDownloadFavicon(kManifestURL))
.WillByDefault(Return(true));
EXPECT_CALL(favicon_service_, DeleteFaviconMappings(_, _)).Times(0);
EXPECT_CALL(favicon_service_, SetFavicons(_, kIconURL12x12, _, _));
RunHandlerWithSimpleTouchIconCandidates({kIconURL12x12}, kManifestURL);
EXPECT_THAT(favicon_service_.fake()->db_requests(),
ElementsAre(kPageURL, kIconURL12x12));
EXPECT_THAT(delegate_.downloads(), ElementsAre(kIconURL12x12));
}
// Test that favicon mappings are deleted when a manifest previously cached in
// the DB (but expired) returns a 404, when the page lists no regular icons.
TEST_F(FaviconHandlerManifestsEnabledTest,
ExpiredManifestReturning404AndNoRegularIcons) {
favicon_service_.fake()->Store(
kPageURL, kManifestURL,
CreateRawBitmapResult(kManifestURL, kWebManifestIcon, /*expired=*/true));
EXPECT_CALL(
favicon_service_,
DeleteFaviconMappings(base::flat_set<GURL>{kPageURL}, kWebManifestIcon));
RunHandlerWithSimpleTouchIconCandidates(URLVector(), kManifestURL);
}
// Test that favicon mappings are updated (but not deleted) when a manifest
// previously cached in the DB (but expired) returns a 404, when the page lists
// regular icons that haven't been cached before.
TEST_F(FaviconHandlerManifestsEnabledTest,
ExpiredManifestReturning404AndRegularIcons) {
favicon_service_.fake()->Store(
kPageURL, kManifestURL,
CreateRawBitmapResult(kManifestURL, kWebManifestIcon, /*expired=*/true));
EXPECT_CALL(favicon_service_, DeleteFaviconMappings(_, _)).Times(0);
EXPECT_CALL(favicon_service_, SetFavicons(_, kIconURL12x12, kTouchIcon, _));
RunHandlerWithSimpleTouchIconCandidates({kIconURL12x12}, kManifestURL);
}
// Test that favicon mappings are deleted when a manifest previously cached in
// the DB (but expired) contains no icons, when the page lists no regular icons.
TEST_F(FaviconHandlerManifestsEnabledTest,
ExpiredManifestWithoutIconsAndNoRegularIcons) {
delegate_.fake_manifest_downloader().Add(kManifestURL,
std::vector<favicon::FaviconURL>());
favicon_service_.fake()->Store(
kPageURL, kManifestURL,
CreateRawBitmapResult(kManifestURL, kWebManifestIcon, /*expired=*/true));
EXPECT_CALL(
favicon_service_,
DeleteFaviconMappings(base::flat_set<GURL>{kPageURL}, kWebManifestIcon));
RunHandlerWithSimpleTouchIconCandidates(URLVector(), kManifestURL);
}
// Test that favicon mappings are updated (but not deleted) when a manifest
// previously cached in the DB (but expired) contains no icons, when the page
// lists regular icons that haven't been cached before.
TEST_F(FaviconHandlerManifestsEnabledTest,
ExpiredManifestWithoutIconsAndRegularIcons) {
delegate_.fake_manifest_downloader().Add(kManifestURL,
std::vector<favicon::FaviconURL>());
favicon_service_.fake()->Store(
kPageURL, kManifestURL,
CreateRawBitmapResult(kManifestURL, kWebManifestIcon, /*expired=*/true));
EXPECT_CALL(favicon_service_, DeleteFaviconMappings(_, _)).Times(0);
EXPECT_CALL(favicon_service_, SetFavicons(_, kIconURL12x12, kTouchIcon, _));
RunHandlerWithSimpleTouchIconCandidates({kIconURL12x12}, kManifestURL);
}
// Test that the regular favicon is selected when:
// - The page links to a Web Manifest.
// - The Web Manifest does not contain any icon URLs (it is not a 404).
// - The page has an icon URL provided via a <link rel="icon"> tag.
// - The database does not know about the page URL, manifest URL or icon URL.
TEST_F(FaviconHandlerManifestsEnabledTest, UnknownManifestWithoutIcons) {
delegate_.fake_manifest_downloader().Add(kManifestURL,
std::vector<favicon::FaviconURL>());
EXPECT_CALL(favicon_service_, DeleteFaviconMappings(_, _)).Times(0);
// UnableToDownloadFavicon() is expected to prevent repeated downloads of the
// same manifest (which is not otherwise cached, since it doesn't contain
// icons).
EXPECT_CALL(favicon_service_, UnableToDownloadFavicon(kManifestURL));
EXPECT_CALL(favicon_service_, SetFavicons(_, kIconURL12x12, _, _));
RunHandlerWithSimpleTouchIconCandidates({kIconURL12x12}, kManifestURL);
EXPECT_THAT(favicon_service_.fake()->db_requests(),
ElementsAre(kPageURL, kManifestURL, kIconURL12x12));
EXPECT_THAT(delegate_.downloads(), ElementsAre(kManifestURL, kIconURL12x12));
}
// Test that the regular favicon is selected when:
// - The page links to a Web Manifest.
// - The Web Manifest does not contain any icon URLs (it is not a 404).
// - The page has an icon URL provided via a <link rel="icon"> tag.
// - The database does not know about the page URL.
// - The database does not know about the manifest URL.
// - The database knows about the icon URL.
TEST_F(FaviconHandlerManifestsEnabledTest,
UnknownManifestWithoutIconsAndKnownRegularIcons) {
const GURL kSomePreviousPageURL("https://www.google.com/previous");
delegate_.fake_manifest_downloader().Add(kManifestURL,
std::vector<favicon::FaviconURL>());
favicon_service_.fake()->Store(
kSomePreviousPageURL, kIconURL12x12,
CreateRawBitmapResult(kIconURL12x12, kTouchIcon));
EXPECT_CALL(favicon_service_, SetFavicons(_, _, _, _)).Times(0);
EXPECT_CALL(favicon_service_, DeleteFaviconMappings(_, _)).Times(0);
// UnableToDownloadFavicon() is expected to prevent repeated downloads of the
// same manifest (which is not otherwise cached, since it doesn't contain
// icons).
EXPECT_CALL(favicon_service_, UnableToDownloadFavicon(kManifestURL));
EXPECT_CALL(favicon_service_,
UpdateFaviconMappingsAndFetch(_, kManifestURL, _, _, _, _));
EXPECT_CALL(favicon_service_,
UpdateFaviconMappingsAndFetch(_, kIconURL12x12, _, _, _, _));
EXPECT_CALL(delegate_, OnFaviconUpdated(_, _, kIconURL12x12, _, _));
RunHandlerWithSimpleTouchIconCandidates({kIconURL12x12}, kManifestURL);
EXPECT_THAT(favicon_service_.fake()->db_requests(),
ElementsAre(kPageURL, kManifestURL, kIconURL12x12));
EXPECT_THAT(delegate_.downloads(), ElementsAre(kManifestURL));
}
// Test that the database remains unmodified when:
// - The page links to a Web Manifest.
// - The Web Manifest does not contain any icon URLs (it is not a 404).
// - The page has an icon URL provided via a <link rel="icon"> tag.
// - The database has a mapping between the page URL to the favicon URL.
TEST_F(FaviconHandlerManifestsEnabledTest,
UnknownManifestWithoutIconsAndRegularIconInHistory) {
delegate_.fake_manifest_downloader().Add(kManifestURL,
std::vector<favicon::FaviconURL>());
favicon_service_.fake()->Store(
kPageURL, kIconURL16x16,
CreateRawBitmapResult(kIconURL16x16, kTouchIcon));
EXPECT_CALL(delegate_, OnFaviconUpdated(_, _, kIconURL16x16, _, _));
EXPECT_CALL(favicon_service_,
UpdateFaviconMappingsAndFetch(_, kManifestURL, _, _, _, _));
RunHandlerWithSimpleTouchIconCandidates({kIconURL16x16}, kManifestURL);
ASSERT_THAT(favicon_service_.fake()->db_requests(),
ElementsAre(kPageURL, kManifestURL));
EXPECT_THAT(delegate_.downloads(), ElementsAre(kManifestURL));
}
// Test that Delegate::OnFaviconUpdated() is called if a page uses Javascript to
// modify the page's <link rel="manifest"> tag to point to a different manifest.
TEST_F(FaviconHandlerManifestsEnabledTest, ManifestUpdateViaJavascript) {
const GURL kManifestURL1("http://www.google.com/manifest1.json");
const GURL kManifestURL2("http://www.google.com/manifest2.json");
const std::vector<favicon::FaviconURL> kManifestIcons1 = {
FaviconURL(kIconURL64x64, kWebManifestIcon, kEmptySizes),
};
const std::vector<favicon::FaviconURL> kManifestIcons2 = {
FaviconURL(kIconURL10x10, kWebManifestIcon, kEmptySizes),
};
delegate_.fake_manifest_downloader().Add(kManifestURL1, kManifestIcons1);
delegate_.fake_manifest_downloader().Add(kManifestURL2, kManifestIcons2);
EXPECT_CALL(delegate_, OnFaviconUpdated(_, _, kManifestURL1, _, _));
std::unique_ptr<FaviconHandler> handler =
RunHandlerWithSimpleTouchIconCandidates({kIconURL12x12}, kManifestURL1);
ASSERT_THAT(favicon_service_.fake()->db_requests(),
ElementsAre(kPageURL, kManifestURL1));
EXPECT_THAT(delegate_.downloads(), ElementsAre(kManifestURL1, kIconURL64x64));
ASSERT_TRUE(VerifyAndClearExpectations());
// Simulate the page changing it's manifest URL via Javascript.
EXPECT_CALL(delegate_, OnFaviconUpdated(_, _, kManifestURL2, _, _));
handler->OnUpdateCandidates(
kPageURL, {FaviconURL(kIconURL12x12, kTouchIcon, kEmptySizes)},
kManifestURL2);
base::RunLoop().RunUntilIdle();
ASSERT_THAT(favicon_service_.fake()->db_requests(),
ElementsAre(kManifestURL2));
EXPECT_THAT(delegate_.downloads(), ElementsAre(kManifestURL2, kIconURL10x10));
}
// Test that Delegate::OnFaviconUpdated() is called if a page uses Javascript to
// remove the page's <link rel="manifest"> tag (i.e. no web manifest) WHILE a
// lookup to the history database is ongoing for the manifest URL.
TEST_F(FaviconHandlerManifestsEnabledTest,
RemoveManifestViaJavascriptWhileDatabaseLookup) {
const std::vector<favicon::FaviconURL> kManifestIcons = {
FaviconURL(kIconURL64x64, kWebManifestIcon, kEmptySizes),
};
delegate_.fake_manifest_downloader().Add(kManifestURL, kManifestIcons);
// Defer the database lookup completion to control the exact timing.
favicon_service_.fake()->SetRunCallbackManuallyForUrl(kManifestURL);
std::unique_ptr<FaviconHandler> handler =
RunHandlerWithSimpleTouchIconCandidates({kIconURL12x12}, kManifestURL);
ASSERT_THAT(favicon_service_.fake()->db_requests(),
ElementsAre(kPageURL, kManifestURL));
// Database lookup for |kManifestURL| is ongoing.
ASSERT_TRUE(favicon_service_.fake()->HasPendingManualCallback());
// Simulate the page changing it's manifest URL to empty via Javascript.
EXPECT_CALL(delegate_, OnFaviconUpdated(_, _, kIconURL12x12, _, _));
handler->OnUpdateCandidates(
kPageURL, {FaviconURL(kIconURL12x12, kTouchIcon, kEmptySizes)}, GURL());
// Complete the lookup.
EXPECT_TRUE(favicon_service_.fake()->RunCallbackManually());
base::RunLoop().RunUntilIdle();
ASSERT_THAT(favicon_service_.fake()->db_requests(),
ElementsAre(kPageURL, kManifestURL, kIconURL12x12));
EXPECT_THAT(delegate_.downloads(), ElementsAre(kIconURL12x12));
}
// Tests that favicon mappings are removed if a page initially lists no regular
// favicons but does link to a web manifest, and later uses Javascript to remove
// the manifest URL.
TEST_F(FaviconHandlerManifestsEnabledTest,
RemoveManifestViaJavascriptDeletesMappings) {
const std::vector<favicon::FaviconURL> kManifestIcons = {
FaviconURL(kIconURL64x64, kWebManifestIcon, kEmptySizes),
};
delegate_.fake_manifest_downloader().Add(kManifestURL, kManifestIcons);
EXPECT_CALL(favicon_service_, SetFavicons(base::flat_set<GURL>{kPageURL},
kManifestURL, kWebManifestIcon, _));
std::unique_ptr<FaviconHandler> handler =
RunHandlerWithSimpleTouchIconCandidates(URLVector(), kManifestURL);
ASSERT_THAT(favicon_service_.fake()->db_requests(),
ElementsAre(kPageURL, kManifestURL));
EXPECT_THAT(delegate_.downloads(), ElementsAre(kManifestURL, kIconURL64x64));
ASSERT_TRUE(VerifyAndClearExpectations());
// Simulate the page removing it's manifest URL via Javascript.
EXPECT_CALL(
favicon_service_,
DeleteFaviconMappings(base::flat_set<GURL>{kPageURL}, kWebManifestIcon));
handler->OnUpdateCandidates(kPageURL, std::vector<FaviconURL>(), GURL());
base::RunLoop().RunUntilIdle();
ASSERT_THAT(favicon_service_.fake()->db_requests(), IsEmpty());
EXPECT_THAT(delegate_.downloads(), IsEmpty());
}
// Test that Delegate::OnFaviconUpdated() is called a page without manifest uses
// Javascript to add a <link rel="manifest"> tag (i.e. a new web manifest) WHILE
// a lookup to the history database is ongoing for the icon URL.
TEST_F(FaviconHandlerManifestsEnabledTest,
AddManifestViaJavascriptWhileDatabaseLookup) {
const std::vector<favicon::FaviconURL> kManifestIcons = {
FaviconURL(kIconURL64x64, kWebManifestIcon, kEmptySizes),
};
delegate_.fake_manifest_downloader().Add(kManifestURL, kManifestIcons);
// Defer the database lookup completion to control the exact timing.
favicon_service_.fake()->SetRunCallbackManuallyForUrl(kIconURL12x12);
std::unique_ptr<FaviconHandler> handler =
RunHandlerWithSimpleTouchIconCandidates({kIconURL12x12},
/*manifest_url=*/GURL());
ASSERT_THAT(favicon_service_.fake()->db_requests(),
ElementsAre(kPageURL, kIconURL12x12));
// Database lookup for |kIconURL12x12| is ongoing.
ASSERT_TRUE(favicon_service_.fake()->HasPendingManualCallback());
// Simulate the page changing it's manifest URL to |kManifestURL| via
// Javascript.
EXPECT_CALL(delegate_, OnFaviconUpdated(_, _, kManifestURL, _, _));
handler->OnUpdateCandidates(
kPageURL, {FaviconURL(kIconURL12x12, kTouchIcon, kEmptySizes)},
kManifestURL);
// Complete the lookup.
EXPECT_TRUE(favicon_service_.fake()->RunCallbackManually());
base::RunLoop().RunUntilIdle();
ASSERT_THAT(favicon_service_.fake()->db_requests(),
ElementsAre(kPageURL, kIconURL12x12, kManifestURL));
EXPECT_THAT(delegate_.downloads(), ElementsAre(kManifestURL, kIconURL64x64));
}
// Test that SetFavicons() is not called when:
// - The page doesn't initially link to a Web Manifest.
// - The page has an icon URL provided via a <link rel="icon"> tag.
// - The database does not know about the page URL or icon URL.
// - While the icon is being downloaded, the page uses Javascript to add a
// <link rel="manifest"> tag.
// - The database has bitmap data for the manifest URL.
TEST_F(FaviconHandlerManifestsEnabledTest,
AddKnownManifestViaJavascriptWhileImageDownload) {
const GURL kSomePreviousPageURL("https://www.google.com/previous");
favicon_service_.fake()->Store(
kSomePreviousPageURL, kManifestURL,
CreateRawBitmapResult(kManifestURL, kWebManifestIcon));
// Defer the image download completion to control the exact timing.
delegate_.fake_image_downloader().SetRunCallbackManuallyForUrl(kIconURL16x16);
std::unique_ptr<FaviconHandler> handler =
RunHandlerWithSimpleTouchIconCandidates({kIconURL16x16},
/*manifest_url=*/GURL());
ASSERT_TRUE(VerifyAndClearExpectations());
ASSERT_TRUE(delegate_.fake_image_downloader().HasPendingManualCallback());
// Simulate the page changing it's manifest URL to |kManifestURL| via
// Javascript. Should invalidate the ongoing image download.
EXPECT_CALL(favicon_service_, SetFavicons(_, _, _, _)).Times(0);
EXPECT_CALL(delegate_, OnFaviconUpdated(_, _, kManifestURL, _, _));
handler->OnUpdateCandidates(
kPageURL, {FaviconURL(kIconURL16x16, kTouchIcon, kEmptySizes)},
kManifestURL);
// Finalizes download, which should be thrown away as the manifest URL was
// provided.
EXPECT_TRUE(delegate_.fake_image_downloader().RunCallbackManually());
base::RunLoop().RunUntilIdle();
}
// Test that SetFavicons() is called with the icon URL when:
// - The page doesn't initially link to a Web Manifest.
// - The page has an icon URL provided via a <link rel="icon"> tag.
// - The database does not know about the page URL or icon URL.
// - During the database lookup, the page uses Javascript to add a
// <link rel="manifest"> tag.
// - The database does not know about the manifest URL.
// - The manifest contains no icons.
TEST_F(FaviconHandlerManifestsEnabledTest,
AddManifestWithoutIconsViaJavascriptWhileDatabaseLookup) {
delegate_.fake_manifest_downloader().Add(kManifestURL,
std::vector<favicon::FaviconURL>());
// Defer the database lookup completion to control the exact timing.
favicon_service_.fake()->SetRunCallbackManuallyForUrl(kIconURL16x16);
EXPECT_CALL(favicon_service_, SetFavicons(_, _, _, _)).Times(0);
EXPECT_CALL(delegate_, OnFaviconUpdated(_, _, _, _, _)).Times(0);
std::unique_ptr<FaviconHandler> handler =
RunHandlerWithSimpleTouchIconCandidates({kIconURL16x16},
/*manifest_url=*/GURL());
ASSERT_TRUE(VerifyAndClearExpectations());
ASSERT_TRUE(favicon_service_.fake()->HasPendingManualCallback());
EXPECT_CALL(favicon_service_, SetFavicons(_, kIconURL16x16, _, _));
EXPECT_CALL(delegate_, OnFaviconUpdated(_, _, kIconURL16x16, _, _));
handler->OnUpdateCandidates(
kPageURL, {FaviconURL(kIconURL16x16, kTouchIcon, kEmptySizes)},
kManifestURL);
// Finalizes lookup, which should be thrown away as the manifest URLs was
// provided.
EXPECT_TRUE(favicon_service_.fake()->RunCallbackManually());
base::RunLoop().RunUntilIdle();
// The manifest URL interrupted the original processing of kIconURL16x16, but
// a second one should have been started.
EXPECT_TRUE(favicon_service_.fake()->RunCallbackManually());
base::RunLoop().RunUntilIdle();
}
// Test that SetFavicons() is called when:
// - The page links to one Web Manifest, which contains one icon.
// - The database does not know about the page URL, icon URL or manifest URL.
// - During image download, the page updates the manifest URL to point to
// another manifest.
// - The second manifest contains the same icons as the first.
TEST_F(FaviconHandlerManifestsEnabledTest,
UpdateManifestWithSameIconURLsWhileDownloading) {
const GURL kManifestURL1("http://www.google.com/manifest1.json");
const GURL kManifestURL2("http://www.google.com/manifest2.json");
const std::vector<favicon::FaviconURL> kManifestIcons = {
FaviconURL(kIconURL64x64, kWebManifestIcon, kEmptySizes),
};
delegate_.fake_manifest_downloader().Add(kManifestURL1, kManifestIcons);
delegate_.fake_manifest_downloader().Add(kManifestURL2, kManifestIcons);
// Defer the download completion to control the exact timing.
delegate_.fake_image_downloader().SetRunCallbackManuallyForUrl(kIconURL64x64);
std::unique_ptr<FaviconHandler> handler =
RunHandlerWithSimpleTouchIconCandidates(URLVector(), kManifestURL1);
EXPECT_THAT(delegate_.downloads(), ElementsAre(kManifestURL1, kIconURL64x64));
ASSERT_TRUE(VerifyAndClearExpectations());
ASSERT_TRUE(delegate_.fake_image_downloader().HasPendingManualCallback());
// Calling OnUpdateCandidates() with a different manifest URL should trigger
// its download.
handler->OnUpdateCandidates(kPageURL, std::vector<favicon::FaviconURL>(),
kManifestURL2);
base::RunLoop().RunUntilIdle();
EXPECT_THAT(delegate_.downloads(), ElementsAre(kManifestURL2, kIconURL64x64));
// Complete the download.
EXPECT_CALL(favicon_service_, SetFavicons(_, kManifestURL2, _, _));
EXPECT_CALL(delegate_, OnFaviconUpdated(_, _, kManifestURL2, _, _));
EXPECT_TRUE(delegate_.fake_image_downloader().RunCallbackManually());
base::RunLoop().RunUntilIdle();
}
} // namespace
} // namespace favicon