diff options
| author | sdefresne <sdefresne@chromium.org> | 2015-07-09 07:56:19 -0700 |
|---|---|---|
| committer | Commit bot <commit-bot@chromium.org> | 2015-07-09 14:56:44 +0000 |
| commit | 311a46585ee5b77486ef42d947bc7cc7566558b1 (patch) | |
| tree | d15f84966fbd2c4ade8527e74f99817974cc1979 /components/rlz/rlz_tracker.cc | |
| parent | ec894a5d76eadbe3cb489faed6469ff78e00aec8 (diff) | |
| download | chromium_src-311a46585ee5b77486ef42d947bc7cc7566558b1.zip chromium_src-311a46585ee5b77486ef42d947bc7cc7566558b1.tar.gz chromium_src-311a46585ee5b77486ef42d947bc7cc7566558b1.tar.bz2 | |
Componentize chrome/browser/rlz
Add new class RLZTrackerDelegate that abstract access to embedder
specific singletons and informations and provide an implementation for
Chrome based on the previous implementation.
Split rlz_unittest.cc in tests of the RLZTracker and Chrome
implementation of the RLZTrackerDelegate interface and move the
RLZTracker tests into the component.
Add a new gyp/gn variable "enable_rlz_support". This variable is true on
the platforms that support RLZ (currently Windows, Mac, iOS and
ChromeOS). Use it to build library and unit tests even when the RLZ
support is not enabled in the Chrome binary (this is still controlled by
"enable_rlz" and depends on the branding).
Enable the tests on iOS and convert rlz_tracker_ios.mm to a C++ file.
TEST=Run unit_tests and components_unittests on a platform that supports
RLZ (Windows, Mac, iOS or ChromeOS) and check that they pass. Then build
Chrome with "enable_rlz" and manually checks that RLZ are sent with
searches as expected.
BUG=504841,508148
Review URL: https://codereview.chromium.org/1212163011
Cr-Commit-Position: refs/heads/master@{#338040}
Diffstat (limited to 'components/rlz/rlz_tracker.cc')
| -rw-r--r-- | components/rlz/rlz_tracker.cc | 529 |
1 files changed, 529 insertions, 0 deletions
diff --git a/components/rlz/rlz_tracker.cc b/components/rlz/rlz_tracker.cc new file mode 100644 index 0000000..baa166a --- /dev/null +++ b/components/rlz/rlz_tracker.cc @@ -0,0 +1,529 @@ +// 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 "base/bind.h" +#include "base/message_loop/message_loop.h" +#include "base/strings/string_util.h" +#include "base/strings/utf_string_conversions.h" +#include "base/trace_event/trace_event.h" +#include "components/rlz/rlz_tracker_delegate.h" +#include "net/http/http_util.h" + +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. +const base::TimeDelta kMaxInitDelay = base::TimeDelta::FromSeconds(200); +const base::TimeDelta kMinInitDelay = base::TimeDelta::FromSeconds(20); + +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; + +// static +RLZTracker* RLZTracker::GetInstance() { + return tracker_ ? tracker_ : 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) { +} + +RLZTracker::~RLZTracker() { +} + +// static +void RLZTracker::SetRlzDelegate(scoped_ptr<RLZTrackerDelegate> delegate) { + GetInstance()->SetDelegate(delegate.Pass()); +} + +void RLZTracker::SetDelegate(scoped_ptr<RLZTrackerDelegate> delegate) { + DCHECK(delegate); + if (!delegate_) { + delegate_ = delegate.Pass(); + worker_pool_token_ = delegate_->GetBlockingPool()->GetSequenceToken(); + } +} + +// 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) { + 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_); + + // Could be null; don't run if so. RLZ will try again next restart. + net::URLRequestContextGetter* context_getter = delegate_->GetRequestContext(); + if (context_getter) { + rlz_lib::SetURLRequestContext(context_getter); + 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) { + // The RLZTracker is a singleton object that outlives any runnable tasks + // that will be queued up. + delegate_->GetBlockingPool()->PostDelayedSequencedWorkerTask( + worker_pool_token_, FROM_HERE, + base::Bind(&RLZTracker::DelayedInit, base::Unretained(this)), delay); +} + +void RLZTracker::DelayedInit() { + 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() { + delegate_->GetBlockingPool()->PostSequencedWorkerTaskWithShutdownBehavior( + worker_pool_token_, FROM_HERE, + base::Bind(&RLZTracker::PingNowImpl, base::Unretained(this)), + base::SequencedWorkerPool::SKIP_ON_SHUTDOWN); +} + +void RLZTracker::PingNowImpl() { + 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) { + return GetInstance()->RecordProductEventImpl(product, point, event_id); +} + +bool RLZTracker::RecordProductEventImpl(rlz_lib::Product product, + rlz_lib::AccessPoint point, + rlz_lib::Event event_id) { + // 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; + + 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) { + if (!delegate_->IsOnUIThread()) + return false; + + delegate_->GetBlockingPool()->PostSequencedWorkerTaskWithShutdownBehavior( + worker_pool_token_, FROM_HERE, + base::Bind(base::IgnoreResult(&RLZTracker::RecordProductEvent), product, + point, event_id), + base::SequencedWorkerPool::SKIP_ON_SHUTDOWN); + + return true; +} + +void RLZTracker::RecordFirstSearch(rlz_lib::AccessPoint point) { + // 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; + + 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) { + if (!delegate_->IsOnUIThread()) + return false; + delegate_->GetBlockingPool()->PostSequencedWorkerTaskWithShutdownBehavior( + worker_pool_token_, FROM_HERE, + base::Bind(&RLZTracker::RecordFirstSearch, base::Unretained(this), point), + base::SequencedWorkerPool::SKIP_ON_SHUTDOWN); + 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()) { + net::HttpUtil::AppendHeaderIfMissing("X-Rlz-String", + base::UTF16ToUTF8(rlz_string), + &extra_headers); + } + + return extra_headers; +} + +// GetAccessPointRlz() caches RLZ strings for all access points. If we had +// a successful ping, then we update the cached value. +bool RLZTracker::GetAccessPointRlz(rlz_lib::AccessPoint point, + base::string16* rlz) { + TRACE_EVENT0("RLZ", "RLZTracker::GetAccessPointRlz"); + return GetInstance()->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) { + // 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; + + 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) { + if (!delegate_->IsOnUIThread()) + return false; + + base::string16* not_used = nullptr; + delegate_->GetBlockingPool()->PostSequencedWorkerTaskWithShutdownBehavior( + worker_pool_token_, FROM_HERE, + base::Bind(base::IgnoreResult(&RLZTracker::GetAccessPointRlz), point, + not_used), + base::SequencedWorkerPool::SKIP_ON_SHUTDOWN); + return true; +} + +#if defined(OS_CHROMEOS) +// static +void RLZTracker::ClearRlzState() { + GetInstance()->ClearRlzStateImpl(); +} + +void RLZTracker::ClearRlzStateImpl() { + if (ScheduleClearRlzState()) + return; + rlz_lib::ClearAllProductEvents(rlz_lib::CHROME); +} + +bool RLZTracker::ScheduleClearRlzState() { + if (!delegate_->IsOnUIThread()) + return false; + + delegate_->GetBlockingPool()->PostSequencedWorkerTaskWithShutdownBehavior( + worker_pool_token_, FROM_HERE, + base::Bind(&RLZTracker::ClearRlzStateImpl, base::Unretained(this)), + base::SequencedWorkerPool::SKIP_ON_SHUTDOWN); + return true; +} +#endif + +// static +void RLZTracker::CleanupRlz() { + GetInstance()->Cleanup(); + rlz_lib::SetURLRequestContext(nullptr); +} + +// static +void RLZTracker::EnableZeroDelayForTesting() { + GetInstance()->min_init_delay_ = base::TimeDelta(); +} + +#if !defined(OS_IOS) +// static +void RLZTracker::RecordAppListSearch() { + GetInstance()->RecordFirstSearch(RLZTracker::ChromeAppList()); +} +#endif + +} // namespace rlz |
