blob: 49a2b35d0d842deb7e1de6d8f82467b00db85acd [file] [log] [blame]
// Copyright 2015 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#import "ios/chrome/browser/favicon/model/favicon_loader_impl.h"
#import <UIKit/UIKit.h>
#import <functional>
#import <string>
#import "base/apple/foundation_util.h"
#import "base/functional/bind.h"
#import "base/strings/sys_string_conversions.h"
#import "components/favicon/core/fallback_url_util.h"
#import "components/favicon/core/large_icon_service.h"
#import "components/favicon_base/fallback_icon_style.h"
#import "components/favicon_base/favicon_callback.h"
#import "components/favicon_base/favicon_types.h"
#import "ios/chrome/common/ui/favicon/favicon_attributes.h"
#import "net/traffic_annotation/network_traffic_annotation.h"
#import "skia/ext/skia_utils_ios.h"
#import "url/gurl.h"
namespace {
// NetworkTrafficAnnotationTag for fetching favicon from a Google server.
const net::NetworkTrafficAnnotationTag kTrafficAnnotation =
net::DefineNetworkTrafficAnnotation("favicon_loader_get_large_icon", R"(
semantics {
sender: "FaviconLoader"
description:
"Sends a request to a Google server to retrieve the favicon bitmap."
trigger:
"A request can be sent if Chrome does not have a favicon."
data: "Page URL and desired icon size."
destination: GOOGLE_OWNED_SERVICE
}
policy {
cookies_allowed: NO
setting: "This feature cannot be disabled by settings."
policy_exception_justification: "Not implemented."
}
)");
} // namespace
// Class used as a key in the NSCache dictionary.
//
// Allow to cache data for a pair of an URL and the favicon size.
@interface FaviconLoaderCacheKey : NSObject
- (instancetype)initWithURL:(const GURL&)URL
sizeInPoints:(float)sizeInPoints NS_DESIGNATED_INITIALIZER;
- (instancetype)init NS_UNAVAILABLE;
@end
@implementation FaviconLoaderCacheKey {
std::string _URLSpec;
int _sizeInPoints;
}
- (instancetype)initWithURL:(const GURL&)URL sizeInPoints:(float)sizeInPoints {
if ((self = [super init])) {
_URLSpec = URL.is_valid() ? URL.spec() : std::string();
_sizeInPoints = static_cast<int>(round(sizeInPoints));
}
return self;
}
- (BOOL)isEqual:(NSObject*)object {
FaviconLoaderCacheKey* other =
base::apple::ObjCCast<FaviconLoaderCacheKey>(object);
return [self isEqualToCacheKey:other];
}
- (NSUInteger)hash {
const size_t h1 = std::hash<std::string>{}(_URLSpec);
const size_t h2 = std::hash<int>{}(_sizeInPoints);
return h1 ^ (h2 << 1);
}
- (BOOL)isEqualToCacheKey:(FaviconLoaderCacheKey*)other {
if (!other) {
return NO;
}
if (self == other) {
return YES;
}
return _sizeInPoints == other->_sizeInPoints && _URLSpec == other->_URLSpec;
}
@end
// Class representing the parameters for fetching a favicon.
class FaviconLoaderImpl::Request {
public:
using Completion = FaviconLoader::FaviconAttributesCompletionBlock;
// The different types of requests.
enum class Type {
kFaviconForPageUrl,
kFaviconForPageUrlWithFallback,
kFaviconForPageUrlOrHost,
kFaviconForIconUrl,
};
Request(Request&&) = default;
~Request() = default;
// Constructs an identical Request without fallback to google servers.
Request WithoutFallback() {
DCHECK_EQ(type_, Type::kFaviconForPageUrlWithFallback);
return Request{Type::kFaviconForPageUrl, url_, size_in_points_,
min_size_in_points_, completion_};
}
// Constructs a Request to fetch favicon for a page URL, optionally with
// fallback to google servers.
static Request ForPageUrl(const GURL& page_url,
float size_in_points,
float min_size_in_points,
bool fallback_to_google_server,
Completion completion) {
const Type type = fallback_to_google_server
? Type::kFaviconForPageUrlWithFallback
: Type::kFaviconForPageUrl;
return Request{type, page_url, size_in_points, min_size_in_points,
completion};
}
// Constructs a Request to fetch favicon for a page URL or just an host.
static Request ForPageUrlOrHost(const GURL& page_url,
float size_in_points,
Completion completion) {
return Request{Type::kFaviconForPageUrlOrHost, page_url, size_in_points,
/*min_size_in_points=*/0.0, completion};
}
// Constructs a Request to fetch favicon from an icon url directly.
static Request ForIconUrl(const GURL& icon_url,
float size_in_points,
float min_size_in_points,
Completion completion) {
return Request{Type::kFaviconForIconUrl, icon_url, size_in_points,
min_size_in_points, completion};
}
// Return the request's type.
Type type() const { return type_; }
// Returns the requet's URL.
const GURL& url() const { return url_; }
// Returns the size in points for the favicon.
float size_in_points() const { return size_in_points_; }
// Returns the minimum size in points for the favicon.
float min_size_in_points() const { return min_size_in_points_; }
// Returns the key to use when locating the favicon in the cache.
FaviconLoaderCacheKey* key() const {
return [[FaviconLoaderCacheKey alloc]
initWithURL:url_
sizeInPoints:static_cast<int>(round(size_in_points_))];
}
// Returns the default fallback favicon if no icon is found in the cache.
FaviconAttributes* fallback() const {
switch (type_) {
case Type::kFaviconForPageUrl:
case Type::kFaviconForPageUrlWithFallback:
case Type::kFaviconForPageUrlOrHost:
return [FaviconAttributes attributesWithDefaultImage];
case Type::kFaviconForIconUrl:
return [FaviconAttributes
attributesWithImage:[UIImage imageNamed:@"default_world_favicon"]];
}
}
// Invokes the request's completion block with `attributes`.
void completion(FaviconAttributes* attributes) { completion_(attributes); }
private:
Request(Type type,
const GURL& url,
float size_in_points,
float min_size_in_points,
Completion completion)
: type_(type),
url_(url),
size_in_points_(size_in_points),
min_size_in_points_(min_size_in_points),
completion_(completion) {
DCHECK(completion_);
}
const Type type_;
const GURL url_;
const float size_in_points_;
const float min_size_in_points_;
const Completion completion_;
};
FaviconLoaderImpl::FaviconLoaderImpl(
favicon::LargeIconService* large_icon_service)
: large_icon_service_(large_icon_service),
favicon_cache_([[NSCache alloc] init]) {}
FaviconLoaderImpl::~FaviconLoaderImpl() = default;
void FaviconLoaderImpl::FaviconForPageUrl(
const GURL& page_url,
float size_in_points,
float min_size_in_points,
bool fallback_to_google_server,
FaviconAttributesCompletionBlock completion) {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
DCHECK(completion);
FetchFavicon(Request::ForPageUrl(page_url, size_in_points, min_size_in_points,
fallback_to_google_server, completion));
}
void FaviconLoaderImpl::FaviconForPageUrlOrHost(
const GURL& page_url,
float size_in_points,
FaviconAttributesCompletionBlock completion) {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
DCHECK(completion);
FetchFavicon(Request::ForPageUrlOrHost(page_url, size_in_points, completion));
}
void FaviconLoaderImpl::FaviconForIconUrl(
const GURL& icon_url,
float size_in_points,
float min_size_in_points,
FaviconAttributesCompletionBlock completion) {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
DCHECK(completion);
FetchFavicon(Request::ForIconUrl(icon_url, size_in_points, min_size_in_points,
completion));
}
void FaviconLoaderImpl::CancellAllRequests() {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
cancelable_task_tracker_.TryCancelAll();
}
base::WeakPtr<FaviconLoader> FaviconLoaderImpl::AsWeakPtr() {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
return weak_ptr_factory_.GetWeakPtr();
}
void FaviconLoaderImpl::FetchFavicon(Request request) {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
// First check whether the favicon is present in the cache.
if (FaviconAttributes* cached_value = GetCachedAttributes(request.key())) {
request.completion(cached_value);
return;
}
// If the favicon was not cached, then return a fallback image synchronously.
request.completion(request.fallback());
// Fetch asynchronously a better favicon using the LargeIconServvice.
DCHECK(large_icon_service_);
const GURL url = request.url();
const CGFloat scale = UIScreen.mainScreen.scale;
const CGFloat size_in_pixels = scale * request.size_in_points();
const CGFloat min_size_in_pixels = scale * request.min_size_in_points();
auto callback =
base::BindOnce(&FaviconLoaderImpl::OnFaviconFetched,
weak_ptr_factory_.GetWeakPtr(), scale, std::move(request));
switch (request.type()) {
case Request::Type::kFaviconForPageUrl:
case Request::Type::kFaviconForPageUrlWithFallback:
large_icon_service_->GetLargeIconRawBitmapOrFallbackStyleForPageUrl(
url, min_size_in_pixels, size_in_pixels, std::move(callback),
&cancelable_task_tracker_);
break;
case Request::Type::kFaviconForPageUrlOrHost:
large_icon_service_->GetIconRawBitmapOrFallbackStyleForPageUrl(
url, size_in_pixels, std::move(callback), &cancelable_task_tracker_);
break;
case Request::Type::kFaviconForIconUrl:
large_icon_service_->GetLargeIconRawBitmapOrFallbackStyleForIconUrl(
url, min_size_in_pixels, size_in_pixels, std::move(callback),
&cancelable_task_tracker_);
break;
}
}
void FaviconLoaderImpl::OnFaviconFetched(
CGFloat scale,
Request request,
const favicon_base::LargeIconResult& result) {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
// LargeIconResult is either a valid favicon (which can be the default
// favicon) or fallback attributes.
if (result.bitmap.is_valid()) {
scoped_refptr<base::RefCountedMemory> data =
result.bitmap.bitmap_data.get();
// The favicon code assumes favicons are PNG-encoded.
UIImage* favicon = [UIImage
imageWithData:[NSData dataWithBytes:data->front() length:data->size()]
scale:scale];
FaviconAttributes* attributes =
[FaviconAttributes attributesWithImage:favicon];
StoreAttributesInCache(attributes, request.key());
DCHECK(favicon.size.width <= request.size_in_points() &&
favicon.size.height <= request.size_in_points());
request.completion(attributes);
return;
}
if (request.type() == Request::Type::kFaviconForPageUrlWithFallback) {
const GURL url = request.url();
large_icon_service_
->GetLargeIconOrFallbackStyleFromGoogleServerSkippingLocalCache(
url,
/*should_trim_page_url_path=*/false, kTrafficAnnotation,
base::BindOnce(&FaviconLoaderImpl::OnGoogleServerFallbackCompleted,
weak_ptr_factory_.GetWeakPtr(), std::move(request)));
return;
}
// Did not get valid favicon back and are not attempting to retrieve one
// from a Google Server.
DCHECK(result.fallback_icon_style);
FaviconAttributes* attributes = [FaviconAttributes
attributesWithMonogram:base::SysUTF16ToNSString(
favicon::GetFallbackIconText(request.url()))
textColor:[UIColor colorWithWhite:
kFallbackIconDefaultTextColorGrayscale
alpha:1]
backgroundColor:UIColor.clearColor
defaultBackgroundColor:result.fallback_icon_style->
is_default_background_color];
request.completion(attributes);
}
void FaviconLoaderImpl::OnGoogleServerFallbackCompleted(
Request request,
favicon_base::GoogleFaviconServerRequestStatus status) {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
// Update the time when the icon was last requested - postpone thus
// the automatic eviction of the favicon from the favicon database.
large_icon_service_->TouchIconFromGoogleServer(request.url());
// Favicon should be loaded to the db that backs LargeIconService
// now. Fetch it again. Even if the request was not successful, the
// fallback style will be used.
FetchFavicon(request.WithoutFallback());
}
FaviconAttributes* FaviconLoaderImpl::GetCachedAttributes(
FaviconLoaderCacheKey* key) const {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
return [favicon_cache_ objectForKey:key];
}
void FaviconLoaderImpl::StoreAttributesInCache(FaviconAttributes* attributes,
FaviconLoaderCacheKey* key) {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
[favicon_cache_ setObject:attributes forKey:key];
}