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_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_entry.h" 34#include "content/public/browser/notification_service.h" 35#include "net/http/http_util.h" 36 37#if defined(OS_WIN) 38#include "chrome/installer/util/google_update_settings.h" 39#else 40namespace GoogleUpdateSettings { 41static bool GetLanguage(base::string16* language) { 42 // TODO(thakis): Implement. 43 NOTIMPLEMENTED(); 44 return false; 45} 46 47// The referral program is defunct and not used. No need to implement these 48// functions on non-Win platforms. 49static bool GetReferral(base::string16* referral) { 50 return true; 51} 52static bool ClearReferral() { 53 return true; 54} 55} // namespace GoogleUpdateSettings 56#endif 57 58using content::BrowserThread; 59using content::NavigationEntry; 60 61namespace { 62 63// Maximum and minimum delay for financial ping we would allow to be set through 64// master preferences. Somewhat arbitrary, may need to be adjusted in future. 65const base::TimeDelta kMaxInitDelay = base::TimeDelta::FromSeconds(200); 66const base::TimeDelta kMinInitDelay = base::TimeDelta::FromSeconds(20); 67 68bool IsBrandOrganic(const std::string& brand) { 69 return brand.empty() || google_brand::IsOrganic(brand); 70} 71 72void RecordProductEvents(bool first_run, 73 bool is_google_default_search, 74 bool is_google_homepage, 75 bool is_google_in_startpages, 76 bool already_ran, 77 bool omnibox_used, 78 bool homepage_used, 79 bool app_list_used) { 80 TRACE_EVENT0("RLZ", "RecordProductEvents"); 81 // Record the installation of chrome. We call this all the time but the rlz 82 // lib should ignore all but the first one. 83 rlz_lib::RecordProductEvent(rlz_lib::CHROME, 84 RLZTracker::ChromeOmnibox(), 85 rlz_lib::INSTALL); 86#if !defined(OS_IOS) 87 rlz_lib::RecordProductEvent(rlz_lib::CHROME, 88 RLZTracker::ChromeHomePage(), 89 rlz_lib::INSTALL); 90 rlz_lib::RecordProductEvent(rlz_lib::CHROME, 91 RLZTracker::ChromeAppList(), 92 rlz_lib::INSTALL); 93#endif // !defined(OS_IOS) 94 95 if (!already_ran) { 96 // Do the initial event recording if is the first run or if we have an 97 // empty rlz which means we haven't got a chance to do it. 98 char omnibox_rlz[rlz_lib::kMaxRlzLength + 1]; 99 if (!rlz_lib::GetAccessPointRlz(RLZTracker::ChromeOmnibox(), omnibox_rlz, 100 rlz_lib::kMaxRlzLength)) { 101 omnibox_rlz[0] = 0; 102 } 103 104 // Record if google is the initial search provider and/or home page. 105 if ((first_run || omnibox_rlz[0] == 0) && is_google_default_search) { 106 rlz_lib::RecordProductEvent(rlz_lib::CHROME, 107 RLZTracker::ChromeOmnibox(), 108 rlz_lib::SET_TO_GOOGLE); 109 } 110 111#if !defined(OS_IOS) 112 char homepage_rlz[rlz_lib::kMaxRlzLength + 1]; 113 if (!rlz_lib::GetAccessPointRlz(RLZTracker::ChromeHomePage(), homepage_rlz, 114 rlz_lib::kMaxRlzLength)) { 115 homepage_rlz[0] = 0; 116 } 117 118 if ((first_run || homepage_rlz[0] == 0) && 119 (is_google_homepage || is_google_in_startpages)) { 120 rlz_lib::RecordProductEvent(rlz_lib::CHROME, 121 RLZTracker::ChromeHomePage(), 122 rlz_lib::SET_TO_GOOGLE); 123 } 124 125 char app_list_rlz[rlz_lib::kMaxRlzLength + 1]; 126 if (!rlz_lib::GetAccessPointRlz(RLZTracker::ChromeAppList(), app_list_rlz, 127 rlz_lib::kMaxRlzLength)) { 128 app_list_rlz[0] = 0; 129 } 130 131 // Record if google is the initial search provider and/or home page. 132 if ((first_run || app_list_rlz[0] == 0) && is_google_default_search) { 133 rlz_lib::RecordProductEvent(rlz_lib::CHROME, 134 RLZTracker::ChromeAppList(), 135 rlz_lib::SET_TO_GOOGLE); 136 } 137#endif // !defined(OS_IOS) 138 } 139 140 // Record first user interaction with the omnibox. We call this all the 141 // time but the rlz lib should ingore all but the first one. 142 if (omnibox_used) { 143 rlz_lib::RecordProductEvent(rlz_lib::CHROME, 144 RLZTracker::ChromeOmnibox(), 145 rlz_lib::FIRST_SEARCH); 146 } 147 148#if !defined(OS_IOS) 149 // Record first user interaction with the home page. We call this all the 150 // time but the rlz lib should ingore all but the first one. 151 if (homepage_used || is_google_in_startpages) { 152 rlz_lib::RecordProductEvent(rlz_lib::CHROME, 153 RLZTracker::ChromeHomePage(), 154 rlz_lib::FIRST_SEARCH); 155 } 156 157 // Record first user interaction with the app list. We call this all the 158 // time but the rlz lib should ingore all but the first one. 159 if (app_list_used) { 160 rlz_lib::RecordProductEvent(rlz_lib::CHROME, 161 RLZTracker::ChromeAppList(), 162 rlz_lib::FIRST_SEARCH); 163 } 164#endif // !defined(OS_IOS) 165} 166 167bool SendFinancialPing(const std::string& brand, 168 const base::string16& lang, 169 const base::string16& referral) { 170 rlz_lib::AccessPoint points[] = {RLZTracker::ChromeOmnibox(), 171#if !defined(OS_IOS) 172 RLZTracker::ChromeHomePage(), 173 RLZTracker::ChromeAppList(), 174#endif 175 rlz_lib::NO_ACCESS_POINT}; 176 std::string lang_ascii(base::UTF16ToASCII(lang)); 177 std::string referral_ascii(base::UTF16ToASCII(referral)); 178 std::string product_signature; 179#if defined(OS_CHROMEOS) 180 product_signature = "chromeos"; 181#else 182 product_signature = "chrome"; 183#endif 184 return rlz_lib::SendFinancialPing(rlz_lib::CHROME, points, 185 product_signature.c_str(), 186 brand.c_str(), referral_ascii.c_str(), 187 lang_ascii.c_str(), false, true); 188} 189 190} // namespace 191 192RLZTracker* RLZTracker::tracker_ = NULL; 193 194// static 195RLZTracker* RLZTracker::GetInstance() { 196 return tracker_ ? tracker_ : Singleton<RLZTracker>::get(); 197} 198 199RLZTracker::RLZTracker() 200 : first_run_(false), 201 send_ping_immediately_(false), 202 is_google_default_search_(false), 203 is_google_homepage_(false), 204 is_google_in_startpages_(false), 205 worker_pool_token_(BrowserThread::GetBlockingPool()->GetSequenceToken()), 206 already_ran_(false), 207 omnibox_used_(false), 208 homepage_used_(false), 209 app_list_used_(false), 210 min_init_delay_(kMinInitDelay) { 211} 212 213RLZTracker::~RLZTracker() { 214} 215 216// static 217bool RLZTracker::InitRlzDelayed(bool first_run, 218 bool send_ping_immediately, 219 base::TimeDelta delay, 220 bool is_google_default_search, 221 bool is_google_homepage, 222 bool is_google_in_startpages) { 223 return GetInstance()->Init(first_run, send_ping_immediately, delay, 224 is_google_default_search, is_google_homepage, 225 is_google_in_startpages); 226} 227 228// static 229bool RLZTracker::InitRlzFromProfileDelayed(Profile* profile, 230 bool first_run, 231 bool send_ping_immediately, 232 base::TimeDelta delay) { 233 bool is_google_default_search = false; 234 TemplateURLService* template_url_service = 235 TemplateURLServiceFactory::GetForProfile(profile); 236 if (template_url_service) { 237 const TemplateURL* url_template = 238 template_url_service->GetDefaultSearchProvider(); 239 is_google_default_search = 240 url_template && url_template->url_ref().HasGoogleBaseURLs( 241 template_url_service->search_terms_data()); 242 } 243 244 PrefService* pref_service = profile->GetPrefs(); 245 bool is_google_homepage = google_util::IsGoogleHomePageUrl( 246 GURL(pref_service->GetString(prefs::kHomePage))); 247 248 bool is_google_in_startpages = false; 249#if !defined(OS_IOS) 250 // iOS does not have a notion of startpages. 251 SessionStartupPref session_startup_prefs = 252 StartupBrowserCreator::GetSessionStartupPref( 253 *CommandLine::ForCurrentProcess(), profile); 254 if (session_startup_prefs.type == SessionStartupPref::URLS) { 255 is_google_in_startpages = 256 std::count_if(session_startup_prefs.urls.begin(), 257 session_startup_prefs.urls.end(), 258 google_util::IsGoogleHomePageUrl) > 0; 259 } 260#endif 261 262 if (!InitRlzDelayed(first_run, send_ping_immediately, delay, 263 is_google_default_search, is_google_homepage, 264 is_google_in_startpages)) { 265 return false; 266 } 267 268#if !defined(OS_IOS) 269 // Prime the RLZ cache for the home page access point so that its avaiable 270 // for the startup page if needed (i.e., when the startup page is set to 271 // the home page). 272 GetAccessPointRlz(ChromeHomePage(), NULL); 273#endif // !defined(OS_IOS) 274 275 return true; 276} 277 278bool RLZTracker::Init(bool first_run, 279 bool send_ping_immediately, 280 base::TimeDelta delay, 281 bool is_google_default_search, 282 bool is_google_homepage, 283 bool is_google_in_startpages) { 284 first_run_ = first_run; 285 is_google_default_search_ = is_google_default_search; 286 is_google_homepage_ = is_google_homepage; 287 is_google_in_startpages_ = is_google_in_startpages; 288 send_ping_immediately_ = send_ping_immediately; 289 290 // Enable zero delays for testing. 291 if (CommandLine::ForCurrentProcess()->HasSwitch(::switches::kTestType)) 292 EnableZeroDelayForTesting(); 293 294 delay = std::min(kMaxInitDelay, std::max(min_init_delay_, delay)); 295 296 if (google_brand::GetBrand(&brand_) && !IsBrandOrganic(brand_)) { 297 // Register for notifications from the omnibox so that we can record when 298 // the user performs a first search. 299 registrar_.Add(this, chrome::NOTIFICATION_OMNIBOX_OPENED_URL, 300 content::NotificationService::AllSources()); 301 302#if !defined(OS_IOS) 303 // Register for notifications from navigations, to see if the user has used 304 // the home page. 305 registrar_.Add(this, content::NOTIFICATION_NAV_ENTRY_PENDING, 306 content::NotificationService::AllSources()); 307#endif // !defined(OS_IOS) 308 } 309 google_brand::GetReactivationBrand(&reactivation_brand_); 310 311 net::URLRequestContextGetter* context_getter = 312 g_browser_process->system_request_context(); 313 314 // Could be NULL; don't run if so. RLZ will try again next restart. 315 if (context_getter) { 316 rlz_lib::SetURLRequestContext(context_getter); 317 ScheduleDelayedInit(delay); 318 } 319 320 return true; 321} 322 323void RLZTracker::ScheduleDelayedInit(base::TimeDelta delay) { 324 // The RLZTracker is a singleton object that outlives any runnable tasks 325 // that will be queued up. 326 BrowserThread::GetBlockingPool()->PostDelayedSequencedWorkerTask( 327 worker_pool_token_, 328 FROM_HERE, 329 base::Bind(&RLZTracker::DelayedInit, base::Unretained(this)), 330 delay); 331} 332 333void RLZTracker::DelayedInit() { 334 bool schedule_ping = false; 335 336 // For organic brandcodes do not use rlz at all. Empty brandcode usually 337 // means a chromium install. This is ok. 338 if (!IsBrandOrganic(brand_)) { 339 RecordProductEvents(first_run_, is_google_default_search_, 340 is_google_homepage_, is_google_in_startpages_, 341 already_ran_, omnibox_used_, homepage_used_, 342 app_list_used_); 343 schedule_ping = true; 344 } 345 346 // If chrome has been reactivated, record the events for this brand 347 // as well. 348 if (!IsBrandOrganic(reactivation_brand_)) { 349 rlz_lib::SupplementaryBranding branding(reactivation_brand_.c_str()); 350 RecordProductEvents(first_run_, is_google_default_search_, 351 is_google_homepage_, is_google_in_startpages_, 352 already_ran_, omnibox_used_, homepage_used_, 353 app_list_used_); 354 schedule_ping = true; 355 } 356 357 already_ran_ = true; 358 359 if (schedule_ping) 360 ScheduleFinancialPing(); 361} 362 363void RLZTracker::ScheduleFinancialPing() { 364 BrowserThread::GetBlockingPool()->PostSequencedWorkerTaskWithShutdownBehavior( 365 worker_pool_token_, 366 FROM_HERE, 367 base::Bind(&RLZTracker::PingNowImpl, base::Unretained(this)), 368 base::SequencedWorkerPool::SKIP_ON_SHUTDOWN); 369} 370 371void RLZTracker::PingNowImpl() { 372 TRACE_EVENT0("RLZ", "RLZTracker::PingNowImpl"); 373 base::string16 lang; 374 GoogleUpdateSettings::GetLanguage(&lang); 375 if (lang.empty()) 376 lang = base::ASCIIToUTF16("en"); 377 base::string16 referral; 378 GoogleUpdateSettings::GetReferral(&referral); 379 380 if (!IsBrandOrganic(brand_) && SendFinancialPing(brand_, lang, referral)) { 381 GoogleUpdateSettings::ClearReferral(); 382 383 { 384 base::AutoLock lock(cache_lock_); 385 rlz_cache_.clear(); 386 } 387 388 // Prime the RLZ cache for the access points we are interested in. 389 GetAccessPointRlz(RLZTracker::ChromeOmnibox(), NULL); 390#if !defined(OS_IOS) 391 GetAccessPointRlz(RLZTracker::ChromeHomePage(), NULL); 392 GetAccessPointRlz(RLZTracker::ChromeAppList(), NULL); 393#endif // !defined(OS_IOS) 394 } 395 396 if (!IsBrandOrganic(reactivation_brand_)) { 397 rlz_lib::SupplementaryBranding branding(reactivation_brand_.c_str()); 398 SendFinancialPing(reactivation_brand_, lang, referral); 399 } 400} 401 402bool RLZTracker::SendFinancialPing(const std::string& brand, 403 const base::string16& lang, 404 const base::string16& referral) { 405 return ::SendFinancialPing(brand, lang, referral); 406} 407 408void RLZTracker::Observe(int type, 409 const content::NotificationSource& source, 410 const content::NotificationDetails& details) { 411 switch (type) { 412 case chrome::NOTIFICATION_OMNIBOX_OPENED_URL: 413 // In M-36, we made NOTIFICATION_OMNIBOX_OPENED_URL fire more often than 414 // it did previously. The RLZ folks want RLZ's "first search" detection 415 // to remain as unaffected as possible by this change. This test is 416 // there to keep the old behavior. 417 if (!content::Details<OmniboxLog>(details).ptr()->is_popup_open) 418 break; 419 RecordFirstSearch(ChromeOmnibox()); 420 registrar_.Remove(this, chrome::NOTIFICATION_OMNIBOX_OPENED_URL, 421 content::NotificationService::AllSources()); 422 break; 423#if !defined(OS_IOS) 424 case content::NOTIFICATION_NAV_ENTRY_PENDING: { 425 const NavigationEntry* entry = 426 content::Details<content::NavigationEntry>(details).ptr(); 427 if (entry != NULL && 428 ((entry->GetTransitionType() & 429 ui::PAGE_TRANSITION_HOME_PAGE) != 0)) { 430 RecordFirstSearch(ChromeHomePage()); 431 registrar_.Remove(this, content::NOTIFICATION_NAV_ENTRY_PENDING, 432 content::NotificationService::AllSources()); 433 } 434 break; 435 } 436#endif // !defined(OS_IOS) 437 default: 438 NOTREACHED(); 439 break; 440 } 441} 442 443// static 444bool RLZTracker::RecordProductEvent(rlz_lib::Product product, 445 rlz_lib::AccessPoint point, 446 rlz_lib::Event event_id) { 447 return GetInstance()->RecordProductEventImpl(product, point, event_id); 448} 449 450bool RLZTracker::RecordProductEventImpl(rlz_lib::Product product, 451 rlz_lib::AccessPoint point, 452 rlz_lib::Event event_id) { 453 // Make sure we don't access disk outside of the I/O thread. 454 // In such case we repost the task on the right thread and return error. 455 if (ScheduleRecordProductEvent(product, point, event_id)) 456 return true; 457 458 bool ret = rlz_lib::RecordProductEvent(product, point, event_id); 459 460 // If chrome has been reactivated, record the event for this brand as well. 461 if (!reactivation_brand_.empty()) { 462 rlz_lib::SupplementaryBranding branding(reactivation_brand_.c_str()); 463 ret &= rlz_lib::RecordProductEvent(product, point, event_id); 464 } 465 466 return ret; 467} 468 469bool RLZTracker::ScheduleRecordProductEvent(rlz_lib::Product product, 470 rlz_lib::AccessPoint point, 471 rlz_lib::Event event_id) { 472 if (!BrowserThread::CurrentlyOn(BrowserThread::UI)) 473 return false; 474 475 BrowserThread::GetBlockingPool()->PostSequencedWorkerTaskWithShutdownBehavior( 476 worker_pool_token_, 477 FROM_HERE, 478 base::Bind(base::IgnoreResult(&RLZTracker::RecordProductEvent), 479 product, point, event_id), 480 base::SequencedWorkerPool::SKIP_ON_SHUTDOWN); 481 482 return true; 483} 484 485void RLZTracker::RecordFirstSearch(rlz_lib::AccessPoint point) { 486 // Make sure we don't access disk outside of the I/O thread. 487 // In such case we repost the task on the right thread and return error. 488 if (ScheduleRecordFirstSearch(point)) 489 return; 490 491 bool* record_used = GetAccessPointRecord(point); 492 493 // Try to record event now, else set the flag to try later when we 494 // attempt the ping. 495 if (!RecordProductEvent(rlz_lib::CHROME, point, rlz_lib::FIRST_SEARCH)) 496 *record_used = true; 497 else if (send_ping_immediately_ && point == ChromeOmnibox()) 498 ScheduleDelayedInit(base::TimeDelta()); 499} 500 501bool RLZTracker::ScheduleRecordFirstSearch(rlz_lib::AccessPoint point) { 502 if (!BrowserThread::CurrentlyOn(BrowserThread::UI)) 503 return false; 504 BrowserThread::GetBlockingPool()->PostSequencedWorkerTaskWithShutdownBehavior( 505 worker_pool_token_, 506 FROM_HERE, 507 base::Bind(&RLZTracker::RecordFirstSearch, 508 base::Unretained(this), point), 509 base::SequencedWorkerPool::SKIP_ON_SHUTDOWN); 510 return true; 511} 512 513bool* RLZTracker::GetAccessPointRecord(rlz_lib::AccessPoint point) { 514 if (point == ChromeOmnibox()) 515 return &omnibox_used_; 516#if !defined(OS_IOS) 517 if (point == ChromeHomePage()) 518 return &homepage_used_; 519 if (point == ChromeAppList()) 520 return &app_list_used_; 521#endif // !defined(OS_IOS) 522 NOTREACHED(); 523 return NULL; 524} 525 526// static 527std::string RLZTracker::GetAccessPointHttpHeader(rlz_lib::AccessPoint point) { 528 TRACE_EVENT0("RLZ", "RLZTracker::GetAccessPointHttpHeader"); 529 std::string extra_headers; 530 base::string16 rlz_string; 531 RLZTracker::GetAccessPointRlz(point, &rlz_string); 532 if (!rlz_string.empty()) { 533 net::HttpUtil::AppendHeaderIfMissing("X-Rlz-String", 534 base::UTF16ToUTF8(rlz_string), 535 &extra_headers); 536 } 537 538 return extra_headers; 539} 540 541// GetAccessPointRlz() caches RLZ strings for all access points. If we had 542// a successful ping, then we update the cached value. 543bool RLZTracker::GetAccessPointRlz(rlz_lib::AccessPoint point, 544 base::string16* rlz) { 545 TRACE_EVENT0("RLZ", "RLZTracker::GetAccessPointRlz"); 546 return GetInstance()->GetAccessPointRlzImpl(point, rlz); 547} 548 549// GetAccessPointRlz() caches RLZ strings for all access points. If we had 550// a successful ping, then we update the cached value. 551bool RLZTracker::GetAccessPointRlzImpl(rlz_lib::AccessPoint point, 552 base::string16* rlz) { 553 // If the RLZ string for the specified access point is already cached, 554 // simply return its value. 555 { 556 base::AutoLock lock(cache_lock_); 557 if (rlz_cache_.find(point) != rlz_cache_.end()) { 558 if (rlz) 559 *rlz = rlz_cache_[point]; 560 return true; 561 } 562 } 563 564 // Make sure we don't access disk outside of the I/O thread. 565 // In such case we repost the task on the right thread and return error. 566 if (ScheduleGetAccessPointRlz(point)) 567 return false; 568 569 char str_rlz[rlz_lib::kMaxRlzLength + 1]; 570 if (!rlz_lib::GetAccessPointRlz(point, str_rlz, rlz_lib::kMaxRlzLength)) 571 return false; 572 573 base::string16 rlz_local(base::ASCIIToUTF16(std::string(str_rlz))); 574 if (rlz) 575 *rlz = rlz_local; 576 577 base::AutoLock lock(cache_lock_); 578 rlz_cache_[point] = rlz_local; 579 return true; 580} 581 582bool RLZTracker::ScheduleGetAccessPointRlz(rlz_lib::AccessPoint point) { 583 if (!BrowserThread::CurrentlyOn(BrowserThread::UI)) 584 return false; 585 586 base::string16* not_used = NULL; 587 BrowserThread::GetBlockingPool()->PostSequencedWorkerTaskWithShutdownBehavior( 588 worker_pool_token_, 589 FROM_HERE, 590 base::Bind(base::IgnoreResult(&RLZTracker::GetAccessPointRlz), point, 591 not_used), 592 base::SequencedWorkerPool::SKIP_ON_SHUTDOWN); 593 return true; 594} 595 596#if defined(OS_CHROMEOS) 597// static 598void RLZTracker::ClearRlzState() { 599 GetInstance()->ClearRlzStateImpl(); 600} 601 602void RLZTracker::ClearRlzStateImpl() { 603 if (ScheduleClearRlzState()) 604 return; 605 rlz_lib::ClearAllProductEvents(rlz_lib::CHROME); 606} 607 608bool RLZTracker::ScheduleClearRlzState() { 609 if (!BrowserThread::CurrentlyOn(BrowserThread::UI)) 610 return false; 611 612 BrowserThread::GetBlockingPool()->PostSequencedWorkerTaskWithShutdownBehavior( 613 worker_pool_token_, 614 FROM_HERE, 615 base::Bind(&RLZTracker::ClearRlzStateImpl, 616 base::Unretained(this)), 617 base::SequencedWorkerPool::SKIP_ON_SHUTDOWN); 618 return true; 619} 620#endif 621 622// static 623void RLZTracker::CleanupRlz() { 624 GetInstance()->rlz_cache_.clear(); 625 GetInstance()->registrar_.RemoveAll(); 626 rlz_lib::SetURLRequestContext(NULL); 627} 628 629// static 630void RLZTracker::EnableZeroDelayForTesting() { 631 GetInstance()->min_init_delay_ = base::TimeDelta(); 632} 633 634#if !defined(OS_IOS) 635// static 636void RLZTracker::RecordAppListSearch() { 637 GetInstance()->RecordFirstSearch(RLZTracker::ChromeAppList()); 638} 639#endif 640