1 // Copyright (c) 2012 The Chromium Authors. All rights reserved.
2 // Use of this source code is governed by a BSD-style license that can be
3 // found in the LICENSE file.
5 // This code glues the RLZ library DLL with Chrome. It allows Chrome to work
6 // with or without the DLL being present. If the DLL is not present the
7 // functions do nothing and just return false.
9 #include "chrome/browser/rlz/rlz.h"
13 #include "base/bind.h"
14 #include "base/command_line.h"
15 #include "base/debug/trace_event.h"
16 #include "base/message_loop/message_loop.h"
17 #include "base/prefs/pref_service.h"
18 #include "base/strings/string_util.h"
19 #include "base/strings/utf_string_conversions.h"
20 #include "chrome/browser/browser_process.h"
21 #include "chrome/browser/chrome_notification_types.h"
22 #include "chrome/browser/google/google_util.h"
23 #include "chrome/browser/prefs/session_startup_pref.h"
24 #include "chrome/browser/search_engines/template_url.h"
25 #include "chrome/browser/search_engines/template_url_service.h"
26 #include "chrome/browser/search_engines/template_url_service_factory.h"
27 #include "chrome/browser/ui/startup/startup_browser_creator.h"
28 #include "chrome/common/chrome_switches.h"
29 #include "chrome/common/pref_names.h"
30 #include "content/public/browser/browser_thread.h"
31 #include "content/public/browser/navigation_entry.h"
32 #include "content/public/browser/notification_service.h"
33 #include "net/http/http_util.h"
36 #include "chrome/installer/util/google_update_settings.h"
38 namespace GoogleUpdateSettings {
39 static bool GetLanguage(base::string16* language) {
40 // TODO(thakis): Implement.
45 // The referral program is defunct and not used. No need to implement these
46 // functions on non-Win platforms.
47 static bool GetReferral(base::string16* referral) {
50 static bool ClearReferral() {
53 } // namespace GoogleUpdateSettings
56 using content::BrowserThread;
57 using content::NavigationEntry;
61 // Maximum and minimum delay for financial ping we would allow to be set through
62 // master preferences. Somewhat arbitrary, may need to be adjusted in future.
63 const base::TimeDelta kMaxInitDelay = base::TimeDelta::FromSeconds(200);
64 const base::TimeDelta kMinInitDelay = base::TimeDelta::FromSeconds(20);
66 bool IsBrandOrganic(const std::string& brand) {
67 return brand.empty() || google_util::IsOrganic(brand);
70 void RecordProductEvents(bool first_run,
71 bool is_google_default_search,
72 bool is_google_homepage,
73 bool is_google_in_startpages,
77 TRACE_EVENT0("RLZ", "RecordProductEvents");
78 // Record the installation of chrome. We call this all the time but the rlz
79 // lib should ignore all but the first one.
80 rlz_lib::RecordProductEvent(rlz_lib::CHROME,
81 RLZTracker::CHROME_OMNIBOX,
84 rlz_lib::RecordProductEvent(rlz_lib::CHROME,
85 RLZTracker::CHROME_HOME_PAGE,
87 #endif // !defined(OS_IOS)
90 // Do the initial event recording if is the first run or if we have an
91 // empty rlz which means we haven't got a chance to do it.
92 char omnibox_rlz[rlz_lib::kMaxRlzLength + 1];
93 if (!rlz_lib::GetAccessPointRlz(RLZTracker::CHROME_OMNIBOX, omnibox_rlz,
94 rlz_lib::kMaxRlzLength)) {
98 // Record if google is the initial search provider and/or home page.
99 if ((first_run || omnibox_rlz[0] == 0) && is_google_default_search) {
100 rlz_lib::RecordProductEvent(rlz_lib::CHROME,
101 RLZTracker::CHROME_OMNIBOX,
102 rlz_lib::SET_TO_GOOGLE);
106 char homepage_rlz[rlz_lib::kMaxRlzLength + 1];
107 if (!rlz_lib::GetAccessPointRlz(RLZTracker::CHROME_HOME_PAGE, homepage_rlz,
108 rlz_lib::kMaxRlzLength)) {
112 if ((first_run || homepage_rlz[0] == 0) &&
113 (is_google_homepage || is_google_in_startpages)) {
114 rlz_lib::RecordProductEvent(rlz_lib::CHROME,
115 RLZTracker::CHROME_HOME_PAGE,
116 rlz_lib::SET_TO_GOOGLE);
118 #endif // !defined(OS_IOS)
121 // Record first user interaction with the omnibox. We call this all the
122 // time but the rlz lib should ingore all but the first one.
124 rlz_lib::RecordProductEvent(rlz_lib::CHROME,
125 RLZTracker::CHROME_OMNIBOX,
126 rlz_lib::FIRST_SEARCH);
130 // Record first user interaction with the home page. We call this all the
131 // time but the rlz lib should ingore all but the first one.
132 if (homepage_used || is_google_in_startpages) {
133 rlz_lib::RecordProductEvent(rlz_lib::CHROME,
134 RLZTracker::CHROME_HOME_PAGE,
135 rlz_lib::FIRST_SEARCH);
137 #endif // !defined(OS_IOS)
140 bool SendFinancialPing(const std::string& brand,
141 const base::string16& lang,
142 const base::string16& referral) {
143 rlz_lib::AccessPoint points[] = {RLZTracker::CHROME_OMNIBOX,
145 RLZTracker::CHROME_HOME_PAGE,
147 rlz_lib::NO_ACCESS_POINT};
148 std::string lang_ascii(base::UTF16ToASCII(lang));
149 std::string referral_ascii(base::UTF16ToASCII(referral));
150 std::string product_signature;
151 #if defined(OS_CHROMEOS)
152 product_signature = "chromeos";
154 product_signature = "chrome";
156 return rlz_lib::SendFinancialPing(rlz_lib::CHROME, points,
157 product_signature.c_str(),
158 brand.c_str(), referral_ascii.c_str(),
159 lang_ascii.c_str(), false, true);
166 const rlz_lib::AccessPoint RLZTracker::CHROME_OMNIBOX =
167 rlz_lib::CHROME_OMNIBOX;
169 const rlz_lib::AccessPoint RLZTracker::CHROME_HOME_PAGE =
170 rlz_lib::CHROME_HOME_PAGE;
171 #elif defined(OS_IOS)
173 const rlz_lib::AccessPoint RLZTracker::CHROME_OMNIBOX =
174 rlz_lib::CHROME_IOS_OMNIBOX;
175 #elif defined(OS_MACOSX)
177 const rlz_lib::AccessPoint RLZTracker::CHROME_OMNIBOX =
178 rlz_lib::CHROME_MAC_OMNIBOX;
180 const rlz_lib::AccessPoint RLZTracker::CHROME_HOME_PAGE =
181 rlz_lib::CHROME_MAC_HOME_PAGE;
182 #elif defined(OS_CHROMEOS)
184 const rlz_lib::AccessPoint RLZTracker::CHROME_OMNIBOX =
185 rlz_lib::CHROMEOS_OMNIBOX;
187 const rlz_lib::AccessPoint RLZTracker::CHROME_HOME_PAGE =
188 rlz_lib::CHROMEOS_HOME_PAGE;
191 RLZTracker* RLZTracker::tracker_ = NULL;
194 RLZTracker* RLZTracker::GetInstance() {
195 return tracker_ ? tracker_ : Singleton<RLZTracker>::get();
198 RLZTracker::RLZTracker()
200 send_ping_immediately_(false),
201 is_google_default_search_(false),
202 is_google_homepage_(false),
203 is_google_in_startpages_(false),
204 worker_pool_token_(BrowserThread::GetBlockingPool()->GetSequenceToken()),
206 omnibox_used_(false),
207 homepage_used_(false),
208 min_init_delay_(kMinInitDelay) {
211 RLZTracker::~RLZTracker() {
215 bool RLZTracker::InitRlzDelayed(bool first_run,
216 bool send_ping_immediately,
217 base::TimeDelta delay,
218 bool is_google_default_search,
219 bool is_google_homepage,
220 bool is_google_in_startpages) {
221 return GetInstance()->Init(first_run, send_ping_immediately, delay,
222 is_google_default_search, is_google_homepage,
223 is_google_in_startpages);
227 bool RLZTracker::InitRlzFromProfileDelayed(Profile* profile,
229 bool send_ping_immediately,
230 base::TimeDelta delay) {
231 bool is_google_default_search = false;
232 TemplateURLService* template_url_service =
233 TemplateURLServiceFactory::GetForProfile(profile);
234 if (template_url_service) {
235 const TemplateURL* url_template =
236 template_url_service->GetDefaultSearchProvider();
237 is_google_default_search =
238 url_template && url_template->url_ref().HasGoogleBaseURLs();
241 PrefService* pref_service = profile->GetPrefs();
242 bool is_google_homepage = google_util::IsGoogleHomePageUrl(
243 GURL(pref_service->GetString(prefs::kHomePage)));
245 bool is_google_in_startpages = false;
247 // iOS does not have a notion of startpages.
248 SessionStartupPref session_startup_prefs =
249 StartupBrowserCreator::GetSessionStartupPref(
250 *CommandLine::ForCurrentProcess(), profile);
251 if (session_startup_prefs.type == SessionStartupPref::URLS) {
252 is_google_in_startpages =
253 std::count_if(session_startup_prefs.urls.begin(),
254 session_startup_prefs.urls.end(),
255 google_util::IsGoogleHomePageUrl) > 0;
259 if (!InitRlzDelayed(first_run, send_ping_immediately, delay,
260 is_google_default_search, is_google_homepage,
261 is_google_in_startpages)) {
266 // Prime the RLZ cache for the home page access point so that its avaiable
267 // for the startup page if needed (i.e., when the startup page is set to
269 GetAccessPointRlz(CHROME_HOME_PAGE, NULL);
270 #endif // !defined(OS_IOS)
275 bool RLZTracker::Init(bool first_run,
276 bool send_ping_immediately,
277 base::TimeDelta delay,
278 bool is_google_default_search,
279 bool is_google_homepage,
280 bool is_google_in_startpages) {
281 first_run_ = first_run;
282 is_google_default_search_ = is_google_default_search;
283 is_google_homepage_ = is_google_homepage;
284 is_google_in_startpages_ = is_google_in_startpages;
285 send_ping_immediately_ = send_ping_immediately;
287 // Enable zero delays for testing.
288 if (CommandLine::ForCurrentProcess()->HasSwitch(::switches::kTestType))
289 EnableZeroDelayForTesting();
291 delay = std::min(kMaxInitDelay, std::max(min_init_delay_, delay));
293 if (google_util::GetBrand(&brand_) && !IsBrandOrganic(brand_)) {
294 // Register for notifications from the omnibox so that we can record when
295 // the user performs a first search.
296 registrar_.Add(this, chrome::NOTIFICATION_OMNIBOX_OPENED_URL,
297 content::NotificationService::AllSources());
300 // Register for notifications from navigations, to see if the user has used
302 registrar_.Add(this, content::NOTIFICATION_NAV_ENTRY_PENDING,
303 content::NotificationService::AllSources());
304 #endif // !defined(OS_IOS)
306 google_util::GetReactivationBrand(&reactivation_brand_);
308 net::URLRequestContextGetter* context_getter =
309 g_browser_process->system_request_context();
311 // Could be NULL; don't run if so. RLZ will try again next restart.
312 if (context_getter) {
313 rlz_lib::SetURLRequestContext(context_getter);
314 ScheduleDelayedInit(delay);
320 void RLZTracker::ScheduleDelayedInit(base::TimeDelta delay) {
321 // The RLZTracker is a singleton object that outlives any runnable tasks
322 // that will be queued up.
323 BrowserThread::GetBlockingPool()->PostDelayedSequencedWorkerTask(
326 base::Bind(&RLZTracker::DelayedInit, base::Unretained(this)),
330 void RLZTracker::DelayedInit() {
331 bool schedule_ping = false;
333 // For organic brandcodes do not use rlz at all. Empty brandcode usually
334 // means a chromium install. This is ok.
335 if (!IsBrandOrganic(brand_)) {
336 RecordProductEvents(first_run_, is_google_default_search_,
337 is_google_homepage_, is_google_in_startpages_,
338 already_ran_, omnibox_used_, homepage_used_);
339 schedule_ping = true;
342 // If chrome has been reactivated, record the events for this brand
344 if (!IsBrandOrganic(reactivation_brand_)) {
345 rlz_lib::SupplementaryBranding branding(reactivation_brand_.c_str());
346 RecordProductEvents(first_run_, is_google_default_search_,
347 is_google_homepage_, is_google_in_startpages_,
348 already_ran_, omnibox_used_, homepage_used_);
349 schedule_ping = true;
355 ScheduleFinancialPing();
358 void RLZTracker::ScheduleFinancialPing() {
359 BrowserThread::GetBlockingPool()->PostSequencedWorkerTaskWithShutdownBehavior(
362 base::Bind(&RLZTracker::PingNowImpl, base::Unretained(this)),
363 base::SequencedWorkerPool::SKIP_ON_SHUTDOWN);
366 void RLZTracker::PingNowImpl() {
367 TRACE_EVENT0("RLZ", "RLZTracker::PingNowImpl");
369 GoogleUpdateSettings::GetLanguage(&lang);
371 lang = base::ASCIIToUTF16("en");
372 base::string16 referral;
373 GoogleUpdateSettings::GetReferral(&referral);
375 if (!IsBrandOrganic(brand_) && SendFinancialPing(brand_, lang, referral)) {
376 GoogleUpdateSettings::ClearReferral();
379 base::AutoLock lock(cache_lock_);
383 // Prime the RLZ cache for the access points we are interested in.
384 GetAccessPointRlz(RLZTracker::CHROME_OMNIBOX, NULL);
386 GetAccessPointRlz(RLZTracker::CHROME_HOME_PAGE, NULL);
387 #endif // !defined(OS_IOS)
390 if (!IsBrandOrganic(reactivation_brand_)) {
391 rlz_lib::SupplementaryBranding branding(reactivation_brand_.c_str());
392 SendFinancialPing(reactivation_brand_, lang, referral);
396 bool RLZTracker::SendFinancialPing(const std::string& brand,
397 const base::string16& lang,
398 const base::string16& referral) {
399 return ::SendFinancialPing(brand, lang, referral);
402 void RLZTracker::Observe(int type,
403 const content::NotificationSource& source,
404 const content::NotificationDetails& details) {
406 case chrome::NOTIFICATION_OMNIBOX_OPENED_URL:
407 RecordFirstSearch(CHROME_OMNIBOX);
408 registrar_.Remove(this, chrome::NOTIFICATION_OMNIBOX_OPENED_URL,
409 content::NotificationService::AllSources());
412 case content::NOTIFICATION_NAV_ENTRY_PENDING: {
413 const NavigationEntry* entry =
414 content::Details<content::NavigationEntry>(details).ptr();
416 ((entry->GetTransitionType() &
417 content::PAGE_TRANSITION_HOME_PAGE) != 0)) {
418 RecordFirstSearch(CHROME_HOME_PAGE);
419 registrar_.Remove(this, content::NOTIFICATION_NAV_ENTRY_PENDING,
420 content::NotificationService::AllSources());
424 #endif // !defined(OS_IOS)
432 bool RLZTracker::RecordProductEvent(rlz_lib::Product product,
433 rlz_lib::AccessPoint point,
434 rlz_lib::Event event_id) {
435 return GetInstance()->RecordProductEventImpl(product, point, event_id);
438 bool RLZTracker::RecordProductEventImpl(rlz_lib::Product product,
439 rlz_lib::AccessPoint point,
440 rlz_lib::Event event_id) {
441 // Make sure we don't access disk outside of the I/O thread.
442 // In such case we repost the task on the right thread and return error.
443 if (ScheduleRecordProductEvent(product, point, event_id))
446 bool ret = rlz_lib::RecordProductEvent(product, point, event_id);
448 // If chrome has been reactivated, record the event for this brand as well.
449 if (!reactivation_brand_.empty()) {
450 rlz_lib::SupplementaryBranding branding(reactivation_brand_.c_str());
451 ret &= rlz_lib::RecordProductEvent(product, point, event_id);
457 bool RLZTracker::ScheduleRecordProductEvent(rlz_lib::Product product,
458 rlz_lib::AccessPoint point,
459 rlz_lib::Event event_id) {
460 if (!BrowserThread::CurrentlyOn(BrowserThread::UI))
463 BrowserThread::GetBlockingPool()->PostSequencedWorkerTaskWithShutdownBehavior(
466 base::Bind(base::IgnoreResult(&RLZTracker::RecordProductEvent),
467 product, point, event_id),
468 base::SequencedWorkerPool::SKIP_ON_SHUTDOWN);
473 void RLZTracker::RecordFirstSearch(rlz_lib::AccessPoint point) {
474 // Make sure we don't access disk outside of the I/O thread.
475 // In such case we repost the task on the right thread and return error.
476 if (ScheduleRecordFirstSearch(point))
480 bool* record_used = point == CHROME_OMNIBOX ?
481 &omnibox_used_ : &homepage_used_;
483 DCHECK_EQ(CHROME_OMNIBOX, point);
484 bool* record_used = &omnibox_used_;
487 // Try to record event now, else set the flag to try later when we
489 if (!RecordProductEvent(rlz_lib::CHROME, point, rlz_lib::FIRST_SEARCH))
491 else if (send_ping_immediately_ && point == CHROME_OMNIBOX)
492 ScheduleDelayedInit(base::TimeDelta());
495 bool RLZTracker::ScheduleRecordFirstSearch(rlz_lib::AccessPoint point) {
496 if (!BrowserThread::CurrentlyOn(BrowserThread::UI))
498 BrowserThread::GetBlockingPool()->PostSequencedWorkerTaskWithShutdownBehavior(
501 base::Bind(&RLZTracker::RecordFirstSearch,
502 base::Unretained(this), point),
503 base::SequencedWorkerPool::SKIP_ON_SHUTDOWN);
508 std::string RLZTracker::GetAccessPointHttpHeader(rlz_lib::AccessPoint point) {
509 TRACE_EVENT0("RLZ", "RLZTracker::GetAccessPointHttpHeader");
510 std::string extra_headers;
511 base::string16 rlz_string;
512 RLZTracker::GetAccessPointRlz(point, &rlz_string);
513 if (!rlz_string.empty()) {
514 net::HttpUtil::AppendHeaderIfMissing("X-Rlz-String",
515 base::UTF16ToUTF8(rlz_string),
519 return extra_headers;
522 // GetAccessPointRlz() caches RLZ strings for all access points. If we had
523 // a successful ping, then we update the cached value.
524 bool RLZTracker::GetAccessPointRlz(rlz_lib::AccessPoint point,
525 base::string16* rlz) {
526 TRACE_EVENT0("RLZ", "RLZTracker::GetAccessPointRlz");
527 return GetInstance()->GetAccessPointRlzImpl(point, rlz);
530 // GetAccessPointRlz() caches RLZ strings for all access points. If we had
531 // a successful ping, then we update the cached value.
532 bool RLZTracker::GetAccessPointRlzImpl(rlz_lib::AccessPoint point,
533 base::string16* rlz) {
534 // If the RLZ string for the specified access point is already cached,
535 // simply return its value.
537 base::AutoLock lock(cache_lock_);
538 if (rlz_cache_.find(point) != rlz_cache_.end()) {
540 *rlz = rlz_cache_[point];
545 // Make sure we don't access disk outside of the I/O thread.
546 // In such case we repost the task on the right thread and return error.
547 if (ScheduleGetAccessPointRlz(point))
550 char str_rlz[rlz_lib::kMaxRlzLength + 1];
551 if (!rlz_lib::GetAccessPointRlz(point, str_rlz, rlz_lib::kMaxRlzLength))
554 base::string16 rlz_local(base::ASCIIToUTF16(std::string(str_rlz)));
558 base::AutoLock lock(cache_lock_);
559 rlz_cache_[point] = rlz_local;
563 bool RLZTracker::ScheduleGetAccessPointRlz(rlz_lib::AccessPoint point) {
564 if (!BrowserThread::CurrentlyOn(BrowserThread::UI))
567 base::string16* not_used = NULL;
568 BrowserThread::GetBlockingPool()->PostSequencedWorkerTaskWithShutdownBehavior(
571 base::Bind(base::IgnoreResult(&RLZTracker::GetAccessPointRlz), point,
573 base::SequencedWorkerPool::SKIP_ON_SHUTDOWN);
577 #if defined(OS_CHROMEOS)
579 void RLZTracker::ClearRlzState() {
580 GetInstance()->ClearRlzStateImpl();
583 void RLZTracker::ClearRlzStateImpl() {
584 if (ScheduleClearRlzState())
586 rlz_lib::ClearAllProductEvents(rlz_lib::CHROME);
589 bool RLZTracker::ScheduleClearRlzState() {
590 if (!BrowserThread::CurrentlyOn(BrowserThread::UI))
593 BrowserThread::GetBlockingPool()->PostSequencedWorkerTaskWithShutdownBehavior(
596 base::Bind(&RLZTracker::ClearRlzStateImpl,
597 base::Unretained(this)),
598 base::SequencedWorkerPool::SKIP_ON_SHUTDOWN);
604 void RLZTracker::CleanupRlz() {
605 GetInstance()->rlz_cache_.clear();
606 GetInstance()->registrar_.RemoveAll();
607 rlz_lib::SetURLRequestContext(NULL);
611 void RLZTracker::EnableZeroDelayForTesting() {
612 GetInstance()->min_init_delay_ = base::TimeDelta();