summaryrefslogtreecommitdiffstats
path: root/components/rlz/rlz_tracker.cc
diff options
context:
space:
mode:
Diffstat (limited to 'components/rlz/rlz_tracker.cc')
-rw-r--r--components/rlz/rlz_tracker.cc529
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