blob: ccde02f86886f78ea507ca454cc0bb2c456547cd [file] [log] [blame]
// Copyright 2015 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.
#import "ios/chrome/browser/favicon/favicon_loader.h"
#import <UIKit/UIKit.h>
#import "base/mac/foundation_util.h"
#import "base/mac/scoped_block.h"
#include "base/strings/sys_string_conversions.h"
#include "components/favicon/core/favicon_service.h"
#include "components/favicon_base/favicon_callback.h"
#include "ui/gfx/favicon_size.h"
#include "url/gurl.h"
#if !defined(__has_feature) || !__has_feature(objc_arc)
#error "This file requires ARC support."
#endif
struct FaviconLoader::RequestData {
RequestData() {}
RequestData(NSString* key, FaviconLoader::ImageCompletionBlock block)
: key([key copy]), block(block) {}
~RequestData() {}
NSString* key;
base::mac::ScopedBlock<FaviconLoader::ImageCompletionBlock> block;
};
FaviconLoader::FaviconLoader(favicon::FaviconService* favicon_service)
: favicon_service_(favicon_service),
favicon_cache_([NSMutableDictionary dictionaryWithCapacity:10]) {}
FaviconLoader::~FaviconLoader() {}
// TODO(pinkerton): How do we update the favicon if it's changed on the web?
// We can possibly just rely on this class being purged or the app being killed
// to reset it, but then how do we ensure the FaviconService is updated?
UIImage* FaviconLoader::ImageForURL(const GURL& url,
int types,
ImageCompletionBlock block) {
DCHECK(thread_checker_.CalledOnValidThread());
NSString* key = base::SysUTF8ToNSString(url.spec());
id value = [favicon_cache_ objectForKey:key];
if (value) {
// [NSNull null] returns a singleton, so we can use it as a sentinel value
// and just compare pointers to validate whether the value is the sentinel
// or a valid UIImage.
if (value == [NSNull null])
return [UIImage imageNamed:@"default_favicon"];
return base::mac::ObjCCastStrict<UIImage>(value);
}
// Kick off an async request for the favicon.
if (favicon_service_) {
int size = gfx::kFaviconSize * [UIScreen mainScreen].scale;
std::unique_ptr<RequestData> request_data(new RequestData(key, block));
favicon_base::FaviconResultsCallback callback =
base::Bind(&FaviconLoader::OnFaviconAvailable, base::Unretained(this),
base::Passed(&request_data));
favicon_service_->GetFaviconForPageURL(url, types, size, callback,
&cancelable_task_tracker_);
}
return [UIImage imageNamed:@"default_favicon"];
}
void FaviconLoader::PurgeCache() {
DCHECK(thread_checker_.CalledOnValidThread());
cancelable_task_tracker_.TryCancelAll();
favicon_cache_ = [NSMutableDictionary dictionaryWithCapacity:10];
}
void FaviconLoader::OnFaviconAvailable(
std::unique_ptr<RequestData> request_data,
const std::vector<favicon_base::FaviconRawBitmapResult>&
favicon_bitmap_results) {
DCHECK(request_data);
DCHECK(thread_checker_.CalledOnValidThread());
if (favicon_bitmap_results.size() < 1 ||
!favicon_bitmap_results[0].is_valid()) {
// Return early if there were no results or if it is invalid, after adding a
// "no favicon" entry to the cache so that we don't keep trying to fetch a
// missing favicon over and over.
[favicon_cache_ setObject:[NSNull null] forKey:request_data->key];
return;
}
// The favicon code assumes favicons are PNG-encoded.
NSData* image_data =
[NSData dataWithBytes:favicon_bitmap_results[0].bitmap_data->front()
length:favicon_bitmap_results[0].bitmap_data->size()];
UIImage* favicon =
[UIImage imageWithData:image_data scale:[[UIScreen mainScreen] scale]];
[favicon_cache_ setObject:favicon forKey:request_data->key];
// Call the block to tell the caller this is complete.
if (request_data->block)
(request_data->block.get())(favicon);
}