blob: 204a6c6393354b09629cb21c065eb2d492c46e23 [file] [log] [blame]
// Copyright (c) 2012 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.
//
// This code glues the RLZ library DLL with Chrome. It allows Chrome to work
// with or without the DLL being present. If the DLL is not present the
// functions do nothing and just return false.
#include "components/rlz/rlz_tracker.h"
#include <algorithm>
#include <utility>
#include "base/bind.h"
#include "base/sequenced_task_runner.h"
#include "base/strings/string_util.h"
#include "base/strings/stringprintf.h"
#include "base/strings/utf_string_conversions.h"
#include "base/task/post_task.h"
#include "base/task/task_traits.h"
#include "base/trace_event/trace_event.h"
#include "build/build_config.h"
#include "components/rlz/rlz_tracker_delegate.h"
#include "services/network/public/cpp/shared_url_loader_factory.h"
#if defined(OS_CHROMEOS)
#include "base/syslog_logging.h"
#endif
namespace rlz {
namespace {
// Maximum and minimum delay for financial ping we would allow to be set through
// master preferences. Somewhat arbitrary, may need to be adjusted in future.
#if defined(OS_CHROMEOS)
const base::TimeDelta kMinInitDelay = base::TimeDelta::FromSeconds(60);
const base::TimeDelta kMaxInitDelay = base::TimeDelta::FromHours(24);
#else
const base::TimeDelta kMinInitDelay = base::TimeDelta::FromSeconds(20);
const base::TimeDelta kMaxInitDelay = base::TimeDelta::FromSeconds(200);
#endif
void RecordProductEvents(bool first_run,
bool is_google_default_search,
bool is_google_homepage,
bool is_google_in_startpages,
bool already_ran,
bool omnibox_used,
bool homepage_used,
bool app_list_used) {
TRACE_EVENT0("RLZ", "RecordProductEvents");
// Record the installation of chrome. We call this all the time but the rlz
// lib should ignore all but the first one.
rlz_lib::RecordProductEvent(rlz_lib::CHROME,
RLZTracker::ChromeOmnibox(),
rlz_lib::INSTALL);
#if !defined(OS_IOS)
rlz_lib::RecordProductEvent(rlz_lib::CHROME,
RLZTracker::ChromeHomePage(),
rlz_lib::INSTALL);
rlz_lib::RecordProductEvent(rlz_lib::CHROME,
RLZTracker::ChromeAppList(),
rlz_lib::INSTALL);
#endif // !defined(OS_IOS)
if (!already_ran) {
// Do the initial event recording if is the first run or if we have an
// empty rlz which means we haven't got a chance to do it.
char omnibox_rlz[rlz_lib::kMaxRlzLength + 1];
if (!rlz_lib::GetAccessPointRlz(RLZTracker::ChromeOmnibox(), omnibox_rlz,
rlz_lib::kMaxRlzLength)) {
omnibox_rlz[0] = 0;
}
// Record if google is the initial search provider and/or home page.
if ((first_run || omnibox_rlz[0] == 0) && is_google_default_search) {
rlz_lib::RecordProductEvent(rlz_lib::CHROME,
RLZTracker::ChromeOmnibox(),
rlz_lib::SET_TO_GOOGLE);
}
#if !defined(OS_IOS)
char homepage_rlz[rlz_lib::kMaxRlzLength + 1];
if (!rlz_lib::GetAccessPointRlz(RLZTracker::ChromeHomePage(), homepage_rlz,
rlz_lib::kMaxRlzLength)) {
homepage_rlz[0] = 0;
}
if ((first_run || homepage_rlz[0] == 0) &&
(is_google_homepage || is_google_in_startpages)) {
rlz_lib::RecordProductEvent(rlz_lib::CHROME,
RLZTracker::ChromeHomePage(),
rlz_lib::SET_TO_GOOGLE);
}
char app_list_rlz[rlz_lib::kMaxRlzLength + 1];
if (!rlz_lib::GetAccessPointRlz(RLZTracker::ChromeAppList(), app_list_rlz,
rlz_lib::kMaxRlzLength)) {
app_list_rlz[0] = 0;
}
// Record if google is the initial search provider and/or home page.
if ((first_run || app_list_rlz[0] == 0) && is_google_default_search) {
rlz_lib::RecordProductEvent(rlz_lib::CHROME,
RLZTracker::ChromeAppList(),
rlz_lib::SET_TO_GOOGLE);
}
#endif // !defined(OS_IOS)
}
// Record first user interaction with the omnibox. We call this all the
// time but the rlz lib should ingore all but the first one.
if (omnibox_used) {
rlz_lib::RecordProductEvent(rlz_lib::CHROME,
RLZTracker::ChromeOmnibox(),
rlz_lib::FIRST_SEARCH);
}
#if !defined(OS_IOS)
// Record first user interaction with the home page. We call this all the
// time but the rlz lib should ingore all but the first one.
if (homepage_used || is_google_in_startpages) {
rlz_lib::RecordProductEvent(rlz_lib::CHROME,
RLZTracker::ChromeHomePage(),
rlz_lib::FIRST_SEARCH);
}
// Record first user interaction with the app list. We call this all the
// time but the rlz lib should ingore all but the first one.
if (app_list_used) {
rlz_lib::RecordProductEvent(rlz_lib::CHROME,
RLZTracker::ChromeAppList(),
rlz_lib::FIRST_SEARCH);
}
#endif // !defined(OS_IOS)
}
bool SendFinancialPing(const std::string& brand,
const base::string16& lang,
const base::string16& referral) {
rlz_lib::AccessPoint points[] = {RLZTracker::ChromeOmnibox(),
#if !defined(OS_IOS)
RLZTracker::ChromeHomePage(),
RLZTracker::ChromeAppList(),
#endif
rlz_lib::NO_ACCESS_POINT};
std::string lang_ascii(base::UTF16ToASCII(lang));
std::string referral_ascii(base::UTF16ToASCII(referral));
std::string product_signature;
#if defined(OS_CHROMEOS)
product_signature = "chromeos";
#else
product_signature = "chrome";
#endif
return rlz_lib::SendFinancialPing(
rlz_lib::CHROME, points, product_signature.c_str(), brand.c_str(),
referral_ascii.c_str(), lang_ascii.c_str(), false, true);
}
} // namespace
RLZTracker* RLZTracker::tracker_ = nullptr;
// WrapperURLLoaderFactory subclasses mojom::URLLoaderFactory as non-mojo, cross
// thread class. It basically posts ::CreateLoaderAndStart calls over to the UI
// thread, to call them on the real mojo object.
class RLZTracker::WrapperURLLoaderFactory
: public network::mojom::URLLoaderFactory {
public:
explicit WrapperURLLoaderFactory(
scoped_refptr<network::SharedURLLoaderFactory> url_loader_factory)
: url_loader_factory_(std::move(url_loader_factory)),
main_thread_task_runner_(base::SequencedTaskRunnerHandle::Get()) {}
void CreateLoaderAndStart(network::mojom::URLLoaderRequest loader,
int32_t routing_id,
int32_t request_id,
uint32_t options,
const network::ResourceRequest& request,
network::mojom::URLLoaderClientPtr client,
const net::MutableNetworkTrafficAnnotationTag&
traffic_annotation) override {
if (main_thread_task_runner_->RunsTasksInCurrentSequence()) {
url_loader_factory_->CreateLoaderAndStart(
std::move(loader), routing_id, request_id, options, request,
std::move(client), traffic_annotation);
} else {
main_thread_task_runner_->PostTask(
FROM_HERE,
base::BindOnce(&WrapperURLLoaderFactory::CreateLoaderAndStart,
base::Unretained(this), std::move(loader), routing_id,
request_id, options, request, std::move(client),
traffic_annotation));
}
}
void Clone(network::mojom::URLLoaderFactoryRequest factory) override {
NOTIMPLEMENTED();
}
private:
scoped_refptr<network::SharedURLLoaderFactory> url_loader_factory_;
// Runner for RLZ main thread tasks.
scoped_refptr<base::SequencedTaskRunner> main_thread_task_runner_;
DISALLOW_COPY_AND_ASSIGN(WrapperURLLoaderFactory);
};
// static
RLZTracker* RLZTracker::GetInstance() {
return tracker_ ? tracker_ : base::Singleton<RLZTracker>::get();
}
RLZTracker::RLZTracker()
: first_run_(false),
send_ping_immediately_(false),
is_google_default_search_(false),
is_google_homepage_(false),
is_google_in_startpages_(false),
already_ran_(false),
omnibox_used_(false),
homepage_used_(false),
app_list_used_(false),
min_init_delay_(kMinInitDelay),
background_task_runner_(base::CreateSequencedTaskRunnerWithTraits(
{base::TaskShutdownBehavior::SKIP_ON_SHUTDOWN, base::MayBlock(),
base::TaskPriority::BEST_EFFORT})) {
DETACH_FROM_SEQUENCE(sequence_checker_);
}
RLZTracker::~RLZTracker() {
}
// static
void RLZTracker::SetRlzDelegate(std::unique_ptr<RLZTrackerDelegate> delegate) {
RLZTracker* tracker = GetInstance();
if (!tracker->delegate_) {
// RLZTracker::SetRlzDelegate is called at Profile creation time which can
// happens multiple time on ChromeOS, so do nothing if the delegate already
// exists.
tracker->SetDelegate(std::move(delegate));
}
}
void RLZTracker::SetDelegate(std::unique_ptr<RLZTrackerDelegate> delegate) {
DCHECK(delegate);
DCHECK(!delegate_);
delegate_ = std::move(delegate);
}
// static
bool RLZTracker::InitRlzDelayed(bool first_run,
bool send_ping_immediately,
base::TimeDelta delay,
bool is_google_default_search,
bool is_google_homepage,
bool is_google_in_startpages) {
return GetInstance()->Init(first_run, send_ping_immediately, delay,
is_google_default_search, is_google_homepage,
is_google_in_startpages);
}
bool RLZTracker::Init(bool first_run,
bool send_ping_immediately,
base::TimeDelta delay,
bool is_google_default_search,
bool is_google_homepage,
bool is_google_in_startpages) {
DCHECK(delegate_) << "RLZTracker used before initialization";
first_run_ = first_run;
is_google_default_search_ = is_google_default_search;
is_google_homepage_ = is_google_homepage;
is_google_in_startpages_ = is_google_in_startpages;
send_ping_immediately_ = send_ping_immediately;
// Enable zero delays for testing.
if (delegate_->ShouldEnableZeroDelayForTesting())
EnableZeroDelayForTesting();
delay = std::min(kMaxInitDelay, std::max(min_init_delay_, delay));
if (delegate_->GetBrand(&brand_) && !delegate_->IsBrandOrganic(brand_)) {
// Register for notifications from the omnibox so that we can record when
// the user performs a first search.
delegate_->SetOmniboxSearchCallback(
base::Bind(&RLZTracker::RecordFirstSearch, base::Unretained(this),
ChromeOmnibox()));
#if !defined(OS_IOS)
// Register for notifications from navigations, to see if the user has used
// the home page.
delegate_->SetHomepageSearchCallback(
base::Bind(&RLZTracker::RecordFirstSearch, base::Unretained(this),
ChromeHomePage()));
#endif
}
delegate_->GetReactivationBrand(&reactivation_brand_);
#if defined(OS_CHROMEOS)
// If the brand is organic, RLZ is essentially disabled. Write a log to the
// console for administrators and QA.
if (delegate_->IsBrandOrganic(brand_) &&
delegate_->IsBrandOrganic(reactivation_brand_)) {
SYSLOG(INFO) << "RLZ is disabled";
} else if (delegate_->ShouldUpdateExistingAccessPointRlz()) {
background_task_runner_->PostTask(
FROM_HERE, base::BindOnce(
[](const std::string& brand) {
rlz_lib::UpdateExistingAccessPointRlz(brand);
},
brand_));
}
#endif
// Could be null; don't run if so. RLZ will try again next restart.
auto shared_url_loader_factory = delegate_->GetURLLoaderFactory();
if (shared_url_loader_factory) {
custom_url_loader_factory_ =
std::make_unique<WrapperURLLoaderFactory>(shared_url_loader_factory);
rlz_lib::SetURLLoaderFactory(custom_url_loader_factory_.get());
ScheduleDelayedInit(delay);
}
#if !defined(OS_IOS)
// Prime the RLZ cache for the home page access point so that its avaiable
// for the startup page if needed (i.e., when the startup page is set to
// the home page).
GetAccessPointRlz(ChromeHomePage(), nullptr);
#endif // !defined(OS_IOS)
return true;
}
void RLZTracker::Cleanup() {
rlz_cache_.clear();
if (delegate_)
delegate_->Cleanup();
}
void RLZTracker::ScheduleDelayedInit(base::TimeDelta delay) {
DCHECK(delegate_) << "RLZTracker used before initialization";
// The RLZTracker is a singleton object that outlives any runnable tasks
// that will be queued up.
background_task_runner_->PostDelayedTask(
FROM_HERE,
base::BindOnce(&RLZTracker::DelayedInit, base::Unretained(this)), delay);
}
void RLZTracker::DelayedInit() {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
DCHECK(delegate_) << "RLZTracker used before initialization";
bool schedule_ping = false;
// For organic brandcodes do not use rlz at all. Empty brandcode usually
// means a chromium install. This is ok.
if (!delegate_->IsBrandOrganic(brand_)) {
RecordProductEvents(first_run_, is_google_default_search_,
is_google_homepage_, is_google_in_startpages_,
already_ran_, omnibox_used_, homepage_used_,
app_list_used_);
schedule_ping = true;
}
// If chrome has been reactivated, record the events for this brand
// as well.
if (!delegate_->IsBrandOrganic(reactivation_brand_)) {
rlz_lib::SupplementaryBranding branding(reactivation_brand_.c_str());
RecordProductEvents(first_run_, is_google_default_search_,
is_google_homepage_, is_google_in_startpages_,
already_ran_, omnibox_used_, homepage_used_,
app_list_used_);
schedule_ping = true;
}
already_ran_ = true;
if (schedule_ping)
ScheduleFinancialPing();
}
void RLZTracker::ScheduleFinancialPing() {
DCHECK(delegate_) << "RLZTracker used before initialization";
background_task_runner_->PostTask(
FROM_HERE,
base::BindOnce(&RLZTracker::PingNowImpl, base::Unretained(this)));
}
void RLZTracker::PingNowImpl() {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
DCHECK(delegate_) << "RLZTracker used before initialization";
TRACE_EVENT0("RLZ", "RLZTracker::PingNowImpl");
base::string16 lang;
delegate_->GetLanguage(&lang);
if (lang.empty())
lang = base::ASCIIToUTF16("en");
base::string16 referral;
delegate_->GetReferral(&referral);
if (!delegate_->IsBrandOrganic(brand_) &&
SendFinancialPing(brand_, lang, referral)) {
delegate_->ClearReferral();
{
base::AutoLock lock(cache_lock_);
rlz_cache_.clear();
}
// Prime the RLZ cache for the access points we are interested in.
GetAccessPointRlz(RLZTracker::ChromeOmnibox(), nullptr);
#if !defined(OS_IOS)
GetAccessPointRlz(RLZTracker::ChromeHomePage(), nullptr);
GetAccessPointRlz(RLZTracker::ChromeAppList(), nullptr);
#endif // !defined(OS_IOS)
}
if (!delegate_->IsBrandOrganic(reactivation_brand_)) {
rlz_lib::SupplementaryBranding branding(reactivation_brand_.c_str());
SendFinancialPing(reactivation_brand_, lang, referral);
}
}
bool RLZTracker::SendFinancialPing(const std::string& brand,
const base::string16& lang,
const base::string16& referral) {
return ::rlz::SendFinancialPing(brand, lang, referral);
}
// static
bool RLZTracker::RecordProductEvent(rlz_lib::Product product,
rlz_lib::AccessPoint point,
rlz_lib::Event event_id) {
// This method is called during unit tests while the RLZTracker has not been
// initialized, so check for the presence of a delegate and exit if there is
// none registered.
RLZTracker* tracker = GetInstance();
return !tracker->delegate_ ? false : tracker->RecordProductEventImpl(
product, point, event_id);
}
bool RLZTracker::RecordProductEventImpl(rlz_lib::Product product,
rlz_lib::AccessPoint point,
rlz_lib::Event event_id) {
DCHECK(delegate_) << "RLZTracker used before initialization";
// Make sure we don't access disk outside of the I/O thread.
// In such case we repost the task on the right thread and return error.
if (ScheduleRecordProductEvent(product, point, event_id))
return true;
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
bool ret = rlz_lib::RecordProductEvent(product, point, event_id);
// If chrome has been reactivated, record the event for this brand as well.
if (!reactivation_brand_.empty()) {
rlz_lib::SupplementaryBranding branding(reactivation_brand_.c_str());
ret &= rlz_lib::RecordProductEvent(product, point, event_id);
}
return ret;
}
bool RLZTracker::ScheduleRecordProductEvent(rlz_lib::Product product,
rlz_lib::AccessPoint point,
rlz_lib::Event event_id) {
DCHECK(delegate_) << "RLZTracker used before initialization";
if (!delegate_->IsOnUIThread())
return false;
background_task_runner_->PostTask(
FROM_HERE,
base::BindOnce(base::IgnoreResult(&RLZTracker::RecordProductEvent),
product, point, event_id));
return true;
}
void RLZTracker::RecordFirstSearch(rlz_lib::AccessPoint point) {
DCHECK(delegate_) << "RLZTracker used before initialization";
// Make sure we don't access disk outside of the I/O thread.
// In such case we repost the task on the right thread and return error.
if (ScheduleRecordFirstSearch(point))
return;
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
bool* record_used = GetAccessPointRecord(point);
// Try to record event now, else set the flag to try later when we
// attempt the ping.
if (!RecordProductEvent(rlz_lib::CHROME, point, rlz_lib::FIRST_SEARCH)) {
*record_used = true;
} else if (send_ping_immediately_ && point == ChromeOmnibox()) {
ScheduleDelayedInit(base::TimeDelta());
}
}
bool RLZTracker::ScheduleRecordFirstSearch(rlz_lib::AccessPoint point) {
DCHECK(delegate_) << "RLZTracker used before initialization";
if (!delegate_->IsOnUIThread())
return false;
background_task_runner_->PostTask(
FROM_HERE, base::BindOnce(&RLZTracker::RecordFirstSearch,
base::Unretained(this), point));
return true;
}
bool* RLZTracker::GetAccessPointRecord(rlz_lib::AccessPoint point) {
if (point == ChromeOmnibox())
return &omnibox_used_;
#if !defined(OS_IOS)
if (point == ChromeHomePage())
return &homepage_used_;
if (point == ChromeAppList())
return &app_list_used_;
#endif // !defined(OS_IOS)
NOTREACHED();
return nullptr;
}
// static
std::string RLZTracker::GetAccessPointHttpHeader(rlz_lib::AccessPoint point) {
TRACE_EVENT0("RLZ", "RLZTracker::GetAccessPointHttpHeader");
std::string extra_headers;
base::string16 rlz_string;
RLZTracker::GetAccessPointRlz(point, &rlz_string);
if (!rlz_string.empty()) {
return base::StringPrintf("X-Rlz-String: %s\r\n",
base::UTF16ToUTF8(rlz_string).c_str());
}
return extra_headers;
}
// GetAccessPointRlz() caches RLZ strings for all access points. If we had
// a successful ping, then we update the cached value.
// static
bool RLZTracker::GetAccessPointRlz(rlz_lib::AccessPoint point,
base::string16* rlz) {
// This method is called during unit tests while the RLZTracker has not been
// initialized, so check for the presence of a delegate and exit if there is
// none registered.
TRACE_EVENT0("RLZ", "RLZTracker::GetAccessPointRlz");
RLZTracker* tracker = GetInstance();
return !tracker->delegate_ ? false
: tracker->GetAccessPointRlzImpl(point, rlz);
}
// GetAccessPointRlz() caches RLZ strings for all access points. If we had
// a successful ping, then we update the cached value.
bool RLZTracker::GetAccessPointRlzImpl(rlz_lib::AccessPoint point,
base::string16* rlz) {
DCHECK(delegate_) << "RLZTracker used before initialization";
// If the RLZ string for the specified access point is already cached,
// simply return its value.
{
base::AutoLock lock(cache_lock_);
if (rlz_cache_.find(point) != rlz_cache_.end()) {
if (rlz)
*rlz = rlz_cache_[point];
return true;
}
}
// Make sure we don't access disk outside of the I/O thread.
// In such case we repost the task on the right thread and return error.
if (ScheduleGetAccessPointRlz(point))
return false;
char str_rlz[rlz_lib::kMaxRlzLength + 1];
if (!rlz_lib::GetAccessPointRlz(point, str_rlz, rlz_lib::kMaxRlzLength))
return false;
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
base::string16 rlz_local(base::ASCIIToUTF16(str_rlz));
if (rlz)
*rlz = rlz_local;
base::AutoLock lock(cache_lock_);
rlz_cache_[point] = rlz_local;
return true;
}
bool RLZTracker::ScheduleGetAccessPointRlz(rlz_lib::AccessPoint point) {
DCHECK(delegate_) << "RLZTracker used before initialization";
if (!delegate_->IsOnUIThread())
return false;
base::string16* not_used = nullptr;
background_task_runner_->PostTask(
FROM_HERE,
base::BindOnce(base::IgnoreResult(&RLZTracker::GetAccessPointRlz), point,
not_used));
return true;
}
#if defined(OS_CHROMEOS)
// static
void RLZTracker::ClearRlzState() {
RLZTracker* tracker = GetInstance();
if (tracker->delegate_)
tracker->ClearRlzStateImpl();
}
void RLZTracker::ClearRlzStateImpl() {
DCHECK(delegate_) << "RLZTracker used before initialization";
if (ScheduleClearRlzState())
return;
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
rlz_lib::ClearAllProductEvents(rlz_lib::CHROME);
}
bool RLZTracker::ScheduleClearRlzState() {
DCHECK(delegate_) << "RLZTracker used before initialization";
if (!delegate_->IsOnUIThread())
return false;
background_task_runner_->PostTask(
FROM_HERE,
base::BindOnce(&RLZTracker::ClearRlzStateImpl, base::Unretained(this)));
return true;
}
#endif
// static
void RLZTracker::CleanupRlz() {
GetInstance()->Cleanup();
rlz_lib::SetURLLoaderFactory(nullptr);
}
// static
void RLZTracker::EnableZeroDelayForTesting() {
GetInstance()->min_init_delay_ = base::TimeDelta();
}
#if !defined(OS_IOS)
// static
void RLZTracker::RecordAppListSearch() {
// This method is called during unit tests while the RLZTracker has not been
// initialized, so check for the presence of a delegate and exit if there is
// none registered.
RLZTracker* tracker = GetInstance();
if (tracker->delegate_)
tracker->RecordFirstSearch(RLZTracker::ChromeAppList());
}
#endif
} // namespace rlz