// 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();
}