Home | History | Annotate | Download | only in rlz
      1 // Copyright (c) 2011 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 <process.h>
     12 #include <windows.h>
     13 
     14 #include <algorithm>
     15 
     16 #include "base/file_path.h"
     17 #include "base/message_loop.h"
     18 #include "base/path_service.h"
     19 #include "base/string_util.h"
     20 #include "base/synchronization/lock.h"
     21 #include "base/task.h"
     22 #include "base/threading/thread.h"
     23 #include "base/threading/thread_restrictions.h"
     24 #include "base/utf_string_conversions.h"
     25 #include "chrome/browser/browser_process.h"
     26 #include "chrome/browser/profiles/profile.h"
     27 #include "chrome/browser/profiles/profile_manager.h"
     28 #include "chrome/browser/search_engines/template_url.h"
     29 #include "chrome/browser/search_engines/template_url_model.h"
     30 #include "chrome/common/chrome_paths.h"
     31 #include "chrome/common/env_vars.h"
     32 #include "chrome/installer/util/google_update_settings.h"
     33 #include "content/browser/browser_thread.h"
     34 #include "content/common/notification_registrar.h"
     35 #include "content/common/notification_service.h"
     36 
     37 namespace {
     38 
     39 // The maximum length of an access points RLZ in wide chars.
     40 const DWORD kMaxRlzLength = 64;
     41 
     42 enum {
     43   ACCESS_VALUES_STALE,      // Possibly new values available.
     44   ACCESS_VALUES_FRESH       // The cached values are current.
     45 };
     46 
     47 // Tracks if we have tried and succeeded sending the ping. This helps us
     48 // decide if we need to refresh the some cached strings.
     49 volatile int access_values_state = ACCESS_VALUES_STALE;
     50 base::Lock rlz_lock;
     51 
     52 bool SendFinancialPing(const std::wstring& brand, const std::wstring& lang,
     53                        const std::wstring& referral, bool exclude_id) {
     54   rlz_lib::AccessPoint points[] = {rlz_lib::CHROME_OMNIBOX,
     55                                    rlz_lib::CHROME_HOME_PAGE,
     56                                    rlz_lib::NO_ACCESS_POINT};
     57   std::string brand_ascii(WideToASCII(brand));
     58   std::string lang_ascii(WideToASCII(lang));
     59   std::string referral_ascii(WideToASCII(referral));
     60 
     61   return rlz_lib::SendFinancialPing(rlz_lib::CHROME, points, "chrome",
     62                                     brand_ascii.c_str(), referral_ascii.c_str(),
     63                                     lang_ascii.c_str(), exclude_id, NULL, true);
     64 }
     65 
     66 // This class leverages the AutocompleteEditModel notification to know when
     67 // the user first interacted with the omnibox and set a global accordingly.
     68 class OmniBoxUsageObserver : public NotificationObserver {
     69  public:
     70   OmniBoxUsageObserver(bool first_run, bool send_ping_immediately)
     71     : first_run_(first_run),
     72       send_ping_immediately_(send_ping_immediately) {
     73     registrar_.Add(this, NotificationType::OMNIBOX_OPENED_URL,
     74                    NotificationService::AllSources());
     75     // If instant is enabled we'll start searching as soon as the user starts
     76     // typing in the omnibox (which triggers INSTANT_CONTROLLER_UPDATED).
     77     registrar_.Add(this, NotificationType::INSTANT_CONTROLLER_UPDATED,
     78                    NotificationService::AllSources());
     79     omnibox_used_ = false;
     80     DCHECK(!instance_);
     81     instance_ = this;
     82   }
     83 
     84   virtual void Observe(NotificationType type,
     85                        const NotificationSource& source,
     86                        const NotificationDetails& details);
     87 
     88   static bool used() {
     89     return omnibox_used_;
     90   }
     91 
     92   // Deletes the single instance of OmniBoxUsageObserver.
     93   static void DeleteInstance() {
     94     delete instance_;
     95   }
     96 
     97  private:
     98   // Dtor is private so the object cannot be created on the stack.
     99   ~OmniBoxUsageObserver() {
    100     instance_ = NULL;
    101   }
    102 
    103   static bool omnibox_used_;
    104 
    105   // There should only be one instance created at a time, and instance_ points
    106   // to that instance.
    107   // NOTE: this is only non-null for the amount of time it is needed. Once the
    108   // instance_ is no longer needed (or Chrome is exiting), this is null.
    109   static OmniBoxUsageObserver* instance_;
    110 
    111   NotificationRegistrar registrar_;
    112   bool first_run_;
    113   bool send_ping_immediately_;
    114 };
    115 
    116 bool OmniBoxUsageObserver::omnibox_used_ = false;
    117 OmniBoxUsageObserver* OmniBoxUsageObserver::instance_ = NULL;
    118 
    119 // This task is run in the file thread, so to not block it for a long time
    120 // we use a throwaway thread to do the blocking url request.
    121 class DailyPingTask : public Task {
    122  public:
    123   virtual ~DailyPingTask() {
    124   }
    125   virtual void Run() {
    126     // We use a transient thread because we have no guarantees about
    127     // how long the RLZ lib can block us.
    128     _beginthread(PingNow, 0, NULL);
    129   }
    130 
    131  private:
    132   // Causes a ping to the server using WinInet.
    133   static void _cdecl PingNow(void*) {
    134     // Needs to be evaluated. See http://crbug.com/62328.
    135     base::ThreadRestrictions::ScopedAllowIO allow_io;
    136 
    137     std::wstring lang;
    138     GoogleUpdateSettings::GetLanguage(&lang);
    139     if (lang.empty())
    140       lang = L"en";
    141     std::wstring brand;
    142     GoogleUpdateSettings::GetBrand(&brand);
    143     std::wstring referral;
    144     GoogleUpdateSettings::GetReferral(&referral);
    145     if (SendFinancialPing(brand, lang, referral, is_organic(brand))) {
    146       base::AutoLock lock(rlz_lock);
    147       access_values_state = ACCESS_VALUES_STALE;
    148       GoogleUpdateSettings::ClearReferral();
    149     }
    150   }
    151 
    152   // Organic brands all start with GG, such as GGCM.
    153   static bool is_organic(const std::wstring& brand) {
    154     return (brand.size() < 2) ? false : (brand.substr(0, 2) == L"GG");
    155   }
    156 };
    157 
    158 // Performs late RLZ initialization and RLZ event recording for chrome.
    159 // This task needs to run on the UI thread.
    160 class DelayedInitTask : public Task {
    161  public:
    162   explicit DelayedInitTask(bool first_run)
    163       : first_run_(first_run) {
    164   }
    165   virtual ~DelayedInitTask() {
    166   }
    167   virtual void Run() {
    168     // For non-interactive tests we don't do the rest of the initialization
    169     // because sometimes the very act of loading the dll causes QEMU to crash.
    170     if (::GetEnvironmentVariableW(ASCIIToWide(env_vars::kHeadless).c_str(),
    171                                   NULL, 0)) {
    172       return;
    173     }
    174     // For organic brandcodes do not use rlz at all. Empty brandcode usually
    175     // means a chromium install. This is ok.
    176     std::wstring brand;
    177     if (!GoogleUpdateSettings::GetBrand(&brand) || brand.empty() ||
    178         GoogleUpdateSettings::IsOrganic(brand))
    179       return;
    180 
    181     // Do the initial event recording if is the first run or if we have an
    182     // empty rlz which means we haven't got a chance to do it.
    183     std::wstring omnibox_rlz;
    184     RLZTracker::GetAccessPointRlz(rlz_lib::CHROME_OMNIBOX, &omnibox_rlz);
    185 
    186     if ((first_run_ || omnibox_rlz.empty()) && !already_ran_) {
    187       already_ran_ = true;
    188 
    189       // Record the installation of chrome.
    190       RLZTracker::RecordProductEvent(rlz_lib::CHROME,
    191                                      rlz_lib::CHROME_OMNIBOX,
    192                                      rlz_lib::INSTALL);
    193       RLZTracker::RecordProductEvent(rlz_lib::CHROME,
    194                                      rlz_lib::CHROME_HOME_PAGE,
    195                                      rlz_lib::INSTALL);
    196       // Record if google is the initial search provider.
    197       if (IsGoogleDefaultSearch()) {
    198         RLZTracker::RecordProductEvent(rlz_lib::CHROME,
    199                                        rlz_lib::CHROME_OMNIBOX,
    200                                        rlz_lib::SET_TO_GOOGLE);
    201       }
    202     }
    203     // Record first user interaction with the omnibox. We call this all the
    204     // time but the rlz lib should ingore all but the first one.
    205     if (OmniBoxUsageObserver::used()) {
    206       RLZTracker::RecordProductEvent(rlz_lib::CHROME,
    207                                      rlz_lib::CHROME_OMNIBOX,
    208                                      rlz_lib::FIRST_SEARCH);
    209     }
    210     // Schedule the daily RLZ ping.
    211     MessageLoop::current()->PostTask(FROM_HERE, new DailyPingTask());
    212   }
    213 
    214  private:
    215   bool IsGoogleDefaultSearch() {
    216     if (!g_browser_process)
    217       return false;
    218     FilePath user_data_dir;
    219     if (!PathService::Get(chrome::DIR_USER_DATA, &user_data_dir))
    220       return false;
    221     ProfileManager* profile_manager = g_browser_process->profile_manager();
    222     Profile* profile = profile_manager->GetDefaultProfile(user_data_dir);
    223     if (!profile)
    224       return false;
    225     const TemplateURL* url_template =
    226         profile->GetTemplateURLModel()->GetDefaultSearchProvider();
    227     if (!url_template)
    228       return false;
    229     const TemplateURLRef* urlref = url_template->url();
    230     if (!urlref)
    231       return false;
    232     return urlref->HasGoogleBaseURLs();
    233   }
    234 
    235   // Flag that remembers if the delayed task already ran or not.  This is
    236   // needed only in the first_run case, since we don't want to record the
    237   // set-to-google event more than once.  We need to worry about this event
    238   // (and not the others) because it is not a stateful RLZ event.
    239   static bool already_ran_;
    240 
    241   bool first_run_;
    242   DISALLOW_IMPLICIT_CONSTRUCTORS(DelayedInitTask);
    243 };
    244 
    245 bool DelayedInitTask::already_ran_ = false;
    246 
    247 void OmniBoxUsageObserver::Observe(NotificationType type,
    248                                    const NotificationSource& source,
    249                                    const NotificationDetails& details) {
    250   // Needs to be evaluated. See http://crbug.com/62328.
    251   base::ThreadRestrictions::ScopedAllowIO allow_io;
    252 
    253   // Try to record event now, else set the flag to try later when we
    254   // attempt the ping.
    255   if (!RLZTracker::RecordProductEvent(rlz_lib::CHROME,
    256                                       rlz_lib::CHROME_OMNIBOX,
    257                                       rlz_lib::FIRST_SEARCH))
    258     omnibox_used_ = true;
    259   else if (send_ping_immediately_) {
    260     BrowserThread::PostTask(
    261         BrowserThread::FILE, FROM_HERE, new DelayedInitTask(first_run_));
    262   }
    263 
    264   delete this;
    265 }
    266 
    267 }  // namespace
    268 
    269 bool RLZTracker::InitRlzDelayed(bool first_run, int delay) {
    270   // A negative delay means that a financial ping should be sent immediately
    271   // after a first search is recorded, without waiting for the next restart
    272   // of chrome.  However, we only want this behaviour on first run.
    273   bool send_ping_immediately = false;
    274   if (delay < 0) {
    275     send_ping_immediately = true;
    276     delay = -delay;
    277   }
    278 
    279   // Maximum and minimum delay we would allow to be set through master
    280   // preferences. Somewhat arbitrary, may need to be adjusted in future.
    281   const int kMaxDelay = 200 * 1000;
    282   const int kMinDelay = 20 * 1000;
    283 
    284   delay *= 1000;
    285   delay = (delay < kMinDelay) ? kMinDelay : delay;
    286   delay = (delay > kMaxDelay) ? kMaxDelay : delay;
    287 
    288   if (!OmniBoxUsageObserver::used())
    289     new OmniBoxUsageObserver(first_run, send_ping_immediately);
    290 
    291   // Schedule the delayed init items.
    292   BrowserThread::PostDelayedTask(
    293       BrowserThread::FILE, FROM_HERE, new DelayedInitTask(first_run), delay);
    294   return true;
    295 }
    296 
    297 bool RLZTracker::RecordProductEvent(rlz_lib::Product product,
    298                                     rlz_lib::AccessPoint point,
    299                                     rlz_lib::Event event_id) {
    300   return rlz_lib::RecordProductEvent(product, point, event_id);
    301 }
    302 
    303 bool RLZTracker::ClearAllProductEvents(rlz_lib::Product product) {
    304   return rlz_lib::ClearAllProductEvents(product);
    305 }
    306 
    307 // We implement caching of the answer of get_access_point() if the request
    308 // is for CHROME_OMNIBOX. If we had a successful ping, then we update the
    309 // cached value.
    310 
    311 bool RLZTracker::GetAccessPointRlz(rlz_lib::AccessPoint point,
    312                                    std::wstring* rlz) {
    313   static std::wstring cached_ommibox_rlz;
    314   if (rlz_lib::CHROME_OMNIBOX == point) {
    315     base::AutoLock lock(rlz_lock);
    316     if (access_values_state == ACCESS_VALUES_FRESH) {
    317       *rlz = cached_ommibox_rlz;
    318       return true;
    319     }
    320   }
    321 
    322   // Make sure we don't access disk outside of the file context.
    323   // In such case we repost the task on the right thread and return error.
    324   if (!BrowserThread::CurrentlyOn(BrowserThread::FILE)) {
    325     // Caching of access points is now only implemented for the CHROME_OMNIBOX.
    326     // Thus it is not possible to call this function on another thread for
    327     // other access points until proper caching for these has been implemented
    328     // and the code that calls this function can handle synchronous fetching
    329     // of the access point.
    330     DCHECK_EQ(rlz_lib::CHROME_OMNIBOX, point);
    331 
    332     BrowserThread::PostTask(
    333         BrowserThread::FILE, FROM_HERE,
    334         NewRunnableFunction(&RLZTracker::GetAccessPointRlz,
    335                             point, &cached_ommibox_rlz));
    336       rlz->erase();
    337       return false;
    338   }
    339 
    340   char str_rlz[kMaxRlzLength + 1];
    341   if (!rlz_lib::GetAccessPointRlz(point, str_rlz, rlz_lib::kMaxRlzLength, NULL))
    342     return false;
    343   *rlz = ASCIIToWide(std::string(str_rlz));
    344   if (rlz_lib::CHROME_OMNIBOX == point) {
    345     base::AutoLock lock(rlz_lock);
    346     cached_ommibox_rlz.assign(*rlz);
    347     access_values_state = ACCESS_VALUES_FRESH;
    348   }
    349   return true;
    350 }
    351 
    352 // static
    353 void RLZTracker::CleanupRlz() {
    354   OmniBoxUsageObserver::DeleteInstance();
    355 }
    356