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