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
,
83 rlz_lib::RecordProductEvent(rlz_lib::CHROME
,
84 RLZTracker::CHROME_HOME_PAGE
,
88 // Do the initial event recording if is the first run or if we have an
89 // empty rlz which means we haven't got a chance to do it.
90 char omnibox_rlz
[rlz_lib::kMaxRlzLength
+ 1];
91 if (!rlz_lib::GetAccessPointRlz(RLZTracker::CHROME_OMNIBOX
, omnibox_rlz
,
92 rlz_lib::kMaxRlzLength
)) {
96 // Record if google is the initial search provider and/or home page.
97 if ((first_run
|| omnibox_rlz
[0] == 0) && is_google_default_search
) {
98 rlz_lib::RecordProductEvent(rlz_lib::CHROME
,
99 RLZTracker::CHROME_OMNIBOX
,
100 rlz_lib::SET_TO_GOOGLE
);
103 char homepage_rlz
[rlz_lib::kMaxRlzLength
+ 1];
104 if (!rlz_lib::GetAccessPointRlz(RLZTracker::CHROME_HOME_PAGE
, homepage_rlz
,
105 rlz_lib::kMaxRlzLength
)) {
109 if ((first_run
|| homepage_rlz
[0] == 0) &&
110 (is_google_homepage
|| is_google_in_startpages
)) {
111 rlz_lib::RecordProductEvent(rlz_lib::CHROME
,
112 RLZTracker::CHROME_HOME_PAGE
,
113 rlz_lib::SET_TO_GOOGLE
);
117 // Record first user interaction with the omnibox. We call this all the
118 // time but the rlz lib should ingore all but the first one.
120 rlz_lib::RecordProductEvent(rlz_lib::CHROME
,
121 RLZTracker::CHROME_OMNIBOX
,
122 rlz_lib::FIRST_SEARCH
);
125 // Record first user interaction with the home page. We call this all the
126 // time but the rlz lib should ingore all but the first one.
127 if (homepage_used
|| is_google_in_startpages
) {
128 rlz_lib::RecordProductEvent(rlz_lib::CHROME
,
129 RLZTracker::CHROME_HOME_PAGE
,
130 rlz_lib::FIRST_SEARCH
);
134 bool SendFinancialPing(const std::string
& brand
,
135 const base::string16
& lang
,
136 const base::string16
& referral
) {
137 rlz_lib::AccessPoint points
[] = {RLZTracker::CHROME_OMNIBOX
,
138 RLZTracker::CHROME_HOME_PAGE
,
139 rlz_lib::NO_ACCESS_POINT
};
140 std::string
lang_ascii(UTF16ToASCII(lang
));
141 std::string
referral_ascii(UTF16ToASCII(referral
));
142 std::string product_signature
;
143 #if defined(OS_CHROMEOS)
144 product_signature
= "chromeos";
146 product_signature
= "chrome";
148 return rlz_lib::SendFinancialPing(rlz_lib::CHROME
, points
,
149 product_signature
.c_str(),
150 brand
.c_str(), referral_ascii
.c_str(),
151 lang_ascii
.c_str(), false, true);
158 const rlz_lib::AccessPoint
RLZTracker::CHROME_OMNIBOX
=
159 rlz_lib::CHROME_OMNIBOX
;
161 const rlz_lib::AccessPoint
RLZTracker::CHROME_HOME_PAGE
=
162 rlz_lib::CHROME_HOME_PAGE
;
163 #elif defined(OS_IOS)
165 const rlz_lib::AccessPoint
RLZTracker::CHROME_OMNIBOX
=
166 rlz_lib::CHROME_IOS_OMNIBOX
;
168 const rlz_lib::AccessPoint
RLZTracker::CHROME_HOME_PAGE
=
169 rlz_lib::CHROME_IOS_HOME_PAGE
;
170 #elif defined(OS_MACOSX)
172 const rlz_lib::AccessPoint
RLZTracker::CHROME_OMNIBOX
=
173 rlz_lib::CHROME_MAC_OMNIBOX
;
175 const rlz_lib::AccessPoint
RLZTracker::CHROME_HOME_PAGE
=
176 rlz_lib::CHROME_MAC_HOME_PAGE
;
177 #elif defined(OS_CHROMEOS)
179 const rlz_lib::AccessPoint
RLZTracker::CHROME_OMNIBOX
=
180 rlz_lib::CHROMEOS_OMNIBOX
;
182 const rlz_lib::AccessPoint
RLZTracker::CHROME_HOME_PAGE
=
183 rlz_lib::CHROMEOS_HOME_PAGE
;
186 RLZTracker
* RLZTracker::tracker_
= NULL
;
189 RLZTracker
* RLZTracker::GetInstance() {
190 return tracker_
? tracker_
: Singleton
<RLZTracker
>::get();
193 RLZTracker::RLZTracker()
195 send_ping_immediately_(false),
196 is_google_default_search_(false),
197 is_google_homepage_(false),
198 is_google_in_startpages_(false),
199 worker_pool_token_(BrowserThread::GetBlockingPool()->GetSequenceToken()),
201 omnibox_used_(false),
202 homepage_used_(false),
203 min_init_delay_(kMinInitDelay
) {
206 RLZTracker::~RLZTracker() {
210 bool RLZTracker::InitRlzDelayed(bool first_run
,
211 bool send_ping_immediately
,
212 base::TimeDelta delay
,
213 bool is_google_default_search
,
214 bool is_google_homepage
,
215 bool is_google_in_startpages
) {
216 return GetInstance()->Init(first_run
, send_ping_immediately
, delay
,
217 is_google_default_search
, is_google_homepage
,
218 is_google_in_startpages
);
222 bool RLZTracker::InitRlzFromProfileDelayed(Profile
* profile
,
224 bool send_ping_immediately
,
225 base::TimeDelta delay
) {
226 bool is_google_default_search
= false;
227 TemplateURLService
* template_url_service
=
228 TemplateURLServiceFactory::GetForProfile(profile
);
229 if (template_url_service
) {
230 const TemplateURL
* url_template
=
231 template_url_service
->GetDefaultSearchProvider();
232 is_google_default_search
=
233 url_template
&& url_template
->url_ref().HasGoogleBaseURLs();
236 PrefService
* pref_service
= profile
->GetPrefs();
237 bool is_google_homepage
= google_util::IsGoogleHomePageUrl(
238 GURL(pref_service
->GetString(prefs::kHomePage
)));
240 bool is_google_in_startpages
= false;
242 // iOS does not have a notion of startpages.
243 SessionStartupPref session_startup_prefs
=
244 StartupBrowserCreator::GetSessionStartupPref(
245 *CommandLine::ForCurrentProcess(), profile
);
246 if (session_startup_prefs
.type
== SessionStartupPref::URLS
) {
247 is_google_in_startpages
=
248 std::count_if(session_startup_prefs
.urls
.begin(),
249 session_startup_prefs
.urls
.end(),
250 google_util::IsGoogleHomePageUrl
) > 0;
254 if (!InitRlzDelayed(first_run
, send_ping_immediately
, delay
,
255 is_google_default_search
, is_google_homepage
,
256 is_google_in_startpages
)) {
260 // Prime the RLZ cache for the home page access point so that its avaiable
261 // for the startup page if needed (i.e., when the startup page is set to
263 GetAccessPointRlz(CHROME_HOME_PAGE
, NULL
);
268 bool RLZTracker::Init(bool first_run
,
269 bool send_ping_immediately
,
270 base::TimeDelta delay
,
271 bool is_google_default_search
,
272 bool is_google_homepage
,
273 bool is_google_in_startpages
) {
274 first_run_
= first_run
;
275 is_google_default_search_
= is_google_default_search
;
276 is_google_homepage_
= is_google_homepage
;
277 is_google_in_startpages_
= is_google_in_startpages
;
278 send_ping_immediately_
= send_ping_immediately
;
280 // Enable zero delays for testing.
281 if (CommandLine::ForCurrentProcess()->HasSwitch(::switches::kTestType
))
282 EnableZeroDelayForTesting();
284 delay
= std::min(kMaxInitDelay
, std::max(min_init_delay_
, delay
));
286 if (google_util::GetBrand(&brand_
) && !IsBrandOrganic(brand_
)) {
287 // Register for notifications from the omnibox so that we can record when
288 // the user performs a first search.
289 registrar_
.Add(this, chrome::NOTIFICATION_OMNIBOX_OPENED_URL
,
290 content::NotificationService::AllSources());
292 // Register for notifications from navigations, to see if the user has used
294 registrar_
.Add(this, content::NOTIFICATION_NAV_ENTRY_PENDING
,
295 content::NotificationService::AllSources());
297 google_util::GetReactivationBrand(&reactivation_brand_
);
299 net::URLRequestContextGetter
* context_getter
=
300 g_browser_process
->system_request_context();
302 // Could be NULL; don't run if so. RLZ will try again next restart.
303 if (context_getter
) {
304 rlz_lib::SetURLRequestContext(context_getter
);
305 ScheduleDelayedInit(delay
);
311 void RLZTracker::ScheduleDelayedInit(base::TimeDelta delay
) {
312 // The RLZTracker is a singleton object that outlives any runnable tasks
313 // that will be queued up.
314 BrowserThread::GetBlockingPool()->PostDelayedSequencedWorkerTask(
317 base::Bind(&RLZTracker::DelayedInit
, base::Unretained(this)),
321 void RLZTracker::DelayedInit() {
322 bool schedule_ping
= false;
324 // For organic brandcodes do not use rlz at all. Empty brandcode usually
325 // means a chromium install. This is ok.
326 if (!IsBrandOrganic(brand_
)) {
327 RecordProductEvents(first_run_
, is_google_default_search_
,
328 is_google_homepage_
, is_google_in_startpages_
,
329 already_ran_
, omnibox_used_
, homepage_used_
);
330 schedule_ping
= true;
333 // If chrome has been reactivated, record the events for this brand
335 if (!IsBrandOrganic(reactivation_brand_
)) {
336 rlz_lib::SupplementaryBranding
branding(reactivation_brand_
.c_str());
337 RecordProductEvents(first_run_
, is_google_default_search_
,
338 is_google_homepage_
, is_google_in_startpages_
,
339 already_ran_
, omnibox_used_
, homepage_used_
);
340 schedule_ping
= true;
346 ScheduleFinancialPing();
349 void RLZTracker::ScheduleFinancialPing() {
350 BrowserThread::GetBlockingPool()->PostSequencedWorkerTaskWithShutdownBehavior(
353 base::Bind(&RLZTracker::PingNowImpl
, base::Unretained(this)),
354 base::SequencedWorkerPool::SKIP_ON_SHUTDOWN
);
357 void RLZTracker::PingNowImpl() {
358 TRACE_EVENT0("RLZ", "RLZTracker::PingNowImpl");
360 GoogleUpdateSettings::GetLanguage(&lang
);
362 lang
= base::ASCIIToUTF16("en");
363 base::string16 referral
;
364 GoogleUpdateSettings::GetReferral(&referral
);
366 if (!IsBrandOrganic(brand_
) && SendFinancialPing(brand_
, lang
, referral
)) {
367 GoogleUpdateSettings::ClearReferral();
370 base::AutoLock
lock(cache_lock_
);
374 // Prime the RLZ cache for the access points we are interested in.
375 GetAccessPointRlz(RLZTracker::CHROME_OMNIBOX
, NULL
);
376 GetAccessPointRlz(RLZTracker::CHROME_HOME_PAGE
, NULL
);
379 if (!IsBrandOrganic(reactivation_brand_
)) {
380 rlz_lib::SupplementaryBranding
branding(reactivation_brand_
.c_str());
381 SendFinancialPing(reactivation_brand_
, lang
, referral
);
385 bool RLZTracker::SendFinancialPing(const std::string
& brand
,
386 const base::string16
& lang
,
387 const base::string16
& referral
) {
388 return ::SendFinancialPing(brand
, lang
, referral
);
391 void RLZTracker::Observe(int type
,
392 const content::NotificationSource
& source
,
393 const content::NotificationDetails
& details
) {
395 case chrome::NOTIFICATION_OMNIBOX_OPENED_URL
:
396 RecordFirstSearch(CHROME_OMNIBOX
);
397 registrar_
.Remove(this, chrome::NOTIFICATION_OMNIBOX_OPENED_URL
,
398 content::NotificationService::AllSources());
400 case content::NOTIFICATION_NAV_ENTRY_PENDING
: {
401 const NavigationEntry
* entry
=
402 content::Details
<content::NavigationEntry
>(details
).ptr();
404 ((entry
->GetTransitionType() &
405 content::PAGE_TRANSITION_HOME_PAGE
) != 0)) {
406 RecordFirstSearch(CHROME_HOME_PAGE
);
407 registrar_
.Remove(this, content::NOTIFICATION_NAV_ENTRY_PENDING
,
408 content::NotificationService::AllSources());
419 bool RLZTracker::RecordProductEvent(rlz_lib::Product product
,
420 rlz_lib::AccessPoint point
,
421 rlz_lib::Event event_id
) {
422 return GetInstance()->RecordProductEventImpl(product
, point
, event_id
);
425 bool RLZTracker::RecordProductEventImpl(rlz_lib::Product product
,
426 rlz_lib::AccessPoint point
,
427 rlz_lib::Event event_id
) {
428 // Make sure we don't access disk outside of the I/O thread.
429 // In such case we repost the task on the right thread and return error.
430 if (ScheduleRecordProductEvent(product
, point
, event_id
))
433 bool ret
= rlz_lib::RecordProductEvent(product
, point
, event_id
);
435 // If chrome has been reactivated, record the event for this brand as well.
436 if (!reactivation_brand_
.empty()) {
437 rlz_lib::SupplementaryBranding
branding(reactivation_brand_
.c_str());
438 ret
&= rlz_lib::RecordProductEvent(product
, point
, event_id
);
444 bool RLZTracker::ScheduleRecordProductEvent(rlz_lib::Product product
,
445 rlz_lib::AccessPoint point
,
446 rlz_lib::Event event_id
) {
447 if (!BrowserThread::CurrentlyOn(BrowserThread::UI
))
450 BrowserThread::GetBlockingPool()->PostSequencedWorkerTaskWithShutdownBehavior(
453 base::Bind(base::IgnoreResult(&RLZTracker::RecordProductEvent
),
454 product
, point
, event_id
),
455 base::SequencedWorkerPool::SKIP_ON_SHUTDOWN
);
460 void RLZTracker::RecordFirstSearch(rlz_lib::AccessPoint point
) {
461 // Make sure we don't access disk outside of the I/O thread.
462 // In such case we repost the task on the right thread and return error.
463 if (ScheduleRecordFirstSearch(point
))
466 bool* record_used
= point
== CHROME_OMNIBOX
?
467 &omnibox_used_
: &homepage_used_
;
469 // Try to record event now, else set the flag to try later when we
471 if (!RecordProductEvent(rlz_lib::CHROME
, point
, rlz_lib::FIRST_SEARCH
))
473 else if (send_ping_immediately_
&& point
== CHROME_OMNIBOX
)
474 ScheduleDelayedInit(base::TimeDelta());
477 bool RLZTracker::ScheduleRecordFirstSearch(rlz_lib::AccessPoint point
) {
478 if (!BrowserThread::CurrentlyOn(BrowserThread::UI
))
480 BrowserThread::GetBlockingPool()->PostSequencedWorkerTaskWithShutdownBehavior(
483 base::Bind(&RLZTracker::RecordFirstSearch
,
484 base::Unretained(this), point
),
485 base::SequencedWorkerPool::SKIP_ON_SHUTDOWN
);
490 std::string
RLZTracker::GetAccessPointHttpHeader(rlz_lib::AccessPoint point
) {
491 TRACE_EVENT0("RLZ", "RLZTracker::GetAccessPointHttpHeader");
492 std::string extra_headers
;
493 base::string16 rlz_string
;
494 RLZTracker::GetAccessPointRlz(point
, &rlz_string
);
495 if (!rlz_string
.empty()) {
496 net::HttpUtil::AppendHeaderIfMissing("X-Rlz-String",
497 base::UTF16ToUTF8(rlz_string
),
501 return extra_headers
;
504 // GetAccessPointRlz() caches RLZ strings for all access points. If we had
505 // a successful ping, then we update the cached value.
506 bool RLZTracker::GetAccessPointRlz(rlz_lib::AccessPoint point
,
507 base::string16
* rlz
) {
508 TRACE_EVENT0("RLZ", "RLZTracker::GetAccessPointRlz");
509 return GetInstance()->GetAccessPointRlzImpl(point
, rlz
);
512 // GetAccessPointRlz() caches RLZ strings for all access points. If we had
513 // a successful ping, then we update the cached value.
514 bool RLZTracker::GetAccessPointRlzImpl(rlz_lib::AccessPoint point
,
515 base::string16
* rlz
) {
516 // If the RLZ string for the specified access point is already cached,
517 // simply return its value.
519 base::AutoLock
lock(cache_lock_
);
520 if (rlz_cache_
.find(point
) != rlz_cache_
.end()) {
522 *rlz
= rlz_cache_
[point
];
527 // Make sure we don't access disk outside of the I/O thread.
528 // In such case we repost the task on the right thread and return error.
529 if (ScheduleGetAccessPointRlz(point
))
532 char str_rlz
[rlz_lib::kMaxRlzLength
+ 1];
533 if (!rlz_lib::GetAccessPointRlz(point
, str_rlz
, rlz_lib::kMaxRlzLength
))
536 base::string16
rlz_local(base::ASCIIToUTF16(std::string(str_rlz
)));
540 base::AutoLock
lock(cache_lock_
);
541 rlz_cache_
[point
] = rlz_local
;
545 bool RLZTracker::ScheduleGetAccessPointRlz(rlz_lib::AccessPoint point
) {
546 if (!BrowserThread::CurrentlyOn(BrowserThread::UI
))
549 base::string16
* not_used
= NULL
;
550 BrowserThread::GetBlockingPool()->PostSequencedWorkerTaskWithShutdownBehavior(
553 base::Bind(base::IgnoreResult(&RLZTracker::GetAccessPointRlz
), point
,
555 base::SequencedWorkerPool::SKIP_ON_SHUTDOWN
);
559 #if defined(OS_CHROMEOS)
561 void RLZTracker::ClearRlzState() {
562 GetInstance()->ClearRlzStateImpl();
565 void RLZTracker::ClearRlzStateImpl() {
566 if (ScheduleClearRlzState())
568 rlz_lib::ClearAllProductEvents(rlz_lib::CHROME
);
571 bool RLZTracker::ScheduleClearRlzState() {
572 if (!BrowserThread::CurrentlyOn(BrowserThread::UI
))
575 BrowserThread::GetBlockingPool()->PostSequencedWorkerTaskWithShutdownBehavior(
578 base::Bind(&RLZTracker::ClearRlzStateImpl
,
579 base::Unretained(this)),
580 base::SequencedWorkerPool::SKIP_ON_SHUTDOWN
);
586 void RLZTracker::CleanupRlz() {
587 GetInstance()->rlz_cache_
.clear();
588 GetInstance()->registrar_
.RemoveAll();
589 rlz_lib::SetURLRequestContext(NULL
);
593 void RLZTracker::EnableZeroDelayForTesting() {
594 GetInstance()->min_init_delay_
= base::TimeDelta();