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. 4 // 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. 8 9 #include "chrome/browser/rlz/rlz.h" 10 11 #include <algorithm> 12 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" 34 35 #if defined(OS_WIN) 36 #include "chrome/installer/util/google_update_settings.h" 37 #else 38 namespace GoogleUpdateSettings { 39 static bool GetLanguage(base::string16* language) { 40 // TODO(thakis): Implement. 41 NOTIMPLEMENTED(); 42 return false; 43 } 44 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) { 48 return true; 49 } 50 static bool ClearReferral() { 51 return true; 52 } 53 } // namespace GoogleUpdateSettings 54 #endif 55 56 using content::BrowserThread; 57 using content::NavigationEntry; 58 59 namespace { 60 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); 65 66 bool IsBrandOrganic(const std::string& brand) { 67 return brand.empty() || google_util::IsOrganic(brand); 68 } 69 70 void RecordProductEvents(bool first_run, 71 bool is_google_default_search, 72 bool is_google_homepage, 73 bool is_google_in_startpages, 74 bool already_ran, 75 bool omnibox_used, 76 bool homepage_used) { 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, 82 rlz_lib::INSTALL); 83 rlz_lib::RecordProductEvent(rlz_lib::CHROME, 84 RLZTracker::CHROME_HOME_PAGE, 85 rlz_lib::INSTALL); 86 87 if (!already_ran) { 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)) { 93 omnibox_rlz[0] = 0; 94 } 95 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); 101 } 102 103 char homepage_rlz[rlz_lib::kMaxRlzLength + 1]; 104 if (!rlz_lib::GetAccessPointRlz(RLZTracker::CHROME_HOME_PAGE, homepage_rlz, 105 rlz_lib::kMaxRlzLength)) { 106 homepage_rlz[0] = 0; 107 } 108 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); 114 } 115 } 116 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. 119 if (omnibox_used) { 120 rlz_lib::RecordProductEvent(rlz_lib::CHROME, 121 RLZTracker::CHROME_OMNIBOX, 122 rlz_lib::FIRST_SEARCH); 123 } 124 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); 131 } 132 } 133 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"; 145 #else 146 product_signature = "chrome"; 147 #endif 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); 152 } 153 154 } // namespace 155 156 #if defined(OS_WIN) 157 // static 158 const rlz_lib::AccessPoint RLZTracker::CHROME_OMNIBOX = 159 rlz_lib::CHROME_OMNIBOX; 160 // static 161 const rlz_lib::AccessPoint RLZTracker::CHROME_HOME_PAGE = 162 rlz_lib::CHROME_HOME_PAGE; 163 #elif defined(OS_IOS) 164 // static 165 const rlz_lib::AccessPoint RLZTracker::CHROME_OMNIBOX = 166 rlz_lib::CHROME_IOS_OMNIBOX; 167 // static 168 const rlz_lib::AccessPoint RLZTracker::CHROME_HOME_PAGE = 169 rlz_lib::CHROME_IOS_HOME_PAGE; 170 #elif defined(OS_MACOSX) 171 // static 172 const rlz_lib::AccessPoint RLZTracker::CHROME_OMNIBOX = 173 rlz_lib::CHROME_MAC_OMNIBOX; 174 // static 175 const rlz_lib::AccessPoint RLZTracker::CHROME_HOME_PAGE = 176 rlz_lib::CHROME_MAC_HOME_PAGE; 177 #elif defined(OS_CHROMEOS) 178 // static 179 const rlz_lib::AccessPoint RLZTracker::CHROME_OMNIBOX = 180 rlz_lib::CHROMEOS_OMNIBOX; 181 // static 182 const rlz_lib::AccessPoint RLZTracker::CHROME_HOME_PAGE = 183 rlz_lib::CHROMEOS_HOME_PAGE; 184 #endif 185 186 RLZTracker* RLZTracker::tracker_ = NULL; 187 188 // static 189 RLZTracker* RLZTracker::GetInstance() { 190 return tracker_ ? tracker_ : Singleton<RLZTracker>::get(); 191 } 192 193 RLZTracker::RLZTracker() 194 : first_run_(false), 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()), 200 already_ran_(false), 201 omnibox_used_(false), 202 homepage_used_(false), 203 min_init_delay_(kMinInitDelay) { 204 } 205 206 RLZTracker::~RLZTracker() { 207 } 208 209 // static 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); 219 } 220 221 // static 222 bool RLZTracker::InitRlzFromProfileDelayed(Profile* profile, 223 bool first_run, 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(); 234 } 235 236 PrefService* pref_service = profile->GetPrefs(); 237 bool is_google_homepage = google_util::IsGoogleHomePageUrl( 238 GURL(pref_service->GetString(prefs::kHomePage))); 239 240 bool is_google_in_startpages = false; 241 #if !defined(OS_IOS) 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; 251 } 252 #endif 253 254 if (!InitRlzDelayed(first_run, send_ping_immediately, delay, 255 is_google_default_search, is_google_homepage, 256 is_google_in_startpages)) { 257 return false; 258 } 259 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 262 // the home page). 263 GetAccessPointRlz(CHROME_HOME_PAGE, NULL); 264 265 return true; 266 } 267 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; 279 280 // Enable zero delays for testing. 281 if (CommandLine::ForCurrentProcess()->HasSwitch(::switches::kTestType)) 282 EnableZeroDelayForTesting(); 283 284 delay = std::min(kMaxInitDelay, std::max(min_init_delay_, delay)); 285 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()); 291 292 // Register for notifications from navigations, to see if the user has used 293 // the home page. 294 registrar_.Add(this, content::NOTIFICATION_NAV_ENTRY_PENDING, 295 content::NotificationService::AllSources()); 296 } 297 google_util::GetReactivationBrand(&reactivation_brand_); 298 299 net::URLRequestContextGetter* context_getter = 300 g_browser_process->system_request_context(); 301 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); 306 } 307 308 return true; 309 } 310 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( 315 worker_pool_token_, 316 FROM_HERE, 317 base::Bind(&RLZTracker::DelayedInit, base::Unretained(this)), 318 delay); 319 } 320 321 void RLZTracker::DelayedInit() { 322 bool schedule_ping = false; 323 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; 331 } 332 333 // If chrome has been reactivated, record the events for this brand 334 // as well. 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; 341 } 342 343 already_ran_ = true; 344 345 if (schedule_ping) 346 ScheduleFinancialPing(); 347 } 348 349 void RLZTracker::ScheduleFinancialPing() { 350 BrowserThread::GetBlockingPool()->PostSequencedWorkerTaskWithShutdownBehavior( 351 worker_pool_token_, 352 FROM_HERE, 353 base::Bind(&RLZTracker::PingNowImpl, base::Unretained(this)), 354 base::SequencedWorkerPool::SKIP_ON_SHUTDOWN); 355 } 356 357 void RLZTracker::PingNowImpl() { 358 TRACE_EVENT0("RLZ", "RLZTracker::PingNowImpl"); 359 base::string16 lang; 360 GoogleUpdateSettings::GetLanguage(&lang); 361 if (lang.empty()) 362 lang = ASCIIToUTF16("en"); 363 base::string16 referral; 364 GoogleUpdateSettings::GetReferral(&referral); 365 366 if (!IsBrandOrganic(brand_) && SendFinancialPing(brand_, lang, referral)) { 367 GoogleUpdateSettings::ClearReferral(); 368 369 { 370 base::AutoLock lock(cache_lock_); 371 rlz_cache_.clear(); 372 } 373 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); 377 } 378 379 if (!IsBrandOrganic(reactivation_brand_)) { 380 rlz_lib::SupplementaryBranding branding(reactivation_brand_.c_str()); 381 SendFinancialPing(reactivation_brand_, lang, referral); 382 } 383 } 384 385 bool RLZTracker::SendFinancialPing(const std::string& brand, 386 const base::string16& lang, 387 const base::string16& referral) { 388 return ::SendFinancialPing(brand, lang, referral); 389 } 390 391 void RLZTracker::Observe(int type, 392 const content::NotificationSource& source, 393 const content::NotificationDetails& details) { 394 switch (type) { 395 case chrome::NOTIFICATION_OMNIBOX_OPENED_URL: 396 RecordFirstSearch(CHROME_OMNIBOX); 397 registrar_.Remove(this, chrome::NOTIFICATION_OMNIBOX_OPENED_URL, 398 content::NotificationService::AllSources()); 399 break; 400 case content::NOTIFICATION_NAV_ENTRY_PENDING: { 401 const NavigationEntry* entry = 402 content::Details<content::NavigationEntry>(details).ptr(); 403 if (entry != NULL && 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()); 409 } 410 break; 411 } 412 default: 413 NOTREACHED(); 414 break; 415 } 416 } 417 418 // static 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); 423 } 424 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)) 431 return true; 432 433 bool ret = rlz_lib::RecordProductEvent(product, point, event_id); 434 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); 439 } 440 441 return ret; 442 } 443 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)) 448 return false; 449 450 BrowserThread::GetBlockingPool()->PostSequencedWorkerTaskWithShutdownBehavior( 451 worker_pool_token_, 452 FROM_HERE, 453 base::Bind(base::IgnoreResult(&RLZTracker::RecordProductEvent), 454 product, point, event_id), 455 base::SequencedWorkerPool::SKIP_ON_SHUTDOWN); 456 457 return true; 458 } 459 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)) 464 return; 465 466 bool* record_used = point == CHROME_OMNIBOX ? 467 &omnibox_used_ : &homepage_used_; 468 469 // Try to record event now, else set the flag to try later when we 470 // attempt the ping. 471 if (!RecordProductEvent(rlz_lib::CHROME, point, rlz_lib::FIRST_SEARCH)) 472 *record_used = true; 473 else if (send_ping_immediately_ && point == CHROME_OMNIBOX) 474 ScheduleDelayedInit(base::TimeDelta()); 475 } 476 477 bool RLZTracker::ScheduleRecordFirstSearch(rlz_lib::AccessPoint point) { 478 if (!BrowserThread::CurrentlyOn(BrowserThread::UI)) 479 return false; 480 BrowserThread::GetBlockingPool()->PostSequencedWorkerTaskWithShutdownBehavior( 481 worker_pool_token_, 482 FROM_HERE, 483 base::Bind(&RLZTracker::RecordFirstSearch, 484 base::Unretained(this), point), 485 base::SequencedWorkerPool::SKIP_ON_SHUTDOWN); 486 return true; 487 } 488 489 // static 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 UTF16ToUTF8(rlz_string), 498 &extra_headers); 499 } 500 501 return extra_headers; 502 } 503 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); 510 } 511 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. 518 { 519 base::AutoLock lock(cache_lock_); 520 if (rlz_cache_.find(point) != rlz_cache_.end()) { 521 if (rlz) 522 *rlz = rlz_cache_[point]; 523 return true; 524 } 525 } 526 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)) 530 return false; 531 532 char str_rlz[rlz_lib::kMaxRlzLength + 1]; 533 if (!rlz_lib::GetAccessPointRlz(point, str_rlz, rlz_lib::kMaxRlzLength)) 534 return false; 535 536 base::string16 rlz_local(ASCIIToUTF16(std::string(str_rlz))); 537 if (rlz) 538 *rlz = rlz_local; 539 540 base::AutoLock lock(cache_lock_); 541 rlz_cache_[point] = rlz_local; 542 return true; 543 } 544 545 bool RLZTracker::ScheduleGetAccessPointRlz(rlz_lib::AccessPoint point) { 546 if (!BrowserThread::CurrentlyOn(BrowserThread::UI)) 547 return false; 548 549 base::string16* not_used = NULL; 550 BrowserThread::GetBlockingPool()->PostSequencedWorkerTaskWithShutdownBehavior( 551 worker_pool_token_, 552 FROM_HERE, 553 base::Bind(base::IgnoreResult(&RLZTracker::GetAccessPointRlz), point, 554 not_used), 555 base::SequencedWorkerPool::SKIP_ON_SHUTDOWN); 556 return true; 557 } 558 559 #if defined(OS_CHROMEOS) 560 // static 561 void RLZTracker::ClearRlzState() { 562 GetInstance()->ClearRlzStateImpl(); 563 } 564 565 void RLZTracker::ClearRlzStateImpl() { 566 if (ScheduleClearRlzState()) 567 return; 568 rlz_lib::ClearAllProductEvents(rlz_lib::CHROME); 569 } 570 571 bool RLZTracker::ScheduleClearRlzState() { 572 if (!BrowserThread::CurrentlyOn(BrowserThread::UI)) 573 return false; 574 575 BrowserThread::GetBlockingPool()->PostSequencedWorkerTaskWithShutdownBehavior( 576 worker_pool_token_, 577 FROM_HERE, 578 base::Bind(&RLZTracker::ClearRlzStateImpl, 579 base::Unretained(this)), 580 base::SequencedWorkerPool::SKIP_ON_SHUTDOWN); 581 return true; 582 } 583 #endif 584 585 // static 586 void RLZTracker::CleanupRlz() { 587 GetInstance()->rlz_cache_.clear(); 588 GetInstance()->registrar_.RemoveAll(); 589 rlz_lib::SetURLRequestContext(NULL); 590 } 591 592 // static 593 void RLZTracker::EnableZeroDelayForTesting() { 594 GetInstance()->min_init_delay_ = base::TimeDelta(); 595 } 596