blob: ae318469bbd8bf2d602a6e4e2da5b5ce3737ea5c [file] [log] [blame]
// 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 "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/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_tracker.h"
#include "components/search_provider_logos/switches.h"
#include "net/url_request/url_request_context_getter.h"
#include "ui/gfx/image/image.h"
using search_provider_logos::LogoDelegate;
using search_provider_logos::LogoTracker;
namespace search_provider_logos {
namespace {
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::Callback<void(const gfx::Image&)> Wrap(
const base::Callback<void(const SkBitmap&)>& image_decoded_callback) {
auto* handler = new ImageDecodedHandlerWithTimeout(image_decoded_callback);
base::ThreadTaskRunnerHandle::Get()->PostDelayedTask(
FROM_HERE,
base::Bind(&ImageDecodedHandlerWithTimeout::OnImageDecoded,
handler->weak_ptr_factory_.GetWeakPtr(), gfx::Image()),
base::TimeDelta::FromSeconds(kDecodeLogoTimeoutSeconds));
return base::Bind(&ImageDecodedHandlerWithTimeout::OnImageDecoded,
handler->weak_ptr_factory_.GetWeakPtr());
}
private:
explicit ImageDecodedHandlerWithTimeout(
const base::Callback<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::Callback<void(const SkBitmap&)> image_decoded_callback_;
base::WeakPtrFactory<ImageDecodedHandlerWithTimeout> weak_ptr_factory_;
DISALLOW_COPY_AND_ASSIGN(ImageDecodedHandlerWithTimeout);
};
class LogoDelegateImpl : public search_provider_logos::LogoDelegate {
public:
explicit LogoDelegateImpl(
std::unique_ptr<image_fetcher::ImageDecoder> image_decoder)
: image_decoder_(std::move(image_decoder)) {}
~LogoDelegateImpl() override = default;
// search_provider_logos::LogoDelegate:
void DecodeUntrustedImage(
const scoped_refptr<base::RefCountedString>& encoded_image,
base::Callback<void(const SkBitmap&)> image_decoded_callback) override {
image_decoder_->DecodeImage(
encoded_image->data(),
gfx::Size(), // No particular size desired.
ImageDecodedHandlerWithTimeout::Wrap(image_decoded_callback));
}
private:
const std::unique_ptr<image_fetcher::ImageDecoder> image_decoder_;
DISALLOW_COPY_AND_ASSIGN(LogoDelegateImpl);
};
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);
}
}
} // namespace
LogoServiceImpl::LogoServiceImpl(
const base::FilePath& cache_directory,
TemplateURLService* template_url_service,
std::unique_ptr<image_fetcher::ImageDecoder> image_decoder,
scoped_refptr<net::URLRequestContextGetter> request_context_getter,
base::RepeatingCallback<bool()> want_gray_logo_getter)
: LogoService(),
cache_directory_(cache_directory),
template_url_service_(template_url_service),
request_context_getter_(request_context_getter),
want_gray_logo_getter_(std::move(want_gray_logo_getter)),
image_decoder_(std::move(image_decoder)) {}
LogoServiceImpl::~LogoServiceImpl() = default;
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 (base::FeatureList::IsEnabled(features::kThirdPartyDoodles)) {
// First try to get the Doodle URL from the command line, then from a
// feature param, and finally the "real" one from TemplateURL.
if (command_line->HasSwitch(switches::kThirdPartyDoodleURL)) {
doodle_url = GURL(
command_line->GetSwitchValueASCII(switches::kThirdPartyDoodleURL));
} else {
std::string override_url = base::GetFieldTrialParamValueByFeature(
features::kThirdPartyDoodles,
features::kThirdPartyDoodlesOverrideUrlParam);
if (!override_url.empty()) {
doodle_url = GURL(override_url);
} 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;
}
const bool use_fixed_logo = !doodle_url.is_valid();
if (!logo_tracker_) {
std::unique_ptr<LogoCache> logo_cache = std::move(logo_cache_for_test_);
if (!logo_cache) {
logo_cache = std::make_unique<LogoCache>(cache_directory_);
}
std::unique_ptr<base::Clock> clock = std::move(clock_for_test_);
if (!clock) {
clock = std::make_unique<base::DefaultClock>();
}
logo_tracker_ = std::make_unique<LogoTracker>(
request_context_getter_,
std::make_unique<LogoDelegateImpl>(std::move(image_decoder_)),
std::move(logo_cache), std::move(clock));
}
if (use_fixed_logo) {
logo_tracker_->SetServerAPI(
logo_url, base::Bind(&search_provider_logos::ParseFixedLogoResponse),
base::Bind(&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);
logo_tracker_->SetServerAPI(
prefilled_url,
base::Bind(&search_provider_logos::ParseDoodleLogoResponse, base_url),
base::Bind(&search_provider_logos::AppendFingerprintParamToDoodleURL));
}
logo_tracker_->GetLogo(std::move(callbacks));
}
void LogoServiceImpl::SetLogoCacheForTests(std::unique_ptr<LogoCache> cache) {
logo_cache_for_test_ = std::move(cache);
}
void LogoServiceImpl::SetClockForTests(std::unique_ptr<base::Clock> clock) {
clock_for_test_ = std::move(clock);
}
} // namespace search_provider_logos