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 37namespace { 38 39// The maximum length of an access points RLZ in wide chars. 40const DWORD kMaxRlzLength = 64; 41 42enum { 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. 49volatile int access_values_state = ACCESS_VALUES_STALE; 50base::Lock rlz_lock; 51 52bool 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. 68class 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 116bool OmniBoxUsageObserver::omnibox_used_ = false; 117OmniBoxUsageObserver* 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. 121class 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. 160class 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 245bool DelayedInitTask::already_ran_ = false; 246 247void 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 269bool 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 297bool 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 303bool 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 311bool 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 353void RLZTracker::CleanupRlz() { 354 OmniBoxUsageObserver::DeleteInstance(); 355} 356