| // Copyright (c) 2011 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 <process.h> |
| #include <windows.h> |
| |
| #include <algorithm> |
| |
| #include "base/file_path.h" |
| #include "base/message_loop.h" |
| #include "base/path_service.h" |
| #include "base/string_util.h" |
| #include "base/synchronization/lock.h" |
| #include "base/task.h" |
| #include "base/threading/thread.h" |
| #include "base/threading/thread_restrictions.h" |
| #include "base/utf_string_conversions.h" |
| #include "chrome/browser/browser_process.h" |
| #include "chrome/browser/profiles/profile.h" |
| #include "chrome/browser/profiles/profile_manager.h" |
| #include "chrome/browser/search_engines/template_url.h" |
| #include "chrome/browser/search_engines/template_url_model.h" |
| #include "chrome/common/chrome_paths.h" |
| #include "chrome/common/env_vars.h" |
| #include "chrome/installer/util/google_update_settings.h" |
| #include "content/browser/browser_thread.h" |
| #include "content/common/notification_registrar.h" |
| #include "content/common/notification_service.h" |
| |
| namespace { |
| |
| // The maximum length of an access points RLZ in wide chars. |
| const DWORD kMaxRlzLength = 64; |
| |
| enum { |
| ACCESS_VALUES_STALE, // Possibly new values available. |
| ACCESS_VALUES_FRESH // The cached values are current. |
| }; |
| |
| // Tracks if we have tried and succeeded sending the ping. This helps us |
| // decide if we need to refresh the some cached strings. |
| volatile int access_values_state = ACCESS_VALUES_STALE; |
| base::Lock rlz_lock; |
| |
| bool SendFinancialPing(const std::wstring& brand, const std::wstring& lang, |
| const std::wstring& referral, bool exclude_id) { |
| rlz_lib::AccessPoint points[] = {rlz_lib::CHROME_OMNIBOX, |
| rlz_lib::CHROME_HOME_PAGE, |
| rlz_lib::NO_ACCESS_POINT}; |
| std::string brand_ascii(WideToASCII(brand)); |
| std::string lang_ascii(WideToASCII(lang)); |
| std::string referral_ascii(WideToASCII(referral)); |
| |
| return rlz_lib::SendFinancialPing(rlz_lib::CHROME, points, "chrome", |
| brand_ascii.c_str(), referral_ascii.c_str(), |
| lang_ascii.c_str(), exclude_id, NULL, true); |
| } |
| |
| // This class leverages the AutocompleteEditModel notification to know when |
| // the user first interacted with the omnibox and set a global accordingly. |
| class OmniBoxUsageObserver : public NotificationObserver { |
| public: |
| OmniBoxUsageObserver(bool first_run, bool send_ping_immediately) |
| : first_run_(first_run), |
| send_ping_immediately_(send_ping_immediately) { |
| registrar_.Add(this, NotificationType::OMNIBOX_OPENED_URL, |
| 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, NotificationType::INSTANT_CONTROLLER_UPDATED, |
| NotificationService::AllSources()); |
| omnibox_used_ = false; |
| DCHECK(!instance_); |
| instance_ = this; |
| } |
| |
| virtual void Observe(NotificationType type, |
| const NotificationSource& source, |
| const NotificationDetails& details); |
| |
| static bool used() { |
| return omnibox_used_; |
| } |
| |
| // Deletes the single instance of OmniBoxUsageObserver. |
| static void DeleteInstance() { |
| delete instance_; |
| } |
| |
| private: |
| // Dtor is private so the object cannot be created on the stack. |
| ~OmniBoxUsageObserver() { |
| instance_ = NULL; |
| } |
| |
| static bool omnibox_used_; |
| |
| // There should only be one instance created at a time, and instance_ points |
| // to that instance. |
| // NOTE: this is only non-null for the amount of time it is needed. Once the |
| // instance_ is no longer needed (or Chrome is exiting), this is null. |
| static OmniBoxUsageObserver* instance_; |
| |
| NotificationRegistrar registrar_; |
| bool first_run_; |
| bool send_ping_immediately_; |
| }; |
| |
| bool OmniBoxUsageObserver::omnibox_used_ = false; |
| OmniBoxUsageObserver* OmniBoxUsageObserver::instance_ = NULL; |
| |
| // This task is run in the file thread, so to not block it for a long time |
| // we use a throwaway thread to do the blocking url request. |
| class DailyPingTask : public Task { |
| public: |
| virtual ~DailyPingTask() { |
| } |
| virtual void Run() { |
| // We use a transient thread because we have no guarantees about |
| // how long the RLZ lib can block us. |
| _beginthread(PingNow, 0, NULL); |
| } |
| |
| private: |
| // Causes a ping to the server using WinInet. |
| static void _cdecl PingNow(void*) { |
| // Needs to be evaluated. See http://crbug.com/62328. |
| base::ThreadRestrictions::ScopedAllowIO allow_io; |
| |
| std::wstring lang; |
| GoogleUpdateSettings::GetLanguage(&lang); |
| if (lang.empty()) |
| lang = L"en"; |
| std::wstring brand; |
| GoogleUpdateSettings::GetBrand(&brand); |
| std::wstring referral; |
| GoogleUpdateSettings::GetReferral(&referral); |
| if (SendFinancialPing(brand, lang, referral, is_organic(brand))) { |
| base::AutoLock lock(rlz_lock); |
| access_values_state = ACCESS_VALUES_STALE; |
| GoogleUpdateSettings::ClearReferral(); |
| } |
| } |
| |
| // Organic brands all start with GG, such as GGCM. |
| static bool is_organic(const std::wstring& brand) { |
| return (brand.size() < 2) ? false : (brand.substr(0, 2) == L"GG"); |
| } |
| }; |
| |
| // Performs late RLZ initialization and RLZ event recording for chrome. |
| // This task needs to run on the UI thread. |
| class DelayedInitTask : public Task { |
| public: |
| explicit DelayedInitTask(bool first_run) |
| : first_run_(first_run) { |
| } |
| virtual ~DelayedInitTask() { |
| } |
| virtual void Run() { |
| // For non-interactive tests we don't do the rest of the initialization |
| // because sometimes the very act of loading the dll causes QEMU to crash. |
| if (::GetEnvironmentVariableW(ASCIIToWide(env_vars::kHeadless).c_str(), |
| NULL, 0)) { |
| return; |
| } |
| // For organic brandcodes do not use rlz at all. Empty brandcode usually |
| // means a chromium install. This is ok. |
| std::wstring brand; |
| if (!GoogleUpdateSettings::GetBrand(&brand) || brand.empty() || |
| GoogleUpdateSettings::IsOrganic(brand)) |
| return; |
| |
| // 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. |
| std::wstring omnibox_rlz; |
| RLZTracker::GetAccessPointRlz(rlz_lib::CHROME_OMNIBOX, &omnibox_rlz); |
| |
| if ((first_run_ || omnibox_rlz.empty()) && !already_ran_) { |
| already_ran_ = true; |
| |
| // Record the installation of chrome. |
| RLZTracker::RecordProductEvent(rlz_lib::CHROME, |
| rlz_lib::CHROME_OMNIBOX, |
| rlz_lib::INSTALL); |
| RLZTracker::RecordProductEvent(rlz_lib::CHROME, |
| rlz_lib::CHROME_HOME_PAGE, |
| rlz_lib::INSTALL); |
| // Record if google is the initial search provider. |
| if (IsGoogleDefaultSearch()) { |
| RLZTracker::RecordProductEvent(rlz_lib::CHROME, |
| rlz_lib::CHROME_OMNIBOX, |
| 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 (OmniBoxUsageObserver::used()) { |
| RLZTracker::RecordProductEvent(rlz_lib::CHROME, |
| rlz_lib::CHROME_OMNIBOX, |
| rlz_lib::FIRST_SEARCH); |
| } |
| // Schedule the daily RLZ ping. |
| MessageLoop::current()->PostTask(FROM_HERE, new DailyPingTask()); |
| } |
| |
| private: |
| bool IsGoogleDefaultSearch() { |
| if (!g_browser_process) |
| return false; |
| FilePath user_data_dir; |
| if (!PathService::Get(chrome::DIR_USER_DATA, &user_data_dir)) |
| return false; |
| ProfileManager* profile_manager = g_browser_process->profile_manager(); |
| Profile* profile = profile_manager->GetDefaultProfile(user_data_dir); |
| if (!profile) |
| return false; |
| const TemplateURL* url_template = |
| profile->GetTemplateURLModel()->GetDefaultSearchProvider(); |
| if (!url_template) |
| return false; |
| const TemplateURLRef* urlref = url_template->url(); |
| if (!urlref) |
| return false; |
| return urlref->HasGoogleBaseURLs(); |
| } |
| |
| // Flag that remembers if the delayed task already ran or not. This is |
| // needed only in the first_run case, since we don't want to record the |
| // set-to-google event more than once. We need to worry about this event |
| // (and not the others) because it is not a stateful RLZ event. |
| static bool already_ran_; |
| |
| bool first_run_; |
| DISALLOW_IMPLICIT_CONSTRUCTORS(DelayedInitTask); |
| }; |
| |
| bool DelayedInitTask::already_ran_ = false; |
| |
| void OmniBoxUsageObserver::Observe(NotificationType type, |
| const NotificationSource& source, |
| const NotificationDetails& details) { |
| // Needs to be evaluated. See http://crbug.com/62328. |
| base::ThreadRestrictions::ScopedAllowIO allow_io; |
| |
| // Try to record event now, else set the flag to try later when we |
| // attempt the ping. |
| if (!RLZTracker::RecordProductEvent(rlz_lib::CHROME, |
| rlz_lib::CHROME_OMNIBOX, |
| rlz_lib::FIRST_SEARCH)) |
| omnibox_used_ = true; |
| else if (send_ping_immediately_) { |
| BrowserThread::PostTask( |
| BrowserThread::FILE, FROM_HERE, new DelayedInitTask(first_run_)); |
| } |
| |
| delete this; |
| } |
| |
| } // namespace |
| |
| bool RLZTracker::InitRlzDelayed(bool first_run, int delay) { |
| // 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. |
| bool 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; |
| |
| if (!OmniBoxUsageObserver::used()) |
| new OmniBoxUsageObserver(first_run, send_ping_immediately); |
| |
| // Schedule the delayed init items. |
| BrowserThread::PostDelayedTask( |
| BrowserThread::FILE, FROM_HERE, new DelayedInitTask(first_run), delay); |
| return true; |
| } |
| |
| bool RLZTracker::RecordProductEvent(rlz_lib::Product product, |
| rlz_lib::AccessPoint point, |
| rlz_lib::Event event_id) { |
| return rlz_lib::RecordProductEvent(product, point, event_id); |
| } |
| |
| bool RLZTracker::ClearAllProductEvents(rlz_lib::Product product) { |
| return rlz_lib::ClearAllProductEvents(product); |
| } |
| |
| // We implement caching of the answer of get_access_point() if the request |
| // is for CHROME_OMNIBOX. If we had a successful ping, then we update the |
| // cached value. |
| |
| bool RLZTracker::GetAccessPointRlz(rlz_lib::AccessPoint point, |
| std::wstring* rlz) { |
| static std::wstring cached_ommibox_rlz; |
| if (rlz_lib::CHROME_OMNIBOX == point) { |
| base::AutoLock lock(rlz_lock); |
| if (access_values_state == ACCESS_VALUES_FRESH) { |
| *rlz = cached_ommibox_rlz; |
| return true; |
| } |
| } |
| |
| // Make sure we don't access disk outside of the file context. |
| // In such case we repost the task on the right thread and return error. |
| if (!BrowserThread::CurrentlyOn(BrowserThread::FILE)) { |
| // Caching of access points is now only implemented for the CHROME_OMNIBOX. |
| // Thus it is not possible to call this function on another thread for |
| // other access points until proper caching for these has been implemented |
| // and the code that calls this function can handle synchronous fetching |
| // of the access point. |
| DCHECK_EQ(rlz_lib::CHROME_OMNIBOX, point); |
| |
| BrowserThread::PostTask( |
| BrowserThread::FILE, FROM_HERE, |
| NewRunnableFunction(&RLZTracker::GetAccessPointRlz, |
| point, &cached_ommibox_rlz)); |
| rlz->erase(); |
| return false; |
| } |
| |
| char str_rlz[kMaxRlzLength + 1]; |
| if (!rlz_lib::GetAccessPointRlz(point, str_rlz, rlz_lib::kMaxRlzLength, NULL)) |
| return false; |
| *rlz = ASCIIToWide(std::string(str_rlz)); |
| if (rlz_lib::CHROME_OMNIBOX == point) { |
| base::AutoLock lock(rlz_lock); |
| cached_ommibox_rlz.assign(*rlz); |
| access_values_state = ACCESS_VALUES_FRESH; |
| } |
| return true; |
| } |
| |
| // static |
| void RLZTracker::CleanupRlz() { |
| OmniBoxUsageObserver::DeleteInstance(); |
| } |