1 // Copyright 2017 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.
4
5 #include "components/search_provider_logos/logo_service_impl.h"
6
7 #include <algorithm>
8 #include <utility>
9
10 #include "base/bind.h"
11 #include "base/command_line.h"
12 #include "base/feature_list.h"
13 #include "base/macros.h"
14 #include "base/memory/weak_ptr.h"
15 #include "base/metrics/field_trial_params.h"
16 #include "base/metrics/histogram_macros.h"
17 #include "base/task/post_task.h"
18 #include "base/task/thread_pool.h"
19 #include "base/task_runner_util.h"
20 #include "base/threading/thread_task_runner_handle.h"
21 #include "base/time/default_clock.h"
22 #include "build/build_config.h"
23 #include "components/image_fetcher/core/image_decoder.h"
24 #include "components/search_engines/search_terms_data.h"
25 #include "components/search_engines/template_url_service.h"
26 #include "components/search_provider_logos/fixed_logo_api.h"
27 #include "components/search_provider_logos/google_logo_api.h"
28 #include "components/search_provider_logos/logo_cache.h"
29 #include "components/search_provider_logos/logo_observer.h"
30 #include "components/search_provider_logos/switches.h"
31 #include "net/http/http_status_code.h"
32 #include "net/traffic_annotation/network_traffic_annotation.h"
33 #include "services/network/public/cpp/shared_url_loader_factory.h"
34 #include "services/network/public/cpp/simple_url_loader.h"
35 #include "ui/gfx/image/image.h"
36
37 namespace search_provider_logos {
38 namespace {
39
40 const int64_t kMaxDownloadBytes = 1024 * 1024;
41 const int kDecodeLogoTimeoutSeconds = 30;
42
43 // Implements a callback for image_fetcher::ImageDecoder. If Run() is called on
44 // a callback returned by GetCallback() within 30 seconds, forwards the decoded
45 // image to the wrapped callback. If not, sends an empty image to the wrapped
46 // callback instead. Either way, deletes the object and prevents further calls.
47 //
48 // TODO(sfiera): find a more idiomatic way of setting a deadline on the
49 // callback. This is implemented as a self-deleting object in part because it
50 // needed to when it used to be a delegate and in part because I couldn't figure
51 // out a better way, now that it isn't.
52 class ImageDecodedHandlerWithTimeout {
53 public:
Wrap(const base::RepeatingCallback<void (const SkBitmap &)> & image_decoded_callback)54 static base::RepeatingCallback<void(const gfx::Image&)> Wrap(
55 const base::RepeatingCallback<void(const SkBitmap&)>&
56 image_decoded_callback) {
57 auto* handler = new ImageDecodedHandlerWithTimeout(image_decoded_callback);
58 base::ThreadTaskRunnerHandle::Get()->PostDelayedTask(
59 FROM_HERE,
60 base::BindOnce(&ImageDecodedHandlerWithTimeout::OnImageDecoded,
61 handler->weak_ptr_factory_.GetWeakPtr(), gfx::Image()),
62 base::TimeDelta::FromSeconds(kDecodeLogoTimeoutSeconds));
63 return base::BindRepeating(&ImageDecodedHandlerWithTimeout::OnImageDecoded,
64 handler->weak_ptr_factory_.GetWeakPtr());
65 }
66
67 private:
ImageDecodedHandlerWithTimeout(const base::RepeatingCallback<void (const SkBitmap &)> & image_decoded_callback)68 explicit ImageDecodedHandlerWithTimeout(
69 const base::RepeatingCallback<void(const SkBitmap&)>&
70 image_decoded_callback)
71 : image_decoded_callback_(image_decoded_callback) {}
72
OnImageDecoded(const gfx::Image & decoded_image)73 void OnImageDecoded(const gfx::Image& decoded_image) {
74 image_decoded_callback_.Run(decoded_image.AsBitmap());
75 delete this;
76 }
77
78 base::RepeatingCallback<void(const SkBitmap&)> image_decoded_callback_;
79 base::WeakPtrFactory<ImageDecodedHandlerWithTimeout> weak_ptr_factory_{this};
80
81 DISALLOW_COPY_AND_ASSIGN(ImageDecodedHandlerWithTimeout);
82 };
83
ObserverOnLogoAvailable(LogoObserver * observer,bool from_cache,LogoCallbackReason type,const base::Optional<Logo> & logo)84 void ObserverOnLogoAvailable(LogoObserver* observer,
85 bool from_cache,
86 LogoCallbackReason type,
87 const base::Optional<Logo>& logo) {
88 switch (type) {
89 case LogoCallbackReason::DISABLED:
90 case LogoCallbackReason::CANCELED:
91 case LogoCallbackReason::FAILED:
92 break;
93
94 case LogoCallbackReason::REVALIDATED:
95 observer->OnCachedLogoRevalidated();
96 break;
97
98 case LogoCallbackReason::DETERMINED:
99 observer->OnLogoAvailable(logo ? &logo.value() : nullptr, from_cache);
100 break;
101 }
102 if (!from_cache) {
103 observer->OnObserverRemoved();
104 }
105 }
106
RunCallbacksWithDisabled(LogoCallbacks callbacks)107 void RunCallbacksWithDisabled(LogoCallbacks callbacks) {
108 if (callbacks.on_cached_encoded_logo_available) {
109 std::move(callbacks.on_cached_encoded_logo_available)
110 .Run(LogoCallbackReason::DISABLED, base::nullopt);
111 }
112 if (callbacks.on_cached_decoded_logo_available) {
113 std::move(callbacks.on_cached_decoded_logo_available)
114 .Run(LogoCallbackReason::DISABLED, base::nullopt);
115 }
116 if (callbacks.on_fresh_encoded_logo_available) {
117 std::move(callbacks.on_fresh_encoded_logo_available)
118 .Run(LogoCallbackReason::DISABLED, base::nullopt);
119 }
120 if (callbacks.on_fresh_decoded_logo_available) {
121 std::move(callbacks.on_fresh_decoded_logo_available)
122 .Run(LogoCallbackReason::DISABLED, base::nullopt);
123 }
124 }
125
126 // Returns whether the metadata for the cached logo indicates that the logo is
127 // OK to show, i.e. it's not expired or it's allowed to be shown temporarily
128 // after expiration.
IsLogoOkToShow(const LogoMetadata & metadata,base::Time now)129 bool IsLogoOkToShow(const LogoMetadata& metadata, base::Time now) {
130 base::TimeDelta offset =
131 base::TimeDelta::FromMilliseconds(kMaxTimeToLiveMS * 3 / 2);
132 base::Time distant_past = now - offset;
133 // Sanity check so logos aren't accidentally cached forever.
134 if (metadata.expiration_time < distant_past) {
135 return false;
136 }
137 return metadata.can_show_after_expiration || metadata.expiration_time >= now;
138 }
139
140 // Reads the logo from the cache and returns it. Returns NULL if the cache is
141 // empty, corrupt, expired, or doesn't apply to the current logo URL.
GetLogoFromCacheOnFileThread(LogoCache * logo_cache,const GURL & logo_url,base::Time now)142 std::unique_ptr<EncodedLogo> GetLogoFromCacheOnFileThread(LogoCache* logo_cache,
143 const GURL& logo_url,
144 base::Time now) {
145 const LogoMetadata* metadata = logo_cache->GetCachedLogoMetadata();
146 if (!metadata)
147 return nullptr;
148
149 if (metadata->source_url != logo_url || !IsLogoOkToShow(*metadata, now)) {
150 logo_cache->SetCachedLogo(nullptr);
151 return nullptr;
152 }
153
154 return logo_cache->GetCachedLogo();
155 }
156
NotifyAndClear(std::vector<EncodedLogoCallback> * encoded_callbacks,std::vector<LogoCallback> * decoded_callbacks,LogoCallbackReason type,const EncodedLogo * encoded_logo,const Logo * decoded_logo)157 void NotifyAndClear(std::vector<EncodedLogoCallback>* encoded_callbacks,
158 std::vector<LogoCallback>* decoded_callbacks,
159 LogoCallbackReason type,
160 const EncodedLogo* encoded_logo,
161 const Logo* decoded_logo) {
162 auto opt_encoded_logo =
163 encoded_logo ? base::Optional<EncodedLogo>(*encoded_logo) : base::nullopt;
164 for (EncodedLogoCallback& callback : *encoded_callbacks) {
165 std::move(callback).Run(type, opt_encoded_logo);
166 }
167 encoded_callbacks->clear();
168
169 auto opt_decoded_logo =
170 decoded_logo ? base::Optional<Logo>(*decoded_logo) : base::nullopt;
171 for (LogoCallback& callback : *decoded_callbacks) {
172 std::move(callback).Run(type, opt_decoded_logo);
173 }
174 decoded_callbacks->clear();
175 }
176
177 } // namespace
178
LogoServiceImpl(const base::FilePath & cache_directory,signin::IdentityManager * identity_manager,TemplateURLService * template_url_service,std::unique_ptr<image_fetcher::ImageDecoder> image_decoder,scoped_refptr<network::SharedURLLoaderFactory> url_loader_factory,base::RepeatingCallback<bool ()> want_gray_logo_getter)179 LogoServiceImpl::LogoServiceImpl(
180 const base::FilePath& cache_directory,
181 signin::IdentityManager* identity_manager,
182 TemplateURLService* template_url_service,
183 std::unique_ptr<image_fetcher::ImageDecoder> image_decoder,
184 scoped_refptr<network::SharedURLLoaderFactory> url_loader_factory,
185 base::RepeatingCallback<bool()> want_gray_logo_getter)
186 : cache_directory_(cache_directory),
187 identity_manager_(identity_manager),
188 template_url_service_(template_url_service),
189 url_loader_factory_(url_loader_factory),
190 want_gray_logo_getter_(std::move(want_gray_logo_getter)),
191 image_decoder_(std::move(image_decoder)),
192 is_idle_(true),
193 is_cached_logo_valid_(false),
194 cache_task_runner_(base::ThreadPool::CreateSequencedTaskRunner(
195 {base::MayBlock(), base::TaskPriority::USER_VISIBLE,
196 base::TaskShutdownBehavior::SKIP_ON_SHUTDOWN})),
197 logo_cache_(new LogoCache(cache_directory_),
198 base::OnTaskRunnerDeleter(cache_task_runner_)) {
199 identity_manager_->AddObserver(this);
200 }
201
202 LogoServiceImpl::~LogoServiceImpl() = default;
203
Shutdown()204 void LogoServiceImpl::Shutdown() {
205 // The IdentityManager may be destroyed at any point after Shutdown,
206 // so make sure we drop any references to it.
207 identity_manager_->RemoveObserver(this);
208 ReturnToIdle(kDownloadOutcomeNotTracked);
209 }
210
GetLogo(search_provider_logos::LogoObserver * observer)211 void LogoServiceImpl::GetLogo(search_provider_logos::LogoObserver* observer) {
212 LogoCallbacks callbacks;
213 callbacks.on_cached_decoded_logo_available =
214 base::BindOnce(ObserverOnLogoAvailable, observer, true);
215 callbacks.on_fresh_decoded_logo_available =
216 base::BindOnce(ObserverOnLogoAvailable, observer, false);
217 GetLogo(std::move(callbacks), false);
218 }
219
GetLogo(LogoCallbacks callbacks,bool for_webui_ntp)220 void LogoServiceImpl::GetLogo(LogoCallbacks callbacks, bool for_webui_ntp) {
221 if (!template_url_service_) {
222 RunCallbacksWithDisabled(std::move(callbacks));
223 return;
224 }
225
226 const TemplateURL* template_url =
227 template_url_service_->GetDefaultSearchProvider();
228 if (!template_url) {
229 RunCallbacksWithDisabled(std::move(callbacks));
230 return;
231 }
232
233 const base::CommandLine* command_line =
234 base::CommandLine::ForCurrentProcess();
235
236 GURL logo_url;
237 if (command_line->HasSwitch(switches::kSearchProviderLogoURL)) {
238 logo_url = GURL(
239 command_line->GetSwitchValueASCII(switches::kSearchProviderLogoURL));
240 } else {
241 #if defined(OS_ANDROID)
242 // Non-Google default search engine logos are currently enabled only on
243 // Android (https://crbug.com/737283).
244 logo_url = template_url->logo_url();
245 #endif
246 }
247
248 GURL base_url;
249 GURL doodle_url;
250 const bool is_google = template_url->url_ref().HasGoogleBaseURLs(
251 template_url_service_->search_terms_data());
252 if (is_google) {
253 // TODO(treib): Put the Google doodle URL into prepopulated_engines.json.
254 base_url =
255 GURL(template_url_service_->search_terms_data().GoogleBaseURLValue());
256 doodle_url = search_provider_logos::GetGoogleDoodleURL(base_url);
257 } else {
258 if (command_line->HasSwitch(switches::kThirdPartyDoodleURL)) {
259 doodle_url = GURL(
260 command_line->GetSwitchValueASCII(switches::kThirdPartyDoodleURL));
261 } else {
262 doodle_url = template_url->doodle_url();
263 }
264 base_url = doodle_url.GetOrigin();
265 }
266
267 if (!logo_url.is_valid() && !doodle_url.is_valid()) {
268 RunCallbacksWithDisabled(std::move(callbacks));
269 return;
270 }
271
272 if (!clock_) {
273 clock_ = base::DefaultClock::GetInstance();
274 }
275
276 const bool use_fixed_logo = !doodle_url.is_valid();
277 if (use_fixed_logo) {
278 SetServerAPI(
279 logo_url,
280 base::BindRepeating(&search_provider_logos::ParseFixedLogoResponse),
281 base::BindRepeating(&search_provider_logos::UseFixedLogoUrl));
282 } else {
283 // We encode the type of doodle (regular or gray) in the URL so that the
284 // logo cache gets cleared when that value changes.
285 GURL prefilled_url = AppendPreliminaryParamsToDoodleURL(
286 want_gray_logo_getter_.Run(), for_webui_ntp, doodle_url);
287 SetServerAPI(
288 prefilled_url,
289 base::BindRepeating(&search_provider_logos::ParseDoodleLogoResponse,
290 base_url),
291 base::BindRepeating(
292 &search_provider_logos::AppendFingerprintParamToDoodleURL));
293 }
294
295 DCHECK(!logo_url_.is_empty());
296 DCHECK(callbacks.on_cached_decoded_logo_available ||
297 callbacks.on_cached_encoded_logo_available ||
298 callbacks.on_fresh_decoded_logo_available ||
299 callbacks.on_fresh_encoded_logo_available);
300
301 if (callbacks.on_cached_encoded_logo_available) {
302 on_cached_encoded_logo_.push_back(
303 std::move(callbacks.on_cached_encoded_logo_available));
304 }
305 if (callbacks.on_cached_decoded_logo_available) {
306 on_cached_decoded_logo_.push_back(
307 std::move(callbacks.on_cached_decoded_logo_available));
308 }
309 if (callbacks.on_fresh_encoded_logo_available) {
310 on_fresh_encoded_logo_.push_back(
311 std::move(callbacks.on_fresh_encoded_logo_available));
312 }
313 if (callbacks.on_fresh_decoded_logo_available) {
314 on_fresh_decoded_logo_.push_back(
315 std::move(callbacks.on_fresh_decoded_logo_available));
316 }
317
318 if (is_idle_) {
319 is_idle_ = false;
320
321 base::PostTaskAndReplyWithResult(
322 cache_task_runner_.get(), FROM_HERE,
323 base::BindOnce(&GetLogoFromCacheOnFileThread,
324 base::Unretained(logo_cache_.get()), logo_url_,
325 clock_->Now()),
326 base::BindOnce(&LogoServiceImpl::OnCachedLogoRead,
327 weak_ptr_factory_.GetWeakPtr()));
328 } else if (is_cached_logo_valid_) {
329 NotifyAndClear(&on_cached_encoded_logo_, &on_cached_decoded_logo_,
330 LogoCallbackReason::DETERMINED, cached_encoded_logo_.get(),
331 cached_logo_.get());
332 }
333 }
334
SetLogoCacheForTests(std::unique_ptr<LogoCache> cache)335 void LogoServiceImpl::SetLogoCacheForTests(std::unique_ptr<LogoCache> cache) {
336 // |logo_cache_| has a custom deleter, which makes the two unique_ptrs
337 // be different types. so one can't be moved on top of the other.
338 logo_cache_.reset(std::move(cache).release());
339 }
340
SetClockForTests(base::Clock * clock)341 void LogoServiceImpl::SetClockForTests(base::Clock* clock) {
342 clock_ = clock;
343 }
344
SetServerAPI(const GURL & logo_url,const ParseLogoResponse & parse_logo_response_func,const AppendQueryparamsToLogoURL & append_queryparams_func)345 void LogoServiceImpl::SetServerAPI(
346 const GURL& logo_url,
347 const ParseLogoResponse& parse_logo_response_func,
348 const AppendQueryparamsToLogoURL& append_queryparams_func) {
349 if (logo_url == logo_url_)
350 return;
351
352 ReturnToIdle(kDownloadOutcomeNotTracked);
353
354 logo_url_ = logo_url;
355 parse_logo_response_func_ = parse_logo_response_func;
356 append_queryparams_func_ = append_queryparams_func;
357 }
358
ClearCachedLogo()359 void LogoServiceImpl::ClearCachedLogo() {
360 // First cancel any fetch that might be ongoing.
361 ReturnToIdle(kDownloadOutcomeNotTracked);
362 // Then clear any cached logo.
363 SetCachedLogo(nullptr);
364 }
365
ReturnToIdle(int outcome)366 void LogoServiceImpl::ReturnToIdle(int outcome) {
367 if (outcome != kDownloadOutcomeNotTracked) {
368 UMA_HISTOGRAM_ENUMERATION("NewTabPage.LogoDownloadOutcome",
369 static_cast<LogoDownloadOutcome>(outcome),
370 DOWNLOAD_OUTCOME_COUNT);
371 }
372
373 // Cancel the current asynchronous operation, if any.
374 loader_.reset();
375 weak_ptr_factory_.InvalidateWeakPtrs();
376
377 // Reset state.
378 is_idle_ = true;
379 cached_logo_.reset();
380 cached_encoded_logo_.reset();
381 is_cached_logo_valid_ = false;
382
383 // Clear callbacks.
384 NotifyAndClear(&on_cached_encoded_logo_, &on_cached_decoded_logo_,
385 LogoCallbackReason::CANCELED, nullptr, nullptr);
386 NotifyAndClear(&on_fresh_encoded_logo_, &on_fresh_decoded_logo_,
387 LogoCallbackReason::CANCELED, nullptr, nullptr);
388 }
389
OnCachedLogoRead(std::unique_ptr<EncodedLogo> cached_logo)390 void LogoServiceImpl::OnCachedLogoRead(
391 std::unique_ptr<EncodedLogo> cached_logo) {
392 DCHECK(!is_idle_);
393
394 if (cached_logo && cached_logo->encoded_image) {
395 // Store the value of logo->encoded_image for use below. This ensures that
396 // logo->encoded_image is evaluated before base::Passed(&logo), which sets
397 // logo to NULL.
398 scoped_refptr<base::RefCountedString> encoded_image =
399 cached_logo->encoded_image;
400 image_decoder_->DecodeImage(
401 encoded_image->data(), gfx::Size(), // No particular size desired.
402 ImageDecodedHandlerWithTimeout::Wrap(base::BindRepeating(
403 &LogoServiceImpl::OnLightCachedImageDecoded,
404 weak_ptr_factory_.GetWeakPtr(), base::Passed(&cached_logo))));
405 } else if (cached_logo) {
406 OnCachedLogoAvailable(std::move(cached_logo), SkBitmap(), SkBitmap());
407 } else {
408 OnCachedLogoAvailable({}, SkBitmap(), SkBitmap());
409 }
410 }
411
SetCachedLogo(std::unique_ptr<EncodedLogo> logo)412 void LogoServiceImpl::SetCachedLogo(std::unique_ptr<EncodedLogo> logo) {
413 cache_task_runner_->PostTask(
414 FROM_HERE, base::BindOnce(&LogoCache::SetCachedLogo,
415 base::Unretained(logo_cache_.get()),
416 base::Owned(logo.release())));
417 }
418
SetCachedMetadata(const LogoMetadata & metadata)419 void LogoServiceImpl::SetCachedMetadata(const LogoMetadata& metadata) {
420 cache_task_runner_->PostTask(
421 FROM_HERE, base::BindOnce(&LogoCache::UpdateCachedLogoMetadata,
422 base::Unretained(logo_cache_.get()), metadata));
423 }
424
OnLightCachedImageDecoded(std::unique_ptr<EncodedLogo> cached_logo,const SkBitmap & image)425 void LogoServiceImpl::OnLightCachedImageDecoded(
426 std::unique_ptr<EncodedLogo> cached_logo,
427 const SkBitmap& image) {
428 if (cached_logo->metadata.dark_mime_type.empty()) {
429 OnCachedLogoAvailable(std::move(cached_logo), image, SkBitmap());
430 return;
431 }
432
433 // Store the value of logo->dark_encoded_image for use below. This ensures
434 // that logo->dark_encoded_image is evaluated before base::Passed(&logo),
435 // which sets logo to NULL.
436 scoped_refptr<base::RefCountedString> dark_encoded_image =
437 cached_logo->dark_encoded_image;
438
439 image_decoder_->DecodeImage(
440 dark_encoded_image->data(), gfx::Size(), // No particular size desired.
441 ImageDecodedHandlerWithTimeout::Wrap(base::BindRepeating(
442 &LogoServiceImpl::OnCachedLogoAvailable,
443 weak_ptr_factory_.GetWeakPtr(), base::Passed(&cached_logo), image)));
444 }
445
OnCachedLogoAvailable(std::unique_ptr<EncodedLogo> encoded_logo,const SkBitmap & image,const SkBitmap & dark_image)446 void LogoServiceImpl::OnCachedLogoAvailable(
447 std::unique_ptr<EncodedLogo> encoded_logo,
448 const SkBitmap& image,
449 const SkBitmap& dark_image) {
450 DCHECK(!is_idle_);
451
452 // A dark image is not required, but if one exists (mime type is non-empty)
453 // it must be successfully decoded.
454 if (encoded_logo && !image.isNull() &&
455 (encoded_logo->metadata.dark_mime_type.empty() || !dark_image.isNull())) {
456 cached_logo_ = std::make_unique<Logo>();
457 cached_logo_->metadata = encoded_logo->metadata;
458 cached_logo_->image = image;
459 cached_logo_->dark_image = dark_image;
460 cached_encoded_logo_ = std::move(encoded_logo);
461 }
462 is_cached_logo_valid_ = true;
463 NotifyAndClear(&on_cached_encoded_logo_, &on_cached_decoded_logo_,
464 LogoCallbackReason::DETERMINED, cached_encoded_logo_.get(),
465 cached_logo_.get());
466 FetchLogo();
467 }
468
FetchLogo()469 void LogoServiceImpl::FetchLogo() {
470 DCHECK(!loader_);
471 DCHECK(!is_idle_);
472
473 std::string fingerprint;
474 if (cached_logo_ && !cached_logo_->metadata.fingerprint.empty() &&
475 cached_logo_->metadata.expiration_time >= clock_->Now()) {
476 fingerprint = cached_logo_->metadata.fingerprint;
477 }
478 GURL url = append_queryparams_func_.Run(logo_url_, fingerprint);
479 net::NetworkTrafficAnnotationTag traffic_annotation =
480 net::DefineNetworkTrafficAnnotation("logo_service", R"(
481 semantics {
482 sender: "Logo Service"
483 description:
484 "Provides the logo image (aka Doodle) if Google is your configured "
485 "search provider."
486 trigger: "Displaying the new tab page on iOS or Android."
487 data:
488 "Logo ID, and the user's Google cookies to show for example "
489 "birthday doodles at appropriate times."
490 destination: OTHER
491 }
492 policy {
493 cookies_allowed: YES
494 cookies_store: "user"
495 setting:
496 "Choosing a non-Google search engine in Chromium settings under "
497 "'Search Engine' will disable this feature."
498 policy_exception_justification:
499 "Not implemented, considered not useful as it does not upload any"
500 "data and just downloads a logo image."
501 })");
502 auto request = std::make_unique<network::ResourceRequest>();
503 request->url = url;
504 loader_ =
505 network::SimpleURLLoader::Create(std::move(request), traffic_annotation);
506 loader_->DownloadToString(
507 url_loader_factory_.get(),
508 base::BindOnce(&LogoServiceImpl::OnURLLoadComplete,
509 base::Unretained(this), loader_.get()),
510 kMaxDownloadBytes);
511 logo_download_start_time_ = base::TimeTicks::Now();
512 }
513
514 void LogoServiceImpl::OnFreshLogoParsed(bool* parsing_failed,
515 bool from_http_cache,
516 std::unique_ptr<EncodedLogo> logo) {
517 DCHECK(!is_idle_);
518
519 if (logo)
520 logo->metadata.source_url = logo_url_;
521
522 if (!logo || !logo->encoded_image ||
523 (!logo->metadata.dark_mime_type.empty() && !logo->dark_encoded_image)) {
524 OnFreshLogoAvailable(std::move(logo), /*download_failed=*/false,
525 *parsing_failed, from_http_cache, SkBitmap(),
526 SkBitmap());
527 } else {
528 // Store the value of logo->encoded_image for use below. This ensures that
529 // logo->encoded_image is evaluated before base::Passed(&logo), which sets
530 // logo to NULL.
531 scoped_refptr<base::RefCountedString> encoded_image = logo->encoded_image;
532
533 image_decoder_->DecodeImage(
534 encoded_image->data(), gfx::Size(), // No particular size desired.
535 ImageDecodedHandlerWithTimeout::Wrap(base::BindRepeating(
536 &LogoServiceImpl::OnLightFreshImageDecoded,
537 weak_ptr_factory_.GetWeakPtr(), base::Passed(&logo),
538 /*download_failed=*/false, *parsing_failed, from_http_cache)));
539 }
540 }
541
542 void LogoServiceImpl::OnLightFreshImageDecoded(
543 std::unique_ptr<EncodedLogo> logo,
544 bool download_failed,
545 bool parsing_failed,
546 bool from_http_cache,
547 const SkBitmap& image) {
548 if (logo->metadata.dark_mime_type.empty()) {
549 OnFreshLogoAvailable(std::move(logo), download_failed, parsing_failed,
550 from_http_cache, image, SkBitmap());
551 return;
552 }
553
554 // Store the value of logo->dark_encoded_image for use below. This ensures
555 // that logo->encoded_image is evaluated before base::Passed(&logo), which
556 // sets logo to NULL.
557 scoped_refptr<base::RefCountedString> dark_encoded_image =
558 logo->dark_encoded_image;
559
560 image_decoder_->DecodeImage(
561 dark_encoded_image->data(), gfx::Size(), // No particular size desired.
562 ImageDecodedHandlerWithTimeout::Wrap(base::BindRepeating(
563 &LogoServiceImpl::OnFreshLogoAvailable,
564 weak_ptr_factory_.GetWeakPtr(), base::Passed(&logo), download_failed,
565 parsing_failed, from_http_cache, image)));
566 }
567
568 void LogoServiceImpl::OnFreshLogoAvailable(
569 std::unique_ptr<EncodedLogo> encoded_logo,
570 bool download_failed,
571 bool parsing_failed,
572 bool from_http_cache,
573 const SkBitmap& image,
574 const SkBitmap& dark_image) {
575 DCHECK(!is_idle_);
576
577 LogoDownloadOutcome download_outcome = DOWNLOAD_OUTCOME_COUNT;
578 std::unique_ptr<Logo> logo;
579
580 if (download_failed) {
581 download_outcome = DOWNLOAD_OUTCOME_DOWNLOAD_FAILED;
582 } else if (encoded_logo && !encoded_logo->encoded_image && cached_logo_ &&
583 !encoded_logo->metadata.fingerprint.empty() &&
584 encoded_logo->metadata.fingerprint ==
585 cached_logo_->metadata.fingerprint) {
586 // The cached logo was revalidated, i.e. its fingerprint was verified.
587 // mime_type isn't sent when revalidating, so copy it from the cached logo.
588 encoded_logo->metadata.mime_type = cached_logo_->metadata.mime_type;
589 encoded_logo->metadata.dark_mime_type =
590 cached_logo_->metadata.dark_mime_type;
591 SetCachedMetadata(encoded_logo->metadata);
592 download_outcome = DOWNLOAD_OUTCOME_LOGO_REVALIDATED;
593 } else if ((encoded_logo && encoded_logo->encoded_image && image.isNull()) ||
594 (encoded_logo && !encoded_logo->metadata.dark_mime_type.empty() &&
595 dark_image.isNull())) {
596 // Image decoding failed. Do nothing.
597 download_outcome = DOWNLOAD_OUTCOME_DECODING_FAILED;
598 } else if (encoded_logo && !encoded_logo->encoded_image &&
599 encoded_logo->metadata.type != LogoType::INTERACTIVE) {
600 download_outcome = DOWNLOAD_OUTCOME_MISSING_REQUIRED_IMAGE;
601 #if defined(OS_ANDROID) || defined(OS_IOS)
602 } else if (encoded_logo && !encoded_logo->encoded_image) {
603 // On Mobile interactive doodles require a static CTA image, on Desktop the
604 // static image is not required as it's handled by the iframed page.
605 download_outcome = DOWNLOAD_OUTCOME_MISSING_REQUIRED_IMAGE;
606 #endif
607 } else {
608 // Check if the server returned a valid, non-empty response.
609 if (encoded_logo) {
610 UMA_HISTOGRAM_BOOLEAN("NewTabPage.LogoImageDownloaded", from_http_cache);
611
612 DCHECK(!encoded_logo->encoded_image || !image.isNull());
613 logo.reset(new Logo());
614 logo->metadata = encoded_logo->metadata;
615 logo->image = image;
616 logo->dark_image = dark_image;
617 }
618
619 if (logo) {
620 download_outcome = DOWNLOAD_OUTCOME_NEW_LOGO_SUCCESS;
621 } else {
622 if (parsing_failed)
623 download_outcome = DOWNLOAD_OUTCOME_PARSING_FAILED;
624 else
625 download_outcome = DOWNLOAD_OUTCOME_NO_LOGO_TODAY;
626 }
627 }
628
629 LogoCallbackReason callback_type = LogoCallbackReason::FAILED;
630 switch (download_outcome) {
631 case DOWNLOAD_OUTCOME_NEW_LOGO_SUCCESS:
632 DCHECK(encoded_logo);
633 DCHECK(logo);
634 callback_type = LogoCallbackReason::DETERMINED;
635 break;
636
637 case DOWNLOAD_OUTCOME_PARSING_FAILED:
638 case DOWNLOAD_OUTCOME_NO_LOGO_TODAY:
639 // Clear the cached logo if it was non-null. Otherwise, report this as a
640 // revalidation of "no logo".
641 DCHECK(!encoded_logo);
642 DCHECK(!logo);
643 if (cached_logo_) {
644 callback_type = LogoCallbackReason::DETERMINED;
645 } else {
646 callback_type = LogoCallbackReason::REVALIDATED;
647 }
648 break;
649
650 case DOWNLOAD_OUTCOME_MISSING_REQUIRED_IMAGE:
651 case DOWNLOAD_OUTCOME_DOWNLOAD_FAILED:
652 // In the download failed, don't notify the callback at all, since the
653 // callback should continue to use the cached logo.
654 DCHECK(!encoded_logo || !encoded_logo->encoded_image);
655 DCHECK(!logo);
656 callback_type = LogoCallbackReason::FAILED;
657 break;
658
659 case DOWNLOAD_OUTCOME_DECODING_FAILED:
660 DCHECK(encoded_logo);
661 DCHECK(!logo);
662 encoded_logo.reset();
663 callback_type = LogoCallbackReason::FAILED;
664 break;
665
666 case DOWNLOAD_OUTCOME_LOGO_REVALIDATED:
667 // In the server reported that the cached logo is still current, don't
668 // notify the callback at all, since the callback should continue to use
669 // the cached logo.
670 DCHECK(encoded_logo);
671 DCHECK(!logo);
672 callback_type = LogoCallbackReason::REVALIDATED;
673 break;
674
675 case DOWNLOAD_OUTCOME_COUNT:
676 NOTREACHED();
677 return;
678 }
679
680 NotifyAndClear(&on_fresh_encoded_logo_, &on_fresh_decoded_logo_,
681 callback_type, encoded_logo.get(), logo.get());
682
683 switch (callback_type) {
684 case LogoCallbackReason::DETERMINED:
685 SetCachedLogo(std::move(encoded_logo));
686 break;
687
688 default:
689 break;
690 }
691
692 ReturnToIdle(download_outcome);
693 }
694
OnURLLoadComplete(const network::SimpleURLLoader * source,std::unique_ptr<std::string> body)695 void LogoServiceImpl::OnURLLoadComplete(const network::SimpleURLLoader* source,
696 std::unique_ptr<std::string> body) {
697 DCHECK(!is_idle_);
698 std::unique_ptr<network::SimpleURLLoader> cleanup_loader(loader_.release());
699
700 if (source->NetError() != net::OK) {
701 OnFreshLogoAvailable({}, /*download_failed=*/true, false, false, SkBitmap(),
702 SkBitmap());
703 return;
704 }
705
706 if (!source->ResponseInfo() || !source->ResponseInfo()->headers ||
707 source->ResponseInfo()->headers->response_code() != net::HTTP_OK) {
708 OnFreshLogoAvailable({}, /*download_failed=*/true, false, false, SkBitmap(),
709 SkBitmap());
710 return;
711 }
712
713 UMA_HISTOGRAM_TIMES("NewTabPage.LogoDownloadTime",
714 base::TimeTicks::Now() - logo_download_start_time_);
715
716 std::unique_ptr<std::string> response =
717 body ? std::move(body) : std::make_unique<std::string>();
718 base::Time response_time = clock_->Now();
719
720 bool from_http_cache = !source->ResponseInfo()->network_accessed;
721
722 bool* parsing_failed = new bool(false);
723 base::ThreadPool::PostTaskAndReplyWithResult(
724 FROM_HERE,
725 {base::MayBlock(), base::TaskPriority::USER_VISIBLE,
726 base::TaskShutdownBehavior::SKIP_ON_SHUTDOWN},
727 base::BindOnce(parse_logo_response_func_, std::move(response),
728 response_time, parsing_failed),
729 base::BindOnce(&LogoServiceImpl::OnFreshLogoParsed,
730 weak_ptr_factory_.GetWeakPtr(),
731 base::Owned(parsing_failed), from_http_cache));
732 }
733
OnAccountsInCookieUpdated(const signin::AccountsInCookieJarInfo &,const GoogleServiceAuthError &)734 void LogoServiceImpl::OnAccountsInCookieUpdated(
735 const signin::AccountsInCookieJarInfo&,
736 const GoogleServiceAuthError&) {
737 // Clear any cached logo, since it may be personalized (e.g. birthday Doodle).
738 if (!clock_) {
739 clock_ = base::DefaultClock::GetInstance();
740 }
741 ClearCachedLogo();
742 }
743
744 } // namespace search_provider_logos
745