• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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 
SendFinancialPing(const std::wstring & brand,const std::wstring & lang,const std::wstring & referral,bool exclude_id)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:
OmniBoxUsageObserver(bool first_run,bool send_ping_immediately)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 
used()88   static bool used() {
89     return omnibox_used_;
90   }
91 
92   // Deletes the single instance of OmniBoxUsageObserver.
DeleteInstance()93   static void DeleteInstance() {
94     delete instance_;
95   }
96 
97  private:
98   // Dtor is private so the object cannot be created on the stack.
~OmniBoxUsageObserver()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:
~DailyPingTask()123   virtual ~DailyPingTask() {
124   }
Run()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.
PingNow(void *)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.
is_organic(const std::wstring & brand)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:
DelayedInitTask(bool first_run)162   explicit DelayedInitTask(bool first_run)
163       : first_run_(first_run) {
164   }
~DelayedInitTask()165   virtual ~DelayedInitTask() {
166   }
Run()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:
IsGoogleDefaultSearch()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 
Observe(NotificationType type,const NotificationSource & source,const NotificationDetails & details)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 
InitRlzDelayed(bool first_run,int delay)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 
RecordProductEvent(rlz_lib::Product product,rlz_lib::AccessPoint point,rlz_lib::Event event_id)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 
ClearAllProductEvents(rlz_lib::Product product)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 
GetAccessPointRlz(rlz_lib::AccessPoint point,std::wstring * rlz)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
CleanupRlz()353 void RLZTracker::CleanupRlz() {
354   OmniBoxUsageObserver::DeleteInstance();
355 }
356