Merge Chromium + Blink git repositories
[chromium-blink-merge.git] / components / search_provider_logos / logo_tracker.cc
blob6e8ad20f8d476968c84479d2cd4afce382aa703b
1 // Copyright 2014 The Chromium Authors. All rights reserved.
2 // Use of this source code is governed by a BSD-style license that can be
3 // found in the LICENSE file.
5 #include "components/search_provider_logos/logo_tracker.h"
7 #include <algorithm>
9 #include "base/message_loop/message_loop.h"
10 #include "base/task_runner_util.h"
11 #include "base/thread_task_runner_handle.h"
12 #include "base/time/default_clock.h"
13 #include "net/http/http_response_headers.h"
14 #include "net/url_request/url_fetcher.h"
15 #include "net/url_request/url_request_context_getter.h"
16 #include "net/url_request/url_request_status.h"
18 namespace search_provider_logos {
20 namespace {
22 const int64 kMaxDownloadBytes = 1024 * 1024;
24 //const int kDecodeLogoTimeoutSeconds = 30;
26 // Returns whether the metadata for the cached logo indicates that the logo is
27 // OK to show, i.e. it's not expired or it's allowed to be shown temporarily
28 // after expiration.
29 bool IsLogoOkToShow(const LogoMetadata& metadata, base::Time now) {
30 base::TimeDelta offset =
31 base::TimeDelta::FromMilliseconds(kMaxTimeToLiveMS * 3 / 2);
32 base::Time distant_past = now - offset;
33 base::Time distant_future = now + offset;
34 // Sanity check so logos aren't accidentally cached forever.
35 if (metadata.expiration_time < distant_past ||
36 metadata.expiration_time > distant_future) {
37 return false;
39 return metadata.can_show_after_expiration || metadata.expiration_time >= now;
42 // Reads the logo from the cache and returns it. Returns NULL if the cache is
43 // empty, corrupt, expired, or doesn't apply to the current logo URL.
44 scoped_ptr<EncodedLogo> GetLogoFromCacheOnFileThread(LogoCache* logo_cache,
45 const GURL& logo_url,
46 base::Time now) {
47 const LogoMetadata* metadata = logo_cache->GetCachedLogoMetadata();
48 if (!metadata)
49 return scoped_ptr<EncodedLogo>();
51 if (metadata->source_url != logo_url.spec() ||
52 !IsLogoOkToShow(*metadata, now)) {
53 logo_cache->SetCachedLogo(NULL);
54 return scoped_ptr<EncodedLogo>();
57 return logo_cache->GetCachedLogo().Pass();
60 void DeleteLogoCacheOnFileThread(LogoCache* logo_cache) {
61 delete logo_cache;
64 } // namespace
66 LogoTracker::LogoTracker(
67 base::FilePath cached_logo_directory,
68 scoped_refptr<base::SequencedTaskRunner> file_task_runner,
69 scoped_refptr<base::TaskRunner> background_task_runner,
70 scoped_refptr<net::URLRequestContextGetter> request_context_getter,
71 scoped_ptr<LogoDelegate> delegate)
72 : is_idle_(true),
73 is_cached_logo_valid_(false),
74 logo_delegate_(delegate.Pass()),
75 logo_cache_(new LogoCache(cached_logo_directory)),
76 clock_(new base::DefaultClock()),
77 file_task_runner_(file_task_runner),
78 background_task_runner_(background_task_runner),
79 request_context_getter_(request_context_getter),
80 weak_ptr_factory_(this) {}
82 LogoTracker::~LogoTracker() {
83 ReturnToIdle();
84 file_task_runner_->PostTask(
85 FROM_HERE, base::Bind(&DeleteLogoCacheOnFileThread, logo_cache_));
86 logo_cache_ = NULL;
89 void LogoTracker::SetServerAPI(
90 const GURL& logo_url,
91 const ParseLogoResponse& parse_logo_response_func,
92 const AppendQueryparamsToLogoURL& append_queryparams_func,
93 bool wants_cta) {
94 if (logo_url == logo_url_)
95 return;
97 ReturnToIdle();
99 logo_url_ = logo_url;
100 parse_logo_response_func_ = parse_logo_response_func;
101 append_queryparams_func_ = append_queryparams_func;
102 wants_cta_ = wants_cta;
105 void LogoTracker::GetLogo(LogoObserver* observer) {
106 DCHECK(!logo_url_.is_empty());
107 logo_observers_.AddObserver(observer);
109 if (is_idle_) {
110 is_idle_ = false;
111 base::PostTaskAndReplyWithResult(
112 file_task_runner_.get(),
113 FROM_HERE,
114 base::Bind(&GetLogoFromCacheOnFileThread,
115 logo_cache_,
116 logo_url_,
117 clock_->Now()),
118 base::Bind(&LogoTracker::OnCachedLogoRead,
119 weak_ptr_factory_.GetWeakPtr()));
120 } else if (is_cached_logo_valid_) {
121 observer->OnLogoAvailable(cached_logo_.get(), true);
125 void LogoTracker::RemoveObserver(LogoObserver* observer) {
126 logo_observers_.RemoveObserver(observer);
129 void LogoTracker::SetLogoCacheForTests(scoped_ptr<LogoCache> cache) {
130 DCHECK(cache);
131 file_task_runner_->PostTask(
132 FROM_HERE, base::Bind(&DeleteLogoCacheOnFileThread, logo_cache_));
133 logo_cache_ = cache.release();
136 void LogoTracker::SetClockForTests(scoped_ptr<base::Clock> clock) {
137 clock_ = clock.Pass();
140 void LogoTracker::ReturnToIdle() {
141 // Cancel the current asynchronous operation, if any.
142 fetcher_.reset();
143 weak_ptr_factory_.InvalidateWeakPtrs();
145 // Reset state.
146 is_idle_ = true;
147 cached_logo_.reset();
148 is_cached_logo_valid_ = false;
150 // Clear obsevers.
151 FOR_EACH_OBSERVER(LogoObserver, logo_observers_, OnObserverRemoved());
152 logo_observers_.Clear();
155 void LogoTracker::OnCachedLogoRead(scoped_ptr<EncodedLogo> cached_logo) {
156 DCHECK(!is_idle_);
158 if (cached_logo) {
159 logo_delegate_->DecodeUntrustedImage(
160 cached_logo->encoded_image,
161 base::Bind(&LogoTracker::OnCachedLogoAvailable,
162 weak_ptr_factory_.GetWeakPtr(),
163 cached_logo->metadata));
164 } else {
165 OnCachedLogoAvailable(LogoMetadata(), SkBitmap());
169 void LogoTracker::OnCachedLogoAvailable(const LogoMetadata& metadata,
170 const SkBitmap& image) {
171 DCHECK(!is_idle_);
173 if (!image.isNull()) {
174 cached_logo_.reset(new Logo());
175 cached_logo_->metadata = metadata;
176 cached_logo_->image = image;
178 is_cached_logo_valid_ = true;
179 Logo* logo = cached_logo_.get();
180 FOR_EACH_OBSERVER(LogoObserver, logo_observers_, OnLogoAvailable(logo, true));
181 FetchLogo();
184 void LogoTracker::SetCachedLogo(scoped_ptr<EncodedLogo> logo) {
185 file_task_runner_->PostTask(
186 FROM_HERE,
187 base::Bind(&LogoCache::SetCachedLogo,
188 base::Unretained(logo_cache_),
189 base::Owned(logo.release())));
192 void LogoTracker::SetCachedMetadata(const LogoMetadata& metadata) {
193 file_task_runner_->PostTask(FROM_HERE,
194 base::Bind(&LogoCache::UpdateCachedLogoMetadata,
195 base::Unretained(logo_cache_),
196 metadata));
199 void LogoTracker::FetchLogo() {
200 DCHECK(!fetcher_);
201 DCHECK(!is_idle_);
203 GURL url;
204 std::string fingerprint;
205 if (cached_logo_ && !cached_logo_->metadata.fingerprint.empty() &&
206 cached_logo_->metadata.expiration_time >= clock_->Now()) {
207 fingerprint = cached_logo_->metadata.fingerprint;
209 url = append_queryparams_func_.Run(logo_url_, fingerprint, wants_cta_);
211 fetcher_ = net::URLFetcher::Create(url, net::URLFetcher::GET, this);
212 fetcher_->SetRequestContext(request_context_getter_.get());
213 fetcher_->Start();
216 void LogoTracker::OnFreshLogoParsed(scoped_ptr<EncodedLogo> logo) {
217 DCHECK(!is_idle_);
219 if (logo)
220 logo->metadata.source_url = logo_url_.spec();
222 if (!logo || !logo->encoded_image.get()) {
223 OnFreshLogoAvailable(logo.Pass(), SkBitmap());
224 } else {
225 // Store the value of logo->encoded_image for use below. This ensures that
226 // logo->encoded_image is evaulated before base::Passed(&logo), which sets
227 // logo to NULL.
228 scoped_refptr<base::RefCountedString> encoded_image = logo->encoded_image;
229 logo_delegate_->DecodeUntrustedImage(
230 encoded_image,
231 base::Bind(&LogoTracker::OnFreshLogoAvailable,
232 weak_ptr_factory_.GetWeakPtr(),
233 base::Passed(&logo)));
237 void LogoTracker::OnFreshLogoAvailable(scoped_ptr<EncodedLogo> encoded_logo,
238 const SkBitmap& image) {
239 DCHECK(!is_idle_);
241 if (encoded_logo && !encoded_logo->encoded_image.get() && cached_logo_ &&
242 !encoded_logo->metadata.fingerprint.empty() &&
243 encoded_logo->metadata.fingerprint ==
244 cached_logo_->metadata.fingerprint) {
245 // The cached logo was revalidated, i.e. its fingerprint was verified.
246 // mime_type isn't sent when revalidating, so copy it from the cached logo.
247 encoded_logo->metadata.mime_type = cached_logo_->metadata.mime_type;
248 SetCachedMetadata(encoded_logo->metadata);
249 } else if (encoded_logo && image.isNull()) {
250 // Image decoding failed. Do nothing.
251 } else {
252 scoped_ptr<Logo> logo;
253 // Check if the server returned a valid, non-empty response.
254 if (encoded_logo) {
255 DCHECK(!image.isNull());
256 logo.reset(new Logo());
257 logo->metadata = encoded_logo->metadata;
258 logo->image = image;
261 // Notify observers if a new logo was fetched, or if the new logo is NULL
262 // but the cached logo was non-NULL.
263 if (logo || cached_logo_) {
264 FOR_EACH_OBSERVER(LogoObserver,
265 logo_observers_,
266 OnLogoAvailable(logo.get(), false));
267 SetCachedLogo(encoded_logo.Pass());
271 ReturnToIdle();
274 void LogoTracker::OnURLFetchComplete(const net::URLFetcher* source) {
275 DCHECK(!is_idle_);
276 scoped_ptr<net::URLFetcher> cleanup_fetcher(fetcher_.release());
278 if (!source->GetStatus().is_success() || (source->GetResponseCode() != 200)) {
279 ReturnToIdle();
280 return;
283 scoped_ptr<std::string> response(new std::string());
284 source->GetResponseAsString(response.get());
285 base::Time response_time = clock_->Now();
287 base::PostTaskAndReplyWithResult(
288 background_task_runner_.get(),
289 FROM_HERE,
290 base::Bind(
291 parse_logo_response_func_, base::Passed(&response), response_time),
292 base::Bind(&LogoTracker::OnFreshLogoParsed,
293 weak_ptr_factory_.GetWeakPtr()));
296 void LogoTracker::OnURLFetchDownloadProgress(const net::URLFetcher* source,
297 int64 current,
298 int64 total) {
299 if (total > kMaxDownloadBytes || current > kMaxDownloadBytes) {
300 LOG(WARNING) << "Search provider logo exceeded download size limit";
301 ReturnToIdle();
305 } // namespace search_provider_logos