| // Copyright 2017 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. |
| |
| #include "components/search_provider_logos/logo_service_impl.h" |
| |
| #include <algorithm> |
| #include <utility> |
| |
| #include "base/bind.h" |
| #include "base/command_line.h" |
| #include "base/feature_list.h" |
| #include "base/macros.h" |
| #include "base/memory/weak_ptr.h" |
| #include "base/metrics/field_trial_params.h" |
| #include "base/metrics/histogram_macros.h" |
| #include "base/task/post_task.h" |
| #include "base/task_runner_util.h" |
| #include "base/threading/thread_task_runner_handle.h" |
| #include "base/time/default_clock.h" |
| #include "build/build_config.h" |
| #include "components/image_fetcher/core/image_decoder.h" |
| #include "components/search_engines/search_terms_data.h" |
| #include "components/search_engines/template_url_service.h" |
| #include "components/search_provider_logos/features.h" |
| #include "components/search_provider_logos/fixed_logo_api.h" |
| #include "components/search_provider_logos/google_logo_api.h" |
| #include "components/search_provider_logos/logo_cache.h" |
| #include "components/search_provider_logos/logo_observer.h" |
| #include "components/search_provider_logos/switches.h" |
| #include "net/http/http_status_code.h" |
| #include "net/traffic_annotation/network_traffic_annotation.h" |
| #include "services/network/public/cpp/shared_url_loader_factory.h" |
| #include "services/network/public/cpp/simple_url_loader.h" |
| #include "ui/gfx/image/image.h" |
| |
| namespace search_provider_logos { |
| namespace { |
| |
| const int64_t kMaxDownloadBytes = 1024 * 1024; |
| const int kDecodeLogoTimeoutSeconds = 30; |
| |
| // Implements a callback for image_fetcher::ImageDecoder. If Run() is called on |
| // a callback returned by GetCallback() within 30 seconds, forwards the decoded |
| // image to the wrapped callback. If not, sends an empty image to the wrapped |
| // callback instead. Either way, deletes the object and prevents further calls. |
| // |
| // TODO(sfiera): find a more idiomatic way of setting a deadline on the |
| // callback. This is implemented as a self-deleting object in part because it |
| // needed to when it used to be a delegate and in part because I couldn't figure |
| // out a better way, now that it isn't. |
| class ImageDecodedHandlerWithTimeout { |
| public: |
| static base::RepeatingCallback<void(const gfx::Image&)> Wrap( |
| const base::RepeatingCallback<void(const SkBitmap&)>& |
| image_decoded_callback) { |
| auto* handler = new ImageDecodedHandlerWithTimeout(image_decoded_callback); |
| base::ThreadTaskRunnerHandle::Get()->PostDelayedTask( |
| FROM_HERE, |
| base::BindOnce(&ImageDecodedHandlerWithTimeout::OnImageDecoded, |
| handler->weak_ptr_factory_.GetWeakPtr(), gfx::Image()), |
| base::TimeDelta::FromSeconds(kDecodeLogoTimeoutSeconds)); |
| return base::BindRepeating(&ImageDecodedHandlerWithTimeout::OnImageDecoded, |
| handler->weak_ptr_factory_.GetWeakPtr()); |
| } |
| |
| private: |
| explicit ImageDecodedHandlerWithTimeout( |
| const base::RepeatingCallback<void(const SkBitmap&)>& |
| image_decoded_callback) |
| : image_decoded_callback_(image_decoded_callback), |
| weak_ptr_factory_(this) {} |
| |
| void OnImageDecoded(const gfx::Image& decoded_image) { |
| image_decoded_callback_.Run(decoded_image.AsBitmap()); |
| delete this; |
| } |
| |
| base::RepeatingCallback<void(const SkBitmap&)> image_decoded_callback_; |
| base::WeakPtrFactory<ImageDecodedHandlerWithTimeout> weak_ptr_factory_; |
| |
| DISALLOW_COPY_AND_ASSIGN(ImageDecodedHandlerWithTimeout); |
| }; |
| |
| void ObserverOnLogoAvailable(LogoObserver* observer, |
| bool from_cache, |
| LogoCallbackReason type, |
| const base::Optional<Logo>& logo) { |
| switch (type) { |
| case LogoCallbackReason::DISABLED: |
| case LogoCallbackReason::CANCELED: |
| case LogoCallbackReason::FAILED: |
| break; |
| |
| case LogoCallbackReason::REVALIDATED: |
| // TODO(sfiera): double-check whether we should inform the observer of the |
| // fresh metadata. |
| break; |
| |
| case LogoCallbackReason::DETERMINED: |
| observer->OnLogoAvailable(logo ? &logo.value() : nullptr, from_cache); |
| break; |
| } |
| if (!from_cache) { |
| observer->OnObserverRemoved(); |
| } |
| } |
| |
| void RunCallbacksWithDisabled(LogoCallbacks callbacks) { |
| if (callbacks.on_cached_encoded_logo_available) { |
| std::move(callbacks.on_cached_encoded_logo_available) |
| .Run(LogoCallbackReason::DISABLED, base::nullopt); |
| } |
| if (callbacks.on_cached_decoded_logo_available) { |
| std::move(callbacks.on_cached_decoded_logo_available) |
| .Run(LogoCallbackReason::DISABLED, base::nullopt); |
| } |
| if (callbacks.on_fresh_encoded_logo_available) { |
| std::move(callbacks.on_fresh_encoded_logo_available) |
| .Run(LogoCallbackReason::DISABLED, base::nullopt); |
| } |
| if (callbacks.on_fresh_decoded_logo_available) { |
| std::move(callbacks.on_fresh_decoded_logo_available) |
| .Run(LogoCallbackReason::DISABLED, base::nullopt); |
| } |
| } |
| |
| // Returns whether the metadata for the cached logo indicates that the logo is |
| // OK to show, i.e. it's not expired or it's allowed to be shown temporarily |
| // after expiration. |
| bool IsLogoOkToShow(const LogoMetadata& metadata, base::Time now) { |
| base::TimeDelta offset = |
| base::TimeDelta::FromMilliseconds(kMaxTimeToLiveMS * 3 / 2); |
| base::Time distant_past = now - offset; |
| // Sanity check so logos aren't accidentally cached forever. |
| if (metadata.expiration_time < distant_past) { |
| return false; |
| } |
| return metadata.can_show_after_expiration || metadata.expiration_time >= now; |
| } |
| |
| // Reads the logo from the cache and returns it. Returns NULL if the cache is |
| // empty, corrupt, expired, or doesn't apply to the current logo URL. |
| std::unique_ptr<EncodedLogo> GetLogoFromCacheOnFileThread(LogoCache* logo_cache, |
| const GURL& logo_url, |
| base::Time now) { |
| const LogoMetadata* metadata = logo_cache->GetCachedLogoMetadata(); |
| if (!metadata) |
| return nullptr; |
| |
| if (metadata->source_url != logo_url || !IsLogoOkToShow(*metadata, now)) { |
| logo_cache->SetCachedLogo(nullptr); |
| return nullptr; |
| } |
| |
| return logo_cache->GetCachedLogo(); |
| } |
| |
| void NotifyAndClear(std::vector<EncodedLogoCallback>* encoded_callbacks, |
| std::vector<LogoCallback>* decoded_callbacks, |
| LogoCallbackReason type, |
| const EncodedLogo* encoded_logo, |
| const Logo* decoded_logo) { |
| auto opt_encoded_logo = |
| encoded_logo ? base::Optional<EncodedLogo>(*encoded_logo) : base::nullopt; |
| for (EncodedLogoCallback& callback : *encoded_callbacks) { |
| std::move(callback).Run(type, opt_encoded_logo); |
| } |
| encoded_callbacks->clear(); |
| |
| auto opt_decoded_logo = |
| decoded_logo ? base::Optional<Logo>(*decoded_logo) : base::nullopt; |
| for (LogoCallback& callback : *decoded_callbacks) { |
| std::move(callback).Run(type, opt_decoded_logo); |
| } |
| decoded_callbacks->clear(); |
| } |
| |
| } // namespace |
| |
| LogoServiceImpl::LogoServiceImpl( |
| const base::FilePath& cache_directory, |
| identity::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) |
| : cache_directory_(cache_directory), |
| identity_manager_(identity_manager), |
| template_url_service_(template_url_service), |
| url_loader_factory_(url_loader_factory), |
| want_gray_logo_getter_(std::move(want_gray_logo_getter)), |
| image_decoder_(std::move(image_decoder)), |
| is_idle_(true), |
| is_cached_logo_valid_(false), |
| cache_task_runner_(base::CreateSequencedTaskRunnerWithTraits( |
| {base::MayBlock(), base::TaskPriority::USER_VISIBLE, |
| base::TaskShutdownBehavior::SKIP_ON_SHUTDOWN})), |
| logo_cache_(new LogoCache(cache_directory_), |
| base::OnTaskRunnerDeleter(cache_task_runner_)), |
| weak_ptr_factory_(this) { |
| identity_manager_->AddObserver(this); |
| } |
| |
| LogoServiceImpl::~LogoServiceImpl() = default; |
| |
| void LogoServiceImpl::Shutdown() { |
| // The IdentityManager may be destroyed at any point after Shutdown, |
| // so make sure we drop any references to it. |
| identity_manager_->RemoveObserver(this); |
| ReturnToIdle(kDownloadOutcomeNotTracked); |
| } |
| |
| void LogoServiceImpl::GetLogo(search_provider_logos::LogoObserver* observer) { |
| LogoCallbacks callbacks; |
| callbacks.on_cached_decoded_logo_available = |
| base::BindOnce(ObserverOnLogoAvailable, observer, true); |
| callbacks.on_fresh_decoded_logo_available = |
| base::BindOnce(ObserverOnLogoAvailable, observer, false); |
| GetLogo(std::move(callbacks)); |
| } |
| |
| void LogoServiceImpl::GetLogo(LogoCallbacks callbacks) { |
| if (!template_url_service_) { |
| RunCallbacksWithDisabled(std::move(callbacks)); |
| return; |
| } |
| |
| const TemplateURL* template_url = |
| template_url_service_->GetDefaultSearchProvider(); |
| if (!template_url) { |
| RunCallbacksWithDisabled(std::move(callbacks)); |
| return; |
| } |
| |
| const base::CommandLine* command_line = |
| base::CommandLine::ForCurrentProcess(); |
| |
| GURL logo_url; |
| if (command_line->HasSwitch(switches::kSearchProviderLogoURL)) { |
| logo_url = GURL( |
| command_line->GetSwitchValueASCII(switches::kSearchProviderLogoURL)); |
| } else { |
| #if defined(OS_ANDROID) |
| // Non-Google default search engine logos are currently enabled only on |
| // Android (https://crbug.com/737283). |
| logo_url = template_url->logo_url(); |
| #endif |
| } |
| |
| GURL base_url; |
| GURL doodle_url; |
| const bool is_google = template_url->url_ref().HasGoogleBaseURLs( |
| template_url_service_->search_terms_data()); |
| if (is_google) { |
| // TODO(treib): Put the Google doodle URL into prepopulated_engines.json. |
| base_url = |
| GURL(template_url_service_->search_terms_data().GoogleBaseURLValue()); |
| doodle_url = search_provider_logos::GetGoogleDoodleURL(base_url); |
| } else { |
| if (command_line->HasSwitch(switches::kThirdPartyDoodleURL)) { |
| doodle_url = GURL( |
| command_line->GetSwitchValueASCII(switches::kThirdPartyDoodleURL)); |
| } else { |
| doodle_url = template_url->doodle_url(); |
| } |
| base_url = doodle_url.GetOrigin(); |
| } |
| |
| if (!logo_url.is_valid() && !doodle_url.is_valid()) { |
| RunCallbacksWithDisabled(std::move(callbacks)); |
| return; |
| } |
| |
| if (!clock_) { |
| clock_ = base::DefaultClock::GetInstance(); |
| } |
| |
| const bool use_fixed_logo = !doodle_url.is_valid(); |
| if (use_fixed_logo) { |
| SetServerAPI( |
| logo_url, |
| base::BindRepeating(&search_provider_logos::ParseFixedLogoResponse), |
| base::BindRepeating(&search_provider_logos::UseFixedLogoUrl)); |
| } else { |
| // We encode the type of doodle (regular or gray) in the URL so that the |
| // logo cache gets cleared when that value changes. |
| GURL prefilled_url = AppendPreliminaryParamsToDoodleURL( |
| want_gray_logo_getter_.Run(), doodle_url); |
| SetServerAPI( |
| prefilled_url, |
| base::Bind(&search_provider_logos::ParseDoodleLogoResponse, base_url), |
| base::Bind(&search_provider_logos::AppendFingerprintParamToDoodleURL)); |
| } |
| |
| DCHECK(!logo_url_.is_empty()); |
| DCHECK(callbacks.on_cached_decoded_logo_available || |
| callbacks.on_cached_encoded_logo_available || |
| callbacks.on_fresh_decoded_logo_available || |
| callbacks.on_fresh_encoded_logo_available); |
| |
| if (callbacks.on_cached_encoded_logo_available) { |
| on_cached_encoded_logo_.push_back( |
| std::move(callbacks.on_cached_encoded_logo_available)); |
| } |
| if (callbacks.on_cached_decoded_logo_available) { |
| on_cached_decoded_logo_.push_back( |
| std::move(callbacks.on_cached_decoded_logo_available)); |
| } |
| if (callbacks.on_fresh_encoded_logo_available) { |
| on_fresh_encoded_logo_.push_back( |
| std::move(callbacks.on_fresh_encoded_logo_available)); |
| } |
| if (callbacks.on_fresh_decoded_logo_available) { |
| on_fresh_decoded_logo_.push_back( |
| std::move(callbacks.on_fresh_decoded_logo_available)); |
| } |
| |
| if (is_idle_) { |
| is_idle_ = false; |
| |
| base::PostTaskAndReplyWithResult( |
| cache_task_runner_.get(), FROM_HERE, |
| base::BindRepeating(&GetLogoFromCacheOnFileThread, |
| base::Unretained(logo_cache_.get()), logo_url_, |
| clock_->Now()), |
| base::BindRepeating(&LogoServiceImpl::OnCachedLogoRead, |
| weak_ptr_factory_.GetWeakPtr())); |
| } else if (is_cached_logo_valid_) { |
| NotifyAndClear(&on_cached_encoded_logo_, &on_cached_decoded_logo_, |
| LogoCallbackReason::DETERMINED, cached_encoded_logo_.get(), |
| cached_logo_.get()); |
| } |
| } |
| |
| void LogoServiceImpl::SetLogoCacheForTests(std::unique_ptr<LogoCache> cache) { |
| // |logo_cache_| has a custom deleter, which makes the two unique_ptrs |
| // be different types. so one can't be moved on top of the other. |
| logo_cache_.reset(std::move(cache).release()); |
| } |
| |
| void LogoServiceImpl::SetClockForTests(base::Clock* clock) { |
| clock_ = clock; |
| } |
| |
| void LogoServiceImpl::SetServerAPI( |
| const GURL& logo_url, |
| const ParseLogoResponse& parse_logo_response_func, |
| const AppendQueryparamsToLogoURL& append_queryparams_func) { |
| if (logo_url == logo_url_) |
| return; |
| |
| ReturnToIdle(kDownloadOutcomeNotTracked); |
| |
| logo_url_ = logo_url; |
| parse_logo_response_func_ = parse_logo_response_func; |
| append_queryparams_func_ = append_queryparams_func; |
| } |
| |
| void LogoServiceImpl::ClearCachedLogo() { |
| // First cancel any fetch that might be ongoing. |
| ReturnToIdle(kDownloadOutcomeNotTracked); |
| // Then clear any cached logo. |
| SetCachedLogo(nullptr); |
| } |
| |
| void LogoServiceImpl::ReturnToIdle(int outcome) { |
| if (outcome != kDownloadOutcomeNotTracked) { |
| UMA_HISTOGRAM_ENUMERATION("NewTabPage.LogoDownloadOutcome", |
| static_cast<LogoDownloadOutcome>(outcome), |
| DOWNLOAD_OUTCOME_COUNT); |
| } |
| |
| // Cancel the current asynchronous operation, if any. |
| loader_.reset(); |
| weak_ptr_factory_.InvalidateWeakPtrs(); |
| |
| // Reset state. |
| is_idle_ = true; |
| cached_logo_.reset(); |
| cached_encoded_logo_.reset(); |
| is_cached_logo_valid_ = false; |
| |
| // Clear callbacks. |
| NotifyAndClear(&on_cached_encoded_logo_, &on_cached_decoded_logo_, |
| LogoCallbackReason::CANCELED, nullptr, nullptr); |
| NotifyAndClear(&on_fresh_encoded_logo_, &on_fresh_decoded_logo_, |
| LogoCallbackReason::CANCELED, nullptr, nullptr); |
| } |
| |
| void LogoServiceImpl::OnCachedLogoRead( |
| std::unique_ptr<EncodedLogo> cached_logo) { |
| DCHECK(!is_idle_); |
| |
| if (cached_logo && cached_logo->encoded_image) { |
| // Store the value of logo->encoded_image for use below. This ensures that |
| // logo->encoded_image is evaulated before base::Passed(&logo), which sets |
| // logo to NULL. |
| scoped_refptr<base::RefCountedString> encoded_image = |
| cached_logo->encoded_image; |
| image_decoder_->DecodeImage( |
| encoded_image->data(), gfx::Size(), // No particular size desired. |
| ImageDecodedHandlerWithTimeout::Wrap(base::BindRepeating( |
| &LogoServiceImpl::OnCachedLogoAvailable, |
| weak_ptr_factory_.GetWeakPtr(), base::Passed(&cached_logo)))); |
| } else if (cached_logo) { |
| OnCachedLogoAvailable(std::move(cached_logo), SkBitmap()); |
| } else { |
| OnCachedLogoAvailable({}, SkBitmap()); |
| } |
| } |
| |
| void LogoServiceImpl::SetCachedLogo(std::unique_ptr<EncodedLogo> logo) { |
| cache_task_runner_->PostTask( |
| FROM_HERE, base::BindOnce(&LogoCache::SetCachedLogo, |
| base::Unretained(logo_cache_.get()), |
| base::Owned(logo.release()))); |
| } |
| |
| void LogoServiceImpl::SetCachedMetadata(const LogoMetadata& metadata) { |
| cache_task_runner_->PostTask( |
| FROM_HERE, base::BindOnce(&LogoCache::UpdateCachedLogoMetadata, |
| base::Unretained(logo_cache_.get()), metadata)); |
| } |
| |
| void LogoServiceImpl::OnCachedLogoAvailable( |
| std::unique_ptr<EncodedLogo> encoded_logo, |
| const SkBitmap& image) { |
| DCHECK(!is_idle_); |
| |
| if (encoded_logo && encoded_logo->encoded_image && !image.isNull()) { |
| cached_logo_.reset(new Logo()); |
| cached_logo_->metadata = encoded_logo->metadata; |
| cached_logo_->image = image; |
| cached_encoded_logo_ = std::move(encoded_logo); |
| } |
| is_cached_logo_valid_ = true; |
| NotifyAndClear(&on_cached_encoded_logo_, &on_cached_decoded_logo_, |
| LogoCallbackReason::DETERMINED, cached_encoded_logo_.get(), |
| cached_logo_.get()); |
| FetchLogo(); |
| } |
| |
| void LogoServiceImpl::FetchLogo() { |
| DCHECK(!loader_); |
| DCHECK(!is_idle_); |
| |
| std::string fingerprint; |
| if (cached_logo_ && !cached_logo_->metadata.fingerprint.empty() && |
| cached_logo_->metadata.expiration_time >= clock_->Now()) { |
| fingerprint = cached_logo_->metadata.fingerprint; |
| } |
| GURL url = append_queryparams_func_.Run(logo_url_, fingerprint); |
| net::NetworkTrafficAnnotationTag traffic_annotation = |
| net::DefineNetworkTrafficAnnotation("logo_service", R"( |
| semantics { |
| sender: "Logo Service" |
| description: |
| "Provides the logo image (aka Doodle) if Google is your configured " |
| "search provider." |
| trigger: "Displaying the new tab page on iOS or Android." |
| data: |
| "Logo ID, and the user's Google cookies to show for example " |
| "birthday doodles at appropriate times." |
| destination: OTHER |
| } |
| policy { |
| cookies_allowed: YES |
| cookies_store: "user" |
| setting: |
| "Choosing a non-Google search engine in Chromium settings under " |
| "'Search Engine' will disable this feature." |
| policy_exception_justification: |
| "Not implemented, considered not useful as it does not upload any" |
| "data and just downloads a logo image." |
| })"); |
| auto request = std::make_unique<network::ResourceRequest>(); |
| request->url = url; |
| // TODO(https://crbug.com/808498) re-add data use measurement once |
| // SimpleURLLoader supports it: |
| // data_use_measurement::DataUseUserData::SEARCH_PROVIDER_LOGOS |
| loader_ = |
| network::SimpleURLLoader::Create(std::move(request), traffic_annotation); |
| loader_->DownloadToString( |
| url_loader_factory_.get(), |
| base::BindOnce(&LogoServiceImpl::OnURLLoadComplete, |
| base::Unretained(this), loader_.get()), |
| kMaxDownloadBytes); |
| logo_download_start_time_ = base::TimeTicks::Now(); |
| } |
| |
| void LogoServiceImpl::OnFreshLogoParsed(bool* parsing_failed, |
| bool from_http_cache, |
| std::unique_ptr<EncodedLogo> logo) { |
| DCHECK(!is_idle_); |
| |
| if (logo) |
| logo->metadata.source_url = logo_url_; |
| |
| if (!logo || !logo->encoded_image) { |
| OnFreshLogoAvailable(std::move(logo), /*download_failed=*/false, |
| *parsing_failed, from_http_cache, SkBitmap()); |
| } else { |
| // Store the value of logo->encoded_image for use below. This ensures that |
| // logo->encoded_image is evaulated before base::Passed(&logo), which sets |
| // logo to NULL. |
| scoped_refptr<base::RefCountedString> encoded_image = logo->encoded_image; |
| image_decoder_->DecodeImage( |
| encoded_image->data(), gfx::Size(), // No particular size desired. |
| ImageDecodedHandlerWithTimeout::Wrap(base::BindRepeating( |
| &LogoServiceImpl::OnFreshLogoAvailable, |
| weak_ptr_factory_.GetWeakPtr(), base::Passed(&logo), |
| /*download_failed=*/false, *parsing_failed, from_http_cache))); |
| } |
| } |
| |
| void LogoServiceImpl::OnFreshLogoAvailable( |
| std::unique_ptr<EncodedLogo> encoded_logo, |
| bool download_failed, |
| bool parsing_failed, |
| bool from_http_cache, |
| const SkBitmap& image) { |
| DCHECK(!is_idle_); |
| |
| LogoDownloadOutcome download_outcome = DOWNLOAD_OUTCOME_COUNT; |
| std::unique_ptr<Logo> logo; |
| |
| if (download_failed) { |
| download_outcome = DOWNLOAD_OUTCOME_DOWNLOAD_FAILED; |
| } else if (encoded_logo && !encoded_logo->encoded_image && cached_logo_ && |
| !encoded_logo->metadata.fingerprint.empty() && |
| encoded_logo->metadata.fingerprint == |
| cached_logo_->metadata.fingerprint) { |
| // The cached logo was revalidated, i.e. its fingerprint was verified. |
| // mime_type isn't sent when revalidating, so copy it from the cached logo. |
| encoded_logo->metadata.mime_type = cached_logo_->metadata.mime_type; |
| SetCachedMetadata(encoded_logo->metadata); |
| download_outcome = DOWNLOAD_OUTCOME_LOGO_REVALIDATED; |
| } else if (encoded_logo && encoded_logo->encoded_image && image.isNull()) { |
| // Image decoding failed. Do nothing. |
| download_outcome = DOWNLOAD_OUTCOME_DECODING_FAILED; |
| } else if (encoded_logo && !encoded_logo->encoded_image && |
| encoded_logo->metadata.type != LogoType::INTERACTIVE) { |
| download_outcome = DOWNLOAD_OUTCOME_MISSING_REQUIRED_IMAGE; |
| #if defined(OS_ANDROID) || defined(OS_IOS) |
| } else if (encoded_logo && !encoded_logo->encoded_image) { |
| // On Mobile interactive doodles require a static CTA image, on Desktop the |
| // static image is not required as it's handled by the iframed page. |
| download_outcome = DOWNLOAD_OUTCOME_MISSING_REQUIRED_IMAGE; |
| #endif |
| } else { |
| // Check if the server returned a valid, non-empty response. |
| if (encoded_logo) { |
| UMA_HISTOGRAM_BOOLEAN("NewTabPage.LogoImageDownloaded", from_http_cache); |
| |
| DCHECK(!encoded_logo->encoded_image || !image.isNull()); |
| logo.reset(new Logo()); |
| logo->metadata = encoded_logo->metadata; |
| logo->image = image; |
| } |
| |
| if (logo) { |
| download_outcome = DOWNLOAD_OUTCOME_NEW_LOGO_SUCCESS; |
| } else { |
| if (parsing_failed) |
| download_outcome = DOWNLOAD_OUTCOME_PARSING_FAILED; |
| else |
| download_outcome = DOWNLOAD_OUTCOME_NO_LOGO_TODAY; |
| } |
| } |
| |
| LogoCallbackReason callback_type = LogoCallbackReason::FAILED; |
| switch (download_outcome) { |
| case DOWNLOAD_OUTCOME_NEW_LOGO_SUCCESS: |
| DCHECK(encoded_logo); |
| DCHECK(logo); |
| callback_type = LogoCallbackReason::DETERMINED; |
| break; |
| |
| case DOWNLOAD_OUTCOME_PARSING_FAILED: |
| case DOWNLOAD_OUTCOME_NO_LOGO_TODAY: |
| // Clear the cached logo if it was non-null. Otherwise, report this as a |
| // revalidation of "no logo". |
| DCHECK(!encoded_logo); |
| DCHECK(!logo); |
| if (cached_logo_) { |
| callback_type = LogoCallbackReason::DETERMINED; |
| } else { |
| callback_type = LogoCallbackReason::REVALIDATED; |
| } |
| break; |
| |
| case DOWNLOAD_OUTCOME_MISSING_REQUIRED_IMAGE: |
| case DOWNLOAD_OUTCOME_DOWNLOAD_FAILED: |
| // In the download failed, don't notify the callback at all, since the |
| // callback should continue to use the cached logo. |
| DCHECK(!encoded_logo || !encoded_logo->encoded_image); |
| DCHECK(!logo); |
| callback_type = LogoCallbackReason::FAILED; |
| break; |
| |
| case DOWNLOAD_OUTCOME_DECODING_FAILED: |
| DCHECK(encoded_logo); |
| DCHECK(!logo); |
| encoded_logo.reset(); |
| callback_type = LogoCallbackReason::FAILED; |
| break; |
| |
| case DOWNLOAD_OUTCOME_LOGO_REVALIDATED: |
| // In the server reported that the cached logo is still current, don't |
| // notify the callback at all, since the callback should continue to use |
| // the cached logo. |
| DCHECK(encoded_logo); |
| DCHECK(!logo); |
| callback_type = LogoCallbackReason::REVALIDATED; |
| break; |
| |
| case DOWNLOAD_OUTCOME_COUNT: |
| NOTREACHED(); |
| return; |
| } |
| |
| NotifyAndClear(&on_fresh_encoded_logo_, &on_fresh_decoded_logo_, |
| callback_type, encoded_logo.get(), logo.get()); |
| |
| switch (callback_type) { |
| case LogoCallbackReason::DETERMINED: |
| SetCachedLogo(std::move(encoded_logo)); |
| break; |
| |
| default: |
| break; |
| } |
| |
| ReturnToIdle(download_outcome); |
| } |
| |
| void LogoServiceImpl::OnURLLoadComplete(const network::SimpleURLLoader* source, |
| std::unique_ptr<std::string> body) { |
| DCHECK(!is_idle_); |
| std::unique_ptr<network::SimpleURLLoader> cleanup_loader(loader_.release()); |
| |
| if (source->NetError() != net::OK) { |
| OnFreshLogoAvailable({}, /*download_failed=*/true, false, false, |
| SkBitmap()); |
| return; |
| } |
| |
| if (!source->ResponseInfo() || !source->ResponseInfo()->headers || |
| source->ResponseInfo()->headers->response_code() != net::HTTP_OK) { |
| OnFreshLogoAvailable({}, /*download_failed=*/true, false, false, |
| SkBitmap()); |
| return; |
| } |
| |
| UMA_HISTOGRAM_TIMES("NewTabPage.LogoDownloadTime", |
| base::TimeTicks::Now() - logo_download_start_time_); |
| |
| std::unique_ptr<std::string> response = |
| body ? std::move(body) : std::make_unique<std::string>(); |
| base::Time response_time = clock_->Now(); |
| |
| bool from_http_cache = !source->ResponseInfo()->network_accessed; |
| |
| bool* parsing_failed = new bool(false); |
| base::PostTaskWithTraitsAndReplyWithResult( |
| FROM_HERE, |
| {base::MayBlock(), base::TaskPriority::USER_VISIBLE, |
| base::TaskShutdownBehavior::SKIP_ON_SHUTDOWN}, |
| base::BindOnce(parse_logo_response_func_, std::move(response), |
| response_time, parsing_failed), |
| base::BindOnce(&LogoServiceImpl::OnFreshLogoParsed, |
| weak_ptr_factory_.GetWeakPtr(), |
| base::Owned(parsing_failed), from_http_cache)); |
| } |
| |
| void LogoServiceImpl::OnAccountsInCookieUpdated( |
| const identity::AccountsInCookieJarInfo&, |
| const GoogleServiceAuthError&) { |
| // Clear any cached logo, since it may be personalized (e.g. birthday Doodle). |
| if (!clock_) { |
| clock_ = base::DefaultClock::GetInstance(); |
| } |
| ClearCachedLogo(); |
| } |
| |
| } // namespace search_provider_logos |