// Copyright (c) 2012 The Chromium Authors. All rights reserved. // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. // // This code glues the RLZ library DLL with Chrome. It allows Chrome to work // with or without the DLL being present. If the DLL is not present the // functions do nothing and just return false. #include "chrome/browser/rlz/rlz.h" #include <algorithm> #include "base/bind.h" #include "base/message_loop.h" #include "base/string_util.h" #include "base/threading/thread_restrictions.h" #include "base/utf_string_conversions.h" #include "chrome/browser/browser_process.h" #include "chrome/browser/google/google_util.h" #include "chrome/browser/search_engines/template_url.h" #include "chrome/browser/search_engines/template_url_service.h" #include "chrome/browser/search_engines/template_url_service_factory.h" #include "chrome/common/chrome_notification_types.h" #include "content/public/browser/browser_thread.h" #include "content/public/browser/navigation_entry.h" #include "content/public/browser/notification_service.h" #if defined(OS_WIN) #include "chrome/installer/util/google_update_settings.h" #else namespace GoogleUpdateSettings { static bool GetLanguage(string16* language) { // TODO(thakis): Implement. NOTIMPLEMENTED(); return false; } // The referral program is defunct and not used. No need to implement these // functions on mac. static bool GetReferral(string16* referral) { return true; } static bool ClearReferral() { return true; } } // namespace GoogleUpdateSettings #endif using content::BrowserThread; using content::NavigationEntry; namespace { bool IsBrandOrganic(const std::string& brand) { return brand.empty() || google_util::IsOrganic(brand); } void RecordProductEvents(bool first_run, bool google_default_search, bool google_default_homepage, bool already_ran, bool omnibox_used, bool homepage_used) { // Record the installation of chrome. We call this all the time but the rlz // lib should ingore all but the first one. rlz_lib::RecordProductEvent(rlz_lib::CHROME, rlz_lib::CHROME_OMNIBOX, rlz_lib::INSTALL); rlz_lib::RecordProductEvent(rlz_lib::CHROME, rlz_lib::CHROME_HOME_PAGE, rlz_lib::INSTALL); if (!already_ran) { // Do the initial event recording if is the first run or if we have an // empty rlz which means we haven't got a chance to do it. char omnibox_rlz[rlz_lib::kMaxRlzLength + 1]; if (!rlz_lib::GetAccessPointRlz(rlz_lib::CHROME_OMNIBOX, omnibox_rlz, rlz_lib::kMaxRlzLength)) { omnibox_rlz[0] = 0; } // Record if google is the initial search provider and/or home page. if ((first_run || omnibox_rlz[0] == 0) && google_default_search) { rlz_lib::RecordProductEvent(rlz_lib::CHROME, rlz_lib::CHROME_OMNIBOX, rlz_lib::SET_TO_GOOGLE); } char homepage_rlz[rlz_lib::kMaxRlzLength + 1]; if (!rlz_lib::GetAccessPointRlz(rlz_lib::CHROME_HOME_PAGE, homepage_rlz, rlz_lib::kMaxRlzLength)) { homepage_rlz[0] = 0; } if ((first_run || homepage_rlz[0] == 0) && google_default_homepage) { rlz_lib::RecordProductEvent(rlz_lib::CHROME, rlz_lib::CHROME_HOME_PAGE, rlz_lib::SET_TO_GOOGLE); } } // Record first user interaction with the omnibox. We call this all the // time but the rlz lib should ingore all but the first one. if (omnibox_used) { rlz_lib::RecordProductEvent(rlz_lib::CHROME, rlz_lib::CHROME_OMNIBOX, rlz_lib::FIRST_SEARCH); } // Record first user interaction with the home page. We call this all the // time but the rlz lib should ingore all but the first one. if (homepage_used) { rlz_lib::RecordProductEvent(rlz_lib::CHROME, rlz_lib::CHROME_HOME_PAGE, rlz_lib::FIRST_SEARCH); } } bool SendFinancialPing(const std::string& brand, const string16& lang, const string16& referral) { rlz_lib::AccessPoint points[] = {rlz_lib::CHROME_OMNIBOX, rlz_lib::CHROME_HOME_PAGE, rlz_lib::NO_ACCESS_POINT}; std::string lang_ascii(UTF16ToASCII(lang)); std::string referral_ascii(UTF16ToASCII(referral)); return rlz_lib::SendFinancialPing(rlz_lib::CHROME, points, "chrome", brand.c_str(), referral_ascii.c_str(), lang_ascii.c_str(), false, true); } } // namespace RLZTracker* RLZTracker::tracker_ = NULL; // static RLZTracker* RLZTracker::GetInstance() { return tracker_ ? tracker_ : Singleton<RLZTracker>::get(); } RLZTracker::RLZTracker() : first_run_(false), send_ping_immediately_(false), google_default_search_(false), google_default_homepage_(false), already_ran_(false), omnibox_used_(false), homepage_used_(false) { } RLZTracker::~RLZTracker() { } bool RLZTracker::InitRlzDelayed(bool first_run, int delay, bool google_default_search, bool google_default_homepage) { return GetInstance()->Init(first_run, delay, google_default_search, google_default_homepage); } bool RLZTracker::Init(bool first_run, int delay, bool google_default_search, bool google_default_homepage) { first_run_ = first_run; google_default_search_ = google_default_search; google_default_homepage_ = google_default_homepage; // A negative delay means that a financial ping should be sent immediately // after a first search is recorded, without waiting for the next restart // of chrome. However, we only want this behaviour on first run. send_ping_immediately_ = false; if (delay < 0) { send_ping_immediately_ = true; delay = -delay; } // Maximum and minimum delay we would allow to be set through master // preferences. Somewhat arbitrary, may need to be adjusted in future. const int kMaxDelay = 200 * 1000; const int kMinDelay = 20 * 1000; delay *= 1000; delay = (delay < kMinDelay) ? kMinDelay : delay; delay = (delay > kMaxDelay) ? kMaxDelay : delay; // Register for notifications from the omnibox so that we can record when // the user performs a first search. registrar_.Add(this, chrome::NOTIFICATION_OMNIBOX_OPENED_URL, content::NotificationService::AllSources()); // If instant is enabled we'll start searching as soon as the user starts // typing in the omnibox (which triggers INSTANT_CONTROLLER_UPDATED). registrar_.Add(this, chrome::NOTIFICATION_INSTANT_CONTROLLER_UPDATED, content::NotificationService::AllSources()); // Register for notifications from navigations, to see if the user has used // the home page. registrar_.Add(this, content::NOTIFICATION_NAV_ENTRY_PENDING, content::NotificationService::AllSources()); rlz_lib::SetURLRequestContext(g_browser_process->system_request_context()); ScheduleDelayedInit(delay); return true; } void RLZTracker::ScheduleDelayedInit(int delay) { // The RLZTracker is a singleton object that outlives any runnable tasks // that will be queued up. // TODO: Move to SequencedWorkerPool once http://crbug.com/119657 is fixed. BrowserThread::PostDelayedTask( BrowserThread::FILE, FROM_HERE, base::Bind(&RLZTracker::DelayedInit, base::Unretained(this)), base::TimeDelta::FromMilliseconds(delay)); } void RLZTracker::DelayedInit() { worker_pool_token_ = BrowserThread::GetBlockingPool()->GetSequenceToken(); bool schedule_ping = false; // For organic brandcodes do not use rlz at all. Empty brandcode usually // means a chromium install. This is ok. std::string brand; if (google_util::GetBrand(&brand) && !IsBrandOrganic(brand)) { RecordProductEvents(first_run_, google_default_search_, google_default_homepage_, already_ran_, omnibox_used_, homepage_used_); schedule_ping = true; } // If chrome has been reactivated, record the events for this brand // as well. std::string reactivation_brand; if (google_util::GetReactivationBrand(&reactivation_brand) && !IsBrandOrganic(reactivation_brand)) { rlz_lib::SupplementaryBranding branding(reactivation_brand.c_str()); RecordProductEvents(first_run_, google_default_search_, google_default_homepage_, already_ran_, omnibox_used_, homepage_used_); schedule_ping = true; } already_ran_ = true; if (schedule_ping) ScheduleFinancialPing(); } void RLZTracker::ScheduleFinancialPing() { BrowserThread::GetBlockingPool()->PostSequencedWorkerTask( worker_pool_token_, FROM_HERE, base::Bind(&RLZTracker::PingNowImpl, base::Unretained(this))); } void RLZTracker::PingNowImpl() { string16 lang; GoogleUpdateSettings::GetLanguage(&lang); if (lang.empty()) lang = ASCIIToUTF16("en"); string16 referral; GoogleUpdateSettings::GetReferral(&referral); std::string brand; if (google_util::GetBrand(&brand) && !IsBrandOrganic(brand) && SendFinancialPing(brand, lang, referral)) { GoogleUpdateSettings::ClearReferral(); { base::AutoLock lock(cache_lock_); rlz_cache_.clear(); } // Prime the RLZ cache for the access points we are interested in. GetAccessPointRlz(rlz_lib::CHROME_OMNIBOX, NULL); GetAccessPointRlz(rlz_lib::CHROME_HOME_PAGE, NULL); } std::string reactivation_brand; if (google_util::GetReactivationBrand(&reactivation_brand) && !IsBrandOrganic(reactivation_brand)) { rlz_lib::SupplementaryBranding branding(reactivation_brand.c_str()); SendFinancialPing(reactivation_brand, lang, referral); } } bool RLZTracker::SendFinancialPing(const std::string& brand, const string16& lang, const string16& referral) { return ::SendFinancialPing(brand, lang, referral); } void RLZTracker::Observe(int type, const content::NotificationSource& source, const content::NotificationDetails& details) { // Needs to be evaluated. See http://crbug.com/62328. base::ThreadRestrictions::ScopedAllowIO allow_io; rlz_lib::AccessPoint point; bool* record_used = NULL; bool call_record = false; switch (type) { case chrome::NOTIFICATION_OMNIBOX_OPENED_URL: case chrome::NOTIFICATION_INSTANT_CONTROLLER_UPDATED: point = rlz_lib::CHROME_OMNIBOX; record_used = &omnibox_used_; call_record = true; registrar_.Remove(this, chrome::NOTIFICATION_OMNIBOX_OPENED_URL, content::NotificationService::AllSources()); registrar_.Remove(this, chrome::NOTIFICATION_INSTANT_CONTROLLER_UPDATED, content::NotificationService::AllSources()); break; case content::NOTIFICATION_NAV_ENTRY_PENDING: { const NavigationEntry* entry = content::Details<content::NavigationEntry>(details).ptr(); if (entry != NULL && ((entry->GetTransitionType() & content::PAGE_TRANSITION_HOME_PAGE) != 0)) { point = rlz_lib::CHROME_HOME_PAGE; record_used = &homepage_used_; call_record = true; registrar_.Remove(this, content::NOTIFICATION_NAV_ENTRY_PENDING, content::NotificationService::AllSources()); } break; } default: NOTREACHED(); break; } if (call_record) { // Try to record event now, else set the flag to try later when we // attempt the ping. if (!RecordProductEvent(rlz_lib::CHROME, point, rlz_lib::FIRST_SEARCH)) *record_used = true; else if (send_ping_immediately_ && point == rlz_lib::CHROME_OMNIBOX) { ScheduleDelayedInit(0); } } } bool RLZTracker::RecordProductEvent(rlz_lib::Product product, rlz_lib::AccessPoint point, rlz_lib::Event event_id) { bool ret = rlz_lib::RecordProductEvent(product, point, event_id); // If chrome has been reactivated, record the event for this brand as well. std::string reactivation_brand; if (google_util::GetReactivationBrand(&reactivation_brand)) { rlz_lib::SupplementaryBranding branding(reactivation_brand.c_str()); ret &= rlz_lib::RecordProductEvent(product, point, event_id); } return ret; } // GetAccessPointRlz() caches RLZ strings for all access points. If we had // a successful ping, then we update the cached value. bool RLZTracker::GetAccessPointRlz(rlz_lib::AccessPoint point, string16* rlz) { return GetInstance()->GetAccessPointRlzImpl(point, rlz); } // GetAccessPointRlz() caches RLZ strings for all access points. If we had // a successful ping, then we update the cached value. bool RLZTracker::GetAccessPointRlzImpl(rlz_lib::AccessPoint point, string16* rlz) { // If the RLZ string for the specified access point is already cached, // simply return its value. { base::AutoLock lock(cache_lock_); if (rlz_cache_.find(point) != rlz_cache_.end()) { if (rlz) *rlz = rlz_cache_[point]; return true; } } // Make sure we don't access disk outside of the I/O thread. // In such case we repost the task on the right thread and return error. if (ScheduleGetAccessPointRlz(point)) return false; char str_rlz[rlz_lib::kMaxRlzLength + 1]; if (!rlz_lib::GetAccessPointRlz(point, str_rlz, rlz_lib::kMaxRlzLength)) return false; string16 rlz_local(ASCIIToUTF16(std::string(str_rlz))); if (rlz) *rlz = rlz_local; base::AutoLock lock(cache_lock_); rlz_cache_[point] = rlz_local; return true; } bool RLZTracker::ScheduleGetAccessPointRlz(rlz_lib::AccessPoint point) { if (!BrowserThread::CurrentlyOn(BrowserThread::UI)) return false; string16* not_used = NULL; BrowserThread::GetBlockingPool()->PostSequencedWorkerTask( worker_pool_token_, FROM_HERE, base::Bind(base::IgnoreResult(&RLZTracker::GetAccessPointRlz), point, not_used)); return true; } // static void RLZTracker::CleanupRlz() { GetInstance()->rlz_cache_.clear(); GetInstance()->registrar_.RemoveAll(); }