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/message_loop/message_loop.h"
16 #include "base/prefs/pref_service.h"
17 #include "base/strings/string_util.h"
18 #include "base/strings/utf_string_conversions.h"
19 #include "base/trace_event/trace_event.h"
20 #include "chrome/browser/browser_process.h"
21 #include "chrome/browser/chrome_notification_types.h"
22 #include "chrome/browser/google/google_brand.h"
23 #include "chrome/browser/omnibox/omnibox_log.h"
24 #include "chrome/browser/prefs/session_startup_pref.h"
25 #include "chrome/browser/search_engines/template_url_service_factory.h"
26 #include "chrome/browser/ui/startup/startup_browser_creator.h"
27 #include "chrome/common/chrome_switches.h"
28 #include "chrome/common/pref_names.h"
29 #include "components/google/core/browser/google_util.h"
30 #include "components/search_engines/template_url.h"
31 #include "components/search_engines/template_url_service.h"
32 #include "content/public/browser/browser_thread.h"
33 #include "content/public/browser/navigation_controller.h"
34 #include "content/public/browser/navigation_details.h"
35 #include "content/public/browser/navigation_entry.h"
36 #include "content/public/browser/notification_service.h"
37 #include "net/http/http_util.h"
40 #include "chrome/installer/util/google_update_settings.h"
42 namespace GoogleUpdateSettings
{
43 static bool GetLanguage(base::string16
* language
) {
44 // TODO(thakis): Implement.
49 // The referral program is defunct and not used. No need to implement these
50 // functions on non-Win platforms.
51 static bool GetReferral(base::string16
* referral
) {
54 static bool ClearReferral() {
57 } // namespace GoogleUpdateSettings
60 using content::BrowserThread
;
61 using content::NavigationEntry
;
62 using content::NavigationController
;
66 // Maximum and minimum delay for financial ping we would allow to be set through
67 // master preferences. Somewhat arbitrary, may need to be adjusted in future.
68 const base::TimeDelta kMaxInitDelay
= base::TimeDelta::FromSeconds(200);
69 const base::TimeDelta kMinInitDelay
= base::TimeDelta::FromSeconds(20);
71 bool IsBrandOrganic(const std::string
& brand
) {
72 return brand
.empty() || google_brand::IsOrganic(brand
);
75 void RecordProductEvents(bool first_run
,
76 bool is_google_default_search
,
77 bool is_google_homepage
,
78 bool is_google_in_startpages
,
83 TRACE_EVENT0("RLZ", "RecordProductEvents");
84 // Record the installation of chrome. We call this all the time but the rlz
85 // lib should ignore all but the first one.
86 rlz_lib::RecordProductEvent(rlz_lib::CHROME
,
87 RLZTracker::ChromeOmnibox(),
90 rlz_lib::RecordProductEvent(rlz_lib::CHROME
,
91 RLZTracker::ChromeHomePage(),
93 rlz_lib::RecordProductEvent(rlz_lib::CHROME
,
94 RLZTracker::ChromeAppList(),
96 #endif // !defined(OS_IOS)
99 // Do the initial event recording if is the first run or if we have an
100 // empty rlz which means we haven't got a chance to do it.
101 char omnibox_rlz
[rlz_lib::kMaxRlzLength
+ 1];
102 if (!rlz_lib::GetAccessPointRlz(RLZTracker::ChromeOmnibox(), omnibox_rlz
,
103 rlz_lib::kMaxRlzLength
)) {
107 // Record if google is the initial search provider and/or home page.
108 if ((first_run
|| omnibox_rlz
[0] == 0) && is_google_default_search
) {
109 rlz_lib::RecordProductEvent(rlz_lib::CHROME
,
110 RLZTracker::ChromeOmnibox(),
111 rlz_lib::SET_TO_GOOGLE
);
115 char homepage_rlz
[rlz_lib::kMaxRlzLength
+ 1];
116 if (!rlz_lib::GetAccessPointRlz(RLZTracker::ChromeHomePage(), homepage_rlz
,
117 rlz_lib::kMaxRlzLength
)) {
121 if ((first_run
|| homepage_rlz
[0] == 0) &&
122 (is_google_homepage
|| is_google_in_startpages
)) {
123 rlz_lib::RecordProductEvent(rlz_lib::CHROME
,
124 RLZTracker::ChromeHomePage(),
125 rlz_lib::SET_TO_GOOGLE
);
128 char app_list_rlz
[rlz_lib::kMaxRlzLength
+ 1];
129 if (!rlz_lib::GetAccessPointRlz(RLZTracker::ChromeAppList(), app_list_rlz
,
130 rlz_lib::kMaxRlzLength
)) {
134 // Record if google is the initial search provider and/or home page.
135 if ((first_run
|| app_list_rlz
[0] == 0) && is_google_default_search
) {
136 rlz_lib::RecordProductEvent(rlz_lib::CHROME
,
137 RLZTracker::ChromeAppList(),
138 rlz_lib::SET_TO_GOOGLE
);
140 #endif // !defined(OS_IOS)
143 // Record first user interaction with the omnibox. We call this all the
144 // time but the rlz lib should ingore all but the first one.
146 rlz_lib::RecordProductEvent(rlz_lib::CHROME
,
147 RLZTracker::ChromeOmnibox(),
148 rlz_lib::FIRST_SEARCH
);
152 // Record first user interaction with the home page. We call this all the
153 // time but the rlz lib should ingore all but the first one.
154 if (homepage_used
|| is_google_in_startpages
) {
155 rlz_lib::RecordProductEvent(rlz_lib::CHROME
,
156 RLZTracker::ChromeHomePage(),
157 rlz_lib::FIRST_SEARCH
);
160 // Record first user interaction with the app list. We call this all the
161 // time but the rlz lib should ingore all but the first one.
163 rlz_lib::RecordProductEvent(rlz_lib::CHROME
,
164 RLZTracker::ChromeAppList(),
165 rlz_lib::FIRST_SEARCH
);
167 #endif // !defined(OS_IOS)
170 bool SendFinancialPing(const std::string
& brand
,
171 const base::string16
& lang
,
172 const base::string16
& referral
) {
173 rlz_lib::AccessPoint points
[] = {RLZTracker::ChromeOmnibox(),
175 RLZTracker::ChromeHomePage(),
176 RLZTracker::ChromeAppList(),
178 rlz_lib::NO_ACCESS_POINT
};
179 std::string
lang_ascii(base::UTF16ToASCII(lang
));
180 std::string
referral_ascii(base::UTF16ToASCII(referral
));
181 std::string product_signature
;
182 #if defined(OS_CHROMEOS)
183 product_signature
= "chromeos";
185 product_signature
= "chrome";
187 return rlz_lib::SendFinancialPing(rlz_lib::CHROME
, points
,
188 product_signature
.c_str(),
189 brand
.c_str(), referral_ascii
.c_str(),
190 lang_ascii
.c_str(), false, true);
195 RLZTracker
* RLZTracker::tracker_
= NULL
;
198 RLZTracker
* RLZTracker::GetInstance() {
199 return tracker_
? tracker_
: Singleton
<RLZTracker
>::get();
202 RLZTracker::RLZTracker()
204 send_ping_immediately_(false),
205 is_google_default_search_(false),
206 is_google_homepage_(false),
207 is_google_in_startpages_(false),
208 worker_pool_token_(BrowserThread::GetBlockingPool()->GetSequenceToken()),
210 omnibox_used_(false),
211 homepage_used_(false),
212 app_list_used_(false),
213 min_init_delay_(kMinInitDelay
) {
216 RLZTracker::~RLZTracker() {
220 bool RLZTracker::InitRlzDelayed(bool first_run
,
221 bool send_ping_immediately
,
222 base::TimeDelta delay
,
223 bool is_google_default_search
,
224 bool is_google_homepage
,
225 bool is_google_in_startpages
) {
226 return GetInstance()->Init(first_run
, send_ping_immediately
, delay
,
227 is_google_default_search
, is_google_homepage
,
228 is_google_in_startpages
);
232 bool RLZTracker::InitRlzFromProfileDelayed(Profile
* profile
,
234 bool send_ping_immediately
,
235 base::TimeDelta delay
) {
236 bool is_google_default_search
= false;
237 TemplateURLService
* template_url_service
=
238 TemplateURLServiceFactory::GetForProfile(profile
);
239 if (template_url_service
) {
240 const TemplateURL
* url_template
=
241 template_url_service
->GetDefaultSearchProvider();
242 is_google_default_search
=
243 url_template
&& url_template
->url_ref().HasGoogleBaseURLs(
244 template_url_service
->search_terms_data());
247 PrefService
* pref_service
= profile
->GetPrefs();
248 bool is_google_homepage
= google_util::IsGoogleHomePageUrl(
249 GURL(pref_service
->GetString(prefs::kHomePage
)));
251 bool is_google_in_startpages
= false;
253 // iOS does not have a notion of startpages.
254 SessionStartupPref session_startup_prefs
=
255 StartupBrowserCreator::GetSessionStartupPref(
256 *base::CommandLine::ForCurrentProcess(), profile
);
257 if (session_startup_prefs
.type
== SessionStartupPref::URLS
) {
258 is_google_in_startpages
=
259 std::count_if(session_startup_prefs
.urls
.begin(),
260 session_startup_prefs
.urls
.end(),
261 google_util::IsGoogleHomePageUrl
) > 0;
265 if (!InitRlzDelayed(first_run
, send_ping_immediately
, delay
,
266 is_google_default_search
, is_google_homepage
,
267 is_google_in_startpages
)) {
272 // Prime the RLZ cache for the home page access point so that its avaiable
273 // for the startup page if needed (i.e., when the startup page is set to
275 GetAccessPointRlz(ChromeHomePage(), NULL
);
276 #endif // !defined(OS_IOS)
281 bool RLZTracker::Init(bool first_run
,
282 bool send_ping_immediately
,
283 base::TimeDelta delay
,
284 bool is_google_default_search
,
285 bool is_google_homepage
,
286 bool is_google_in_startpages
) {
287 first_run_
= first_run
;
288 is_google_default_search_
= is_google_default_search
;
289 is_google_homepage_
= is_google_homepage
;
290 is_google_in_startpages_
= is_google_in_startpages
;
291 send_ping_immediately_
= send_ping_immediately
;
293 // Enable zero delays for testing.
294 if (base::CommandLine::ForCurrentProcess()->HasSwitch(::switches::kTestType
))
295 EnableZeroDelayForTesting();
297 delay
= std::min(kMaxInitDelay
, std::max(min_init_delay_
, delay
));
299 if (google_brand::GetBrand(&brand_
) && !IsBrandOrganic(brand_
)) {
300 // Register for notifications from the omnibox so that we can record when
301 // the user performs a first search.
302 registrar_
.Add(this, chrome::NOTIFICATION_OMNIBOX_OPENED_URL
,
303 content::NotificationService::AllSources());
306 // Register for notifications from navigations, to see if the user has used
308 registrar_
.Add(this, content::NOTIFICATION_NAV_ENTRY_COMMITTED
,
309 content::NotificationService::AllSources());
310 #endif // !defined(OS_IOS)
312 google_brand::GetReactivationBrand(&reactivation_brand_
);
314 net::URLRequestContextGetter
* context_getter
=
315 g_browser_process
->system_request_context();
317 // Could be NULL; don't run if so. RLZ will try again next restart.
318 if (context_getter
) {
319 rlz_lib::SetURLRequestContext(context_getter
);
320 ScheduleDelayedInit(delay
);
326 void RLZTracker::ScheduleDelayedInit(base::TimeDelta delay
) {
327 // The RLZTracker is a singleton object that outlives any runnable tasks
328 // that will be queued up.
329 BrowserThread::GetBlockingPool()->PostDelayedSequencedWorkerTask(
332 base::Bind(&RLZTracker::DelayedInit
, base::Unretained(this)),
336 void RLZTracker::DelayedInit() {
337 bool schedule_ping
= false;
339 // For organic brandcodes do not use rlz at all. Empty brandcode usually
340 // means a chromium install. This is ok.
341 if (!IsBrandOrganic(brand_
)) {
342 RecordProductEvents(first_run_
, is_google_default_search_
,
343 is_google_homepage_
, is_google_in_startpages_
,
344 already_ran_
, omnibox_used_
, homepage_used_
,
346 schedule_ping
= true;
349 // If chrome has been reactivated, record the events for this brand
351 if (!IsBrandOrganic(reactivation_brand_
)) {
352 rlz_lib::SupplementaryBranding
branding(reactivation_brand_
.c_str());
353 RecordProductEvents(first_run_
, is_google_default_search_
,
354 is_google_homepage_
, is_google_in_startpages_
,
355 already_ran_
, omnibox_used_
, homepage_used_
,
357 schedule_ping
= true;
363 ScheduleFinancialPing();
366 void RLZTracker::ScheduleFinancialPing() {
367 BrowserThread::GetBlockingPool()->PostSequencedWorkerTaskWithShutdownBehavior(
370 base::Bind(&RLZTracker::PingNowImpl
, base::Unretained(this)),
371 base::SequencedWorkerPool::SKIP_ON_SHUTDOWN
);
374 void RLZTracker::PingNowImpl() {
375 TRACE_EVENT0("RLZ", "RLZTracker::PingNowImpl");
377 GoogleUpdateSettings::GetLanguage(&lang
);
379 lang
= base::ASCIIToUTF16("en");
380 base::string16 referral
;
381 GoogleUpdateSettings::GetReferral(&referral
);
383 if (!IsBrandOrganic(brand_
) && SendFinancialPing(brand_
, lang
, referral
)) {
384 GoogleUpdateSettings::ClearReferral();
387 base::AutoLock
lock(cache_lock_
);
391 // Prime the RLZ cache for the access points we are interested in.
392 GetAccessPointRlz(RLZTracker::ChromeOmnibox(), NULL
);
394 GetAccessPointRlz(RLZTracker::ChromeHomePage(), NULL
);
395 GetAccessPointRlz(RLZTracker::ChromeAppList(), NULL
);
396 #endif // !defined(OS_IOS)
399 if (!IsBrandOrganic(reactivation_brand_
)) {
400 rlz_lib::SupplementaryBranding
branding(reactivation_brand_
.c_str());
401 SendFinancialPing(reactivation_brand_
, lang
, referral
);
405 bool RLZTracker::SendFinancialPing(const std::string
& brand
,
406 const base::string16
& lang
,
407 const base::string16
& referral
) {
408 return ::SendFinancialPing(brand
, lang
, referral
);
411 void RLZTracker::Observe(int type
,
412 const content::NotificationSource
& source
,
413 const content::NotificationDetails
& details
) {
415 case chrome::NOTIFICATION_OMNIBOX_OPENED_URL
:
416 // In M-36, we made NOTIFICATION_OMNIBOX_OPENED_URL fire more often than
417 // it did previously. The RLZ folks want RLZ's "first search" detection
418 // to remain as unaffected as possible by this change. This test is
419 // there to keep the old behavior.
420 if (!content::Details
<OmniboxLog
>(details
).ptr()->is_popup_open
)
422 RecordFirstSearch(ChromeOmnibox());
423 registrar_
.Remove(this, chrome::NOTIFICATION_OMNIBOX_OPENED_URL
,
424 content::NotificationService::AllSources());
427 case content::NOTIFICATION_NAV_ENTRY_COMMITTED
: {
428 // Firstly check if it is a Google search.
429 content::LoadCommittedDetails
* load_details
=
430 content::Details
<content::LoadCommittedDetails
>(details
).ptr();
431 if (load_details
== NULL
)
434 NavigationEntry
* entry
= load_details
->entry
;
438 if (google_util::IsGoogleSearchUrl(entry
->GetURL())) {
439 // If it is a Google search, check if it originates from HOMEPAGE by
440 // getting the previous NavigationEntry.
441 NavigationController
* controller
=
442 content::Source
<NavigationController
>(source
).ptr();
443 if (controller
== NULL
)
446 int entry_index
= controller
->GetLastCommittedEntryIndex();
450 const NavigationEntry
* previous_entry
= controller
->GetEntryAtIndex(
453 if (previous_entry
== NULL
)
456 // Make sure it is a Google web page originated from HOMEPAGE.
457 if (google_util::IsGoogleHomePageUrl(previous_entry
->GetURL()) &&
458 ((previous_entry
->GetTransitionType() &
459 ui::PAGE_TRANSITION_HOME_PAGE
) != 0)) {
460 RecordFirstSearch(ChromeHomePage());
461 registrar_
.Remove(this, content::NOTIFICATION_NAV_ENTRY_COMMITTED
,
462 content::NotificationService::AllSources());
467 #endif // !defined(OS_IOS)
475 bool RLZTracker::RecordProductEvent(rlz_lib::Product product
,
476 rlz_lib::AccessPoint point
,
477 rlz_lib::Event event_id
) {
478 return GetInstance()->RecordProductEventImpl(product
, point
, event_id
);
481 bool RLZTracker::RecordProductEventImpl(rlz_lib::Product product
,
482 rlz_lib::AccessPoint point
,
483 rlz_lib::Event event_id
) {
484 // Make sure we don't access disk outside of the I/O thread.
485 // In such case we repost the task on the right thread and return error.
486 if (ScheduleRecordProductEvent(product
, point
, event_id
))
489 bool ret
= rlz_lib::RecordProductEvent(product
, point
, event_id
);
491 // If chrome has been reactivated, record the event for this brand as well.
492 if (!reactivation_brand_
.empty()) {
493 rlz_lib::SupplementaryBranding
branding(reactivation_brand_
.c_str());
494 ret
&= rlz_lib::RecordProductEvent(product
, point
, event_id
);
500 bool RLZTracker::ScheduleRecordProductEvent(rlz_lib::Product product
,
501 rlz_lib::AccessPoint point
,
502 rlz_lib::Event event_id
) {
503 if (!BrowserThread::CurrentlyOn(BrowserThread::UI
))
506 BrowserThread::GetBlockingPool()->PostSequencedWorkerTaskWithShutdownBehavior(
509 base::Bind(base::IgnoreResult(&RLZTracker::RecordProductEvent
),
510 product
, point
, event_id
),
511 base::SequencedWorkerPool::SKIP_ON_SHUTDOWN
);
516 void RLZTracker::RecordFirstSearch(rlz_lib::AccessPoint point
) {
517 // Make sure we don't access disk outside of the I/O thread.
518 // In such case we repost the task on the right thread and return error.
519 if (ScheduleRecordFirstSearch(point
))
522 bool* record_used
= GetAccessPointRecord(point
);
524 // Try to record event now, else set the flag to try later when we
526 if (!RecordProductEvent(rlz_lib::CHROME
, point
, rlz_lib::FIRST_SEARCH
))
528 else if (send_ping_immediately_
&& point
== ChromeOmnibox())
529 ScheduleDelayedInit(base::TimeDelta());
532 bool RLZTracker::ScheduleRecordFirstSearch(rlz_lib::AccessPoint point
) {
533 if (!BrowserThread::CurrentlyOn(BrowserThread::UI
))
535 BrowserThread::GetBlockingPool()->PostSequencedWorkerTaskWithShutdownBehavior(
538 base::Bind(&RLZTracker::RecordFirstSearch
,
539 base::Unretained(this), point
),
540 base::SequencedWorkerPool::SKIP_ON_SHUTDOWN
);
544 bool* RLZTracker::GetAccessPointRecord(rlz_lib::AccessPoint point
) {
545 if (point
== ChromeOmnibox())
546 return &omnibox_used_
;
548 if (point
== ChromeHomePage())
549 return &homepage_used_
;
550 if (point
== ChromeAppList())
551 return &app_list_used_
;
552 #endif // !defined(OS_IOS)
558 std::string
RLZTracker::GetAccessPointHttpHeader(rlz_lib::AccessPoint point
) {
559 TRACE_EVENT0("RLZ", "RLZTracker::GetAccessPointHttpHeader");
560 std::string extra_headers
;
561 base::string16 rlz_string
;
562 RLZTracker::GetAccessPointRlz(point
, &rlz_string
);
563 if (!rlz_string
.empty()) {
564 net::HttpUtil::AppendHeaderIfMissing("X-Rlz-String",
565 base::UTF16ToUTF8(rlz_string
),
569 return extra_headers
;
572 // GetAccessPointRlz() caches RLZ strings for all access points. If we had
573 // a successful ping, then we update the cached value.
574 bool RLZTracker::GetAccessPointRlz(rlz_lib::AccessPoint point
,
575 base::string16
* rlz
) {
576 TRACE_EVENT0("RLZ", "RLZTracker::GetAccessPointRlz");
577 return GetInstance()->GetAccessPointRlzImpl(point
, rlz
);
580 // GetAccessPointRlz() caches RLZ strings for all access points. If we had
581 // a successful ping, then we update the cached value.
582 bool RLZTracker::GetAccessPointRlzImpl(rlz_lib::AccessPoint point
,
583 base::string16
* rlz
) {
584 // If the RLZ string for the specified access point is already cached,
585 // simply return its value.
587 base::AutoLock
lock(cache_lock_
);
588 if (rlz_cache_
.find(point
) != rlz_cache_
.end()) {
590 *rlz
= rlz_cache_
[point
];
595 // Make sure we don't access disk outside of the I/O thread.
596 // In such case we repost the task on the right thread and return error.
597 if (ScheduleGetAccessPointRlz(point
))
600 char str_rlz
[rlz_lib::kMaxRlzLength
+ 1];
601 if (!rlz_lib::GetAccessPointRlz(point
, str_rlz
, rlz_lib::kMaxRlzLength
))
604 base::string16
rlz_local(base::ASCIIToUTF16(str_rlz
));
608 base::AutoLock
lock(cache_lock_
);
609 rlz_cache_
[point
] = rlz_local
;
613 bool RLZTracker::ScheduleGetAccessPointRlz(rlz_lib::AccessPoint point
) {
614 if (!BrowserThread::CurrentlyOn(BrowserThread::UI
))
617 base::string16
* not_used
= NULL
;
618 BrowserThread::GetBlockingPool()->PostSequencedWorkerTaskWithShutdownBehavior(
621 base::Bind(base::IgnoreResult(&RLZTracker::GetAccessPointRlz
), point
,
623 base::SequencedWorkerPool::SKIP_ON_SHUTDOWN
);
627 #if defined(OS_CHROMEOS)
629 void RLZTracker::ClearRlzState() {
630 GetInstance()->ClearRlzStateImpl();
633 void RLZTracker::ClearRlzStateImpl() {
634 if (ScheduleClearRlzState())
636 rlz_lib::ClearAllProductEvents(rlz_lib::CHROME
);
639 bool RLZTracker::ScheduleClearRlzState() {
640 if (!BrowserThread::CurrentlyOn(BrowserThread::UI
))
643 BrowserThread::GetBlockingPool()->PostSequencedWorkerTaskWithShutdownBehavior(
646 base::Bind(&RLZTracker::ClearRlzStateImpl
,
647 base::Unretained(this)),
648 base::SequencedWorkerPool::SKIP_ON_SHUTDOWN
);
654 void RLZTracker::CleanupRlz() {
655 GetInstance()->rlz_cache_
.clear();
656 GetInstance()->registrar_
.RemoveAll();
657 rlz_lib::SetURLRequestContext(NULL
);
661 void RLZTracker::EnableZeroDelayForTesting() {
662 GetInstance()->min_init_delay_
= base::TimeDelta();
667 void RLZTracker::RecordAppListSearch() {
668 GetInstance()->RecordFirstSearch(RLZTracker::ChromeAppList());