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(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(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 string16& lang, 136 const 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_MACOSX) 164 // static 165 const rlz_lib::AccessPoint RLZTracker::CHROME_OMNIBOX = 166 rlz_lib::CHROME_MAC_OMNIBOX; 167 // static 168 const rlz_lib::AccessPoint RLZTracker::CHROME_HOME_PAGE = 169 rlz_lib::CHROME_MAC_HOME_PAGE; 170 #elif defined(OS_CHROMEOS) 171 // static 172 const rlz_lib::AccessPoint RLZTracker::CHROME_OMNIBOX = 173 rlz_lib::CHROMEOS_OMNIBOX; 174 // static 175 const rlz_lib::AccessPoint RLZTracker::CHROME_HOME_PAGE = 176 rlz_lib::CHROMEOS_HOME_PAGE; 177 #endif 178 179 RLZTracker* RLZTracker::tracker_ = NULL; 180 181 // static 182 RLZTracker* RLZTracker::GetInstance() { 183 return tracker_ ? tracker_ : Singleton<RLZTracker>::get(); 184 } 185 186 RLZTracker::RLZTracker() 187 : first_run_(false), 188 send_ping_immediately_(false), 189 is_google_default_search_(false), 190 is_google_homepage_(false), 191 is_google_in_startpages_(false), 192 worker_pool_token_(BrowserThread::GetBlockingPool()->GetSequenceToken()), 193 already_ran_(false), 194 omnibox_used_(false), 195 homepage_used_(false), 196 min_init_delay_(kMinInitDelay) { 197 } 198 199 RLZTracker::~RLZTracker() { 200 } 201 202 // static 203 bool RLZTracker::InitRlzDelayed(bool first_run, 204 bool send_ping_immediately, 205 base::TimeDelta delay, 206 bool is_google_default_search, 207 bool is_google_homepage, 208 bool is_google_in_startpages) { 209 return GetInstance()->Init(first_run, send_ping_immediately, delay, 210 is_google_default_search, is_google_homepage, 211 is_google_in_startpages); 212 } 213 214 // static 215 bool RLZTracker::InitRlzFromProfileDelayed(Profile* profile, 216 bool first_run, 217 bool send_ping_immediately, 218 base::TimeDelta delay) { 219 bool is_google_default_search = false; 220 TemplateURLService* template_url_service = 221 TemplateURLServiceFactory::GetForProfile(profile); 222 if (template_url_service) { 223 const TemplateURL* url_template = 224 template_url_service->GetDefaultSearchProvider(); 225 is_google_default_search = 226 url_template && url_template->url_ref().HasGoogleBaseURLs(); 227 } 228 229 PrefService* pref_service = profile->GetPrefs(); 230 bool is_google_homepage = google_util::IsGoogleHomePageUrl( 231 GURL(pref_service->GetString(prefs::kHomePage))); 232 233 bool is_google_in_startpages = false; 234 SessionStartupPref session_startup_prefs = 235 StartupBrowserCreator::GetSessionStartupPref( 236 *CommandLine::ForCurrentProcess(), profile); 237 if (session_startup_prefs.type == SessionStartupPref::URLS) { 238 is_google_in_startpages = 239 std::count_if(session_startup_prefs.urls.begin(), 240 session_startup_prefs.urls.end(), 241 google_util::IsGoogleHomePageUrl) > 0; 242 } 243 244 if (!InitRlzDelayed(first_run, send_ping_immediately, delay, 245 is_google_default_search, is_google_homepage, 246 is_google_in_startpages)) { 247 return false; 248 } 249 250 // Prime the RLZ cache for the home page access point so that its avaiable 251 // for the startup page if needed (i.e., when the startup page is set to 252 // the home page). 253 GetAccessPointRlz(CHROME_HOME_PAGE, NULL); 254 255 return true; 256 } 257 258 bool RLZTracker::Init(bool first_run, 259 bool send_ping_immediately, 260 base::TimeDelta delay, 261 bool is_google_default_search, 262 bool is_google_homepage, 263 bool is_google_in_startpages) { 264 first_run_ = first_run; 265 is_google_default_search_ = is_google_default_search; 266 is_google_homepage_ = is_google_homepage; 267 is_google_in_startpages_ = is_google_in_startpages; 268 send_ping_immediately_ = send_ping_immediately; 269 270 // Enable zero delays for testing. 271 if (CommandLine::ForCurrentProcess()->HasSwitch(::switches::kTestType)) 272 EnableZeroDelayForTesting(); 273 274 delay = std::min(kMaxInitDelay, std::max(min_init_delay_, delay)); 275 276 if (google_util::GetBrand(&brand_) && !IsBrandOrganic(brand_)) { 277 // Register for notifications from the omnibox so that we can record when 278 // the user performs a first search. 279 registrar_.Add(this, chrome::NOTIFICATION_OMNIBOX_OPENED_URL, 280 content::NotificationService::AllSources()); 281 282 // Register for notifications from navigations, to see if the user has used 283 // the home page. 284 registrar_.Add(this, content::NOTIFICATION_NAV_ENTRY_PENDING, 285 content::NotificationService::AllSources()); 286 } 287 google_util::GetReactivationBrand(&reactivation_brand_); 288 289 net::URLRequestContextGetter* context_getter = 290 g_browser_process->system_request_context(); 291 292 // Could be NULL; don't run if so. RLZ will try again next restart. 293 if (context_getter) { 294 rlz_lib::SetURLRequestContext(context_getter); 295 ScheduleDelayedInit(delay); 296 } 297 298 return true; 299 } 300 301 void RLZTracker::ScheduleDelayedInit(base::TimeDelta delay) { 302 // The RLZTracker is a singleton object that outlives any runnable tasks 303 // that will be queued up. 304 BrowserThread::GetBlockingPool()->PostDelayedSequencedWorkerTask( 305 worker_pool_token_, 306 FROM_HERE, 307 base::Bind(&RLZTracker::DelayedInit, base::Unretained(this)), 308 delay); 309 } 310 311 void RLZTracker::DelayedInit() { 312 bool schedule_ping = false; 313 314 // For organic brandcodes do not use rlz at all. Empty brandcode usually 315 // means a chromium install. This is ok. 316 if (!IsBrandOrganic(brand_)) { 317 RecordProductEvents(first_run_, is_google_default_search_, 318 is_google_homepage_, is_google_in_startpages_, 319 already_ran_, omnibox_used_, homepage_used_); 320 schedule_ping = true; 321 } 322 323 // If chrome has been reactivated, record the events for this brand 324 // as well. 325 if (!IsBrandOrganic(reactivation_brand_)) { 326 rlz_lib::SupplementaryBranding branding(reactivation_brand_.c_str()); 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 already_ran_ = true; 334 335 if (schedule_ping) 336 ScheduleFinancialPing(); 337 } 338 339 void RLZTracker::ScheduleFinancialPing() { 340 BrowserThread::GetBlockingPool()->PostSequencedWorkerTaskWithShutdownBehavior( 341 worker_pool_token_, 342 FROM_HERE, 343 base::Bind(&RLZTracker::PingNowImpl, base::Unretained(this)), 344 base::SequencedWorkerPool::SKIP_ON_SHUTDOWN); 345 } 346 347 void RLZTracker::PingNowImpl() { 348 TRACE_EVENT0("RLZ", "RLZTracker::PingNowImpl"); 349 string16 lang; 350 GoogleUpdateSettings::GetLanguage(&lang); 351 if (lang.empty()) 352 lang = ASCIIToUTF16("en"); 353 string16 referral; 354 GoogleUpdateSettings::GetReferral(&referral); 355 356 if (!IsBrandOrganic(brand_) && SendFinancialPing(brand_, lang, referral)) { 357 GoogleUpdateSettings::ClearReferral(); 358 359 { 360 base::AutoLock lock(cache_lock_); 361 rlz_cache_.clear(); 362 } 363 364 // Prime the RLZ cache for the access points we are interested in. 365 GetAccessPointRlz(RLZTracker::CHROME_OMNIBOX, NULL); 366 GetAccessPointRlz(RLZTracker::CHROME_HOME_PAGE, NULL); 367 } 368 369 if (!IsBrandOrganic(reactivation_brand_)) { 370 rlz_lib::SupplementaryBranding branding(reactivation_brand_.c_str()); 371 SendFinancialPing(reactivation_brand_, lang, referral); 372 } 373 } 374 375 bool RLZTracker::SendFinancialPing(const std::string& brand, 376 const string16& lang, 377 const string16& referral) { 378 return ::SendFinancialPing(brand, lang, referral); 379 } 380 381 void RLZTracker::Observe(int type, 382 const content::NotificationSource& source, 383 const content::NotificationDetails& details) { 384 switch (type) { 385 case chrome::NOTIFICATION_OMNIBOX_OPENED_URL: 386 RecordFirstSearch(CHROME_OMNIBOX); 387 registrar_.Remove(this, chrome::NOTIFICATION_OMNIBOX_OPENED_URL, 388 content::NotificationService::AllSources()); 389 break; 390 case content::NOTIFICATION_NAV_ENTRY_PENDING: { 391 const NavigationEntry* entry = 392 content::Details<content::NavigationEntry>(details).ptr(); 393 if (entry != NULL && 394 ((entry->GetTransitionType() & 395 content::PAGE_TRANSITION_HOME_PAGE) != 0)) { 396 RecordFirstSearch(CHROME_HOME_PAGE); 397 registrar_.Remove(this, content::NOTIFICATION_NAV_ENTRY_PENDING, 398 content::NotificationService::AllSources()); 399 } 400 break; 401 } 402 default: 403 NOTREACHED(); 404 break; 405 } 406 } 407 408 // static 409 bool RLZTracker::RecordProductEvent(rlz_lib::Product product, 410 rlz_lib::AccessPoint point, 411 rlz_lib::Event event_id) { 412 return GetInstance()->RecordProductEventImpl(product, point, event_id); 413 } 414 415 bool RLZTracker::RecordProductEventImpl(rlz_lib::Product product, 416 rlz_lib::AccessPoint point, 417 rlz_lib::Event event_id) { 418 // Make sure we don't access disk outside of the I/O thread. 419 // In such case we repost the task on the right thread and return error. 420 if (ScheduleRecordProductEvent(product, point, event_id)) 421 return true; 422 423 bool ret = rlz_lib::RecordProductEvent(product, point, event_id); 424 425 // If chrome has been reactivated, record the event for this brand as well. 426 if (!reactivation_brand_.empty()) { 427 rlz_lib::SupplementaryBranding branding(reactivation_brand_.c_str()); 428 ret &= rlz_lib::RecordProductEvent(product, point, event_id); 429 } 430 431 return ret; 432 } 433 434 bool RLZTracker::ScheduleRecordProductEvent(rlz_lib::Product product, 435 rlz_lib::AccessPoint point, 436 rlz_lib::Event event_id) { 437 if (!BrowserThread::CurrentlyOn(BrowserThread::UI)) 438 return false; 439 440 BrowserThread::GetBlockingPool()->PostSequencedWorkerTaskWithShutdownBehavior( 441 worker_pool_token_, 442 FROM_HERE, 443 base::Bind(base::IgnoreResult(&RLZTracker::RecordProductEvent), 444 product, point, event_id), 445 base::SequencedWorkerPool::SKIP_ON_SHUTDOWN); 446 447 return true; 448 } 449 450 void RLZTracker::RecordFirstSearch(rlz_lib::AccessPoint point) { 451 // Make sure we don't access disk outside of the I/O thread. 452 // In such case we repost the task on the right thread and return error. 453 if (ScheduleRecordFirstSearch(point)) 454 return; 455 456 bool* record_used = point == CHROME_OMNIBOX ? 457 &omnibox_used_ : &homepage_used_; 458 459 // Try to record event now, else set the flag to try later when we 460 // attempt the ping. 461 if (!RecordProductEvent(rlz_lib::CHROME, point, rlz_lib::FIRST_SEARCH)) 462 *record_used = true; 463 else if (send_ping_immediately_ && point == CHROME_OMNIBOX) 464 ScheduleDelayedInit(base::TimeDelta()); 465 } 466 467 bool RLZTracker::ScheduleRecordFirstSearch(rlz_lib::AccessPoint point) { 468 if (!BrowserThread::CurrentlyOn(BrowserThread::UI)) 469 return false; 470 BrowserThread::GetBlockingPool()->PostSequencedWorkerTaskWithShutdownBehavior( 471 worker_pool_token_, 472 FROM_HERE, 473 base::Bind(&RLZTracker::RecordFirstSearch, 474 base::Unretained(this), point), 475 base::SequencedWorkerPool::SKIP_ON_SHUTDOWN); 476 return true; 477 } 478 479 // static 480 std::string RLZTracker::GetAccessPointHttpHeader(rlz_lib::AccessPoint point) { 481 TRACE_EVENT0("RLZ", "RLZTracker::GetAccessPointHttpHeader"); 482 std::string extra_headers; 483 string16 rlz_string; 484 RLZTracker::GetAccessPointRlz(point, &rlz_string); 485 if (!rlz_string.empty()) { 486 net::HttpUtil::AppendHeaderIfMissing("X-Rlz-String", 487 UTF16ToUTF8(rlz_string), 488 &extra_headers); 489 } 490 491 return extra_headers; 492 } 493 494 // GetAccessPointRlz() caches RLZ strings for all access points. If we had 495 // a successful ping, then we update the cached value. 496 bool RLZTracker::GetAccessPointRlz(rlz_lib::AccessPoint point, 497 string16* rlz) { 498 TRACE_EVENT0("RLZ", "RLZTracker::GetAccessPointRlz"); 499 return GetInstance()->GetAccessPointRlzImpl(point, rlz); 500 } 501 502 // GetAccessPointRlz() caches RLZ strings for all access points. If we had 503 // a successful ping, then we update the cached value. 504 bool RLZTracker::GetAccessPointRlzImpl(rlz_lib::AccessPoint point, 505 string16* rlz) { 506 // If the RLZ string for the specified access point is already cached, 507 // simply return its value. 508 { 509 base::AutoLock lock(cache_lock_); 510 if (rlz_cache_.find(point) != rlz_cache_.end()) { 511 if (rlz) 512 *rlz = rlz_cache_[point]; 513 return true; 514 } 515 } 516 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 (ScheduleGetAccessPointRlz(point)) 520 return false; 521 522 char str_rlz[rlz_lib::kMaxRlzLength + 1]; 523 if (!rlz_lib::GetAccessPointRlz(point, str_rlz, rlz_lib::kMaxRlzLength)) 524 return false; 525 526 string16 rlz_local(ASCIIToUTF16(std::string(str_rlz))); 527 if (rlz) 528 *rlz = rlz_local; 529 530 base::AutoLock lock(cache_lock_); 531 rlz_cache_[point] = rlz_local; 532 return true; 533 } 534 535 bool RLZTracker::ScheduleGetAccessPointRlz(rlz_lib::AccessPoint point) { 536 if (!BrowserThread::CurrentlyOn(BrowserThread::UI)) 537 return false; 538 539 string16* not_used = NULL; 540 BrowserThread::GetBlockingPool()->PostSequencedWorkerTaskWithShutdownBehavior( 541 worker_pool_token_, 542 FROM_HERE, 543 base::Bind(base::IgnoreResult(&RLZTracker::GetAccessPointRlz), point, 544 not_used), 545 base::SequencedWorkerPool::SKIP_ON_SHUTDOWN); 546 return true; 547 } 548 549 #if defined(OS_CHROMEOS) 550 // static 551 void RLZTracker::ClearRlzState() { 552 GetInstance()->ClearRlzStateImpl(); 553 } 554 555 void RLZTracker::ClearRlzStateImpl() { 556 if (ScheduleClearRlzState()) 557 return; 558 rlz_lib::ClearAllProductEvents(rlz_lib::CHROME); 559 } 560 561 bool RLZTracker::ScheduleClearRlzState() { 562 if (!BrowserThread::CurrentlyOn(BrowserThread::UI)) 563 return false; 564 565 BrowserThread::GetBlockingPool()->PostSequencedWorkerTaskWithShutdownBehavior( 566 worker_pool_token_, 567 FROM_HERE, 568 base::Bind(&RLZTracker::ClearRlzStateImpl, 569 base::Unretained(this)), 570 base::SequencedWorkerPool::SKIP_ON_SHUTDOWN); 571 return true; 572 } 573 #endif 574 575 // static 576 void RLZTracker::CleanupRlz() { 577 GetInstance()->rlz_cache_.clear(); 578 GetInstance()->registrar_.RemoveAll(); 579 rlz_lib::SetURLRequestContext(NULL); 580 } 581 582 // static 583 void RLZTracker::EnableZeroDelayForTesting() { 584 GetInstance()->min_init_delay_ = base::TimeDelta(); 585 } 586