普通文本  |  640行  |  22.54 KB

// Copyright (c) 2012 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 <algorithm>

#include "base/bind.h"
#include "base/command_line.h"
#include "base/debug/trace_event.h"
#include "base/message_loop/message_loop.h"
#include "base/prefs/pref_service.h"
#include "base/strings/string_util.h"
#include "base/strings/utf_string_conversions.h"
#include "chrome/browser/browser_process.h"
#include "chrome/browser/chrome_notification_types.h"
#include "chrome/browser/google/google_brand.h"
#include "chrome/browser/omnibox/omnibox_log.h"
#include "chrome/browser/prefs/session_startup_pref.h"
#include "chrome/browser/search_engines/template_url.h"
#include "chrome/browser/search_engines/template_url_service.h"
#include "chrome/browser/search_engines/template_url_service_factory.h"
#include "chrome/browser/ui/startup/startup_browser_creator.h"
#include "chrome/common/chrome_switches.h"
#include "chrome/common/pref_names.h"
#include "components/google/core/browser/google_util.h"
#include "content/public/browser/browser_thread.h"
#include "content/public/browser/navigation_entry.h"
#include "content/public/browser/notification_service.h"
#include "net/http/http_util.h"

#if defined(OS_WIN)
#include "chrome/installer/util/google_update_settings.h"
#else
namespace GoogleUpdateSettings {
static bool GetLanguage(base::string16* language) {
  // TODO(thakis): Implement.
  NOTIMPLEMENTED();
  return false;
}

// The referral program is defunct and not used. No need to implement these
// functions on non-Win platforms.
static bool GetReferral(base::string16* referral) {
  return true;
}
static bool ClearReferral() {
  return true;
}
}  // namespace GoogleUpdateSettings
#endif

using content::BrowserThread;
using content::NavigationEntry;

namespace {

// Maximum and minimum delay for financial ping we would allow to be set through
// master preferences. Somewhat arbitrary, may need to be adjusted in future.
const base::TimeDelta kMaxInitDelay = base::TimeDelta::FromSeconds(200);
const base::TimeDelta kMinInitDelay = base::TimeDelta::FromSeconds(20);

bool IsBrandOrganic(const std::string& brand) {
  return brand.empty() || google_brand::IsOrganic(brand);
}

void RecordProductEvents(bool first_run,
                         bool is_google_default_search,
                         bool is_google_homepage,
                         bool is_google_in_startpages,
                         bool already_ran,
                         bool omnibox_used,
                         bool homepage_used,
                         bool app_list_used) {
  TRACE_EVENT0("RLZ", "RecordProductEvents");
  // Record the installation of chrome. We call this all the time but the rlz
  // lib should ignore all but the first one.
  rlz_lib::RecordProductEvent(rlz_lib::CHROME,
                              RLZTracker::ChromeOmnibox(),
                              rlz_lib::INSTALL);
#if !defined(OS_IOS)
  rlz_lib::RecordProductEvent(rlz_lib::CHROME,
                              RLZTracker::ChromeHomePage(),
                              rlz_lib::INSTALL);
  rlz_lib::RecordProductEvent(rlz_lib::CHROME,
                              RLZTracker::ChromeAppList(),
                              rlz_lib::INSTALL);
#endif  // !defined(OS_IOS)

  if (!already_ran) {
    // 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.
    char omnibox_rlz[rlz_lib::kMaxRlzLength + 1];
    if (!rlz_lib::GetAccessPointRlz(RLZTracker::ChromeOmnibox(), omnibox_rlz,
                                    rlz_lib::kMaxRlzLength)) {
      omnibox_rlz[0] = 0;
    }

    // Record if google is the initial search provider and/or home page.
    if ((first_run || omnibox_rlz[0] == 0) && is_google_default_search) {
      rlz_lib::RecordProductEvent(rlz_lib::CHROME,
                                  RLZTracker::ChromeOmnibox(),
                                  rlz_lib::SET_TO_GOOGLE);
    }

#if !defined(OS_IOS)
    char homepage_rlz[rlz_lib::kMaxRlzLength + 1];
    if (!rlz_lib::GetAccessPointRlz(RLZTracker::ChromeHomePage(), homepage_rlz,
                                    rlz_lib::kMaxRlzLength)) {
      homepage_rlz[0] = 0;
    }

    if ((first_run || homepage_rlz[0] == 0) &&
        (is_google_homepage || is_google_in_startpages)) {
      rlz_lib::RecordProductEvent(rlz_lib::CHROME,
                                  RLZTracker::ChromeHomePage(),
                                  rlz_lib::SET_TO_GOOGLE);
    }

    char app_list_rlz[rlz_lib::kMaxRlzLength + 1];
    if (!rlz_lib::GetAccessPointRlz(RLZTracker::ChromeAppList(), app_list_rlz,
                                    rlz_lib::kMaxRlzLength)) {
      app_list_rlz[0] = 0;
    }

    // Record if google is the initial search provider and/or home page.
    if ((first_run || app_list_rlz[0] == 0) && is_google_default_search) {
      rlz_lib::RecordProductEvent(rlz_lib::CHROME,
                                  RLZTracker::ChromeAppList(),
                                  rlz_lib::SET_TO_GOOGLE);
    }
#endif  // !defined(OS_IOS)
  }

  // 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 (omnibox_used) {
    rlz_lib::RecordProductEvent(rlz_lib::CHROME,
                                RLZTracker::ChromeOmnibox(),
                                rlz_lib::FIRST_SEARCH);
  }

#if !defined(OS_IOS)
  // Record first user interaction with the home page. We call this all the
  // time but the rlz lib should ingore all but the first one.
  if (homepage_used || is_google_in_startpages) {
    rlz_lib::RecordProductEvent(rlz_lib::CHROME,
                                RLZTracker::ChromeHomePage(),
                                rlz_lib::FIRST_SEARCH);
  }

  // Record first user interaction with the app list. We call this all the
  // time but the rlz lib should ingore all but the first one.
  if (app_list_used) {
    rlz_lib::RecordProductEvent(rlz_lib::CHROME,
                                RLZTracker::ChromeAppList(),
                                rlz_lib::FIRST_SEARCH);
  }
#endif  // !defined(OS_IOS)
}

bool SendFinancialPing(const std::string& brand,
                       const base::string16& lang,
                       const base::string16& referral) {
  rlz_lib::AccessPoint points[] = {RLZTracker::ChromeOmnibox(),
#if !defined(OS_IOS)
                                   RLZTracker::ChromeHomePage(),
                                   RLZTracker::ChromeAppList(),
#endif
                                   rlz_lib::NO_ACCESS_POINT};
  std::string lang_ascii(base::UTF16ToASCII(lang));
  std::string referral_ascii(base::UTF16ToASCII(referral));
  std::string product_signature;
#if defined(OS_CHROMEOS)
  product_signature = "chromeos";
#else
  product_signature = "chrome";
#endif
  return rlz_lib::SendFinancialPing(rlz_lib::CHROME, points,
                                    product_signature.c_str(),
                                    brand.c_str(), referral_ascii.c_str(),
                                    lang_ascii.c_str(), false, true);
}

}  // namespace

RLZTracker* RLZTracker::tracker_ = NULL;

// static
RLZTracker* RLZTracker::GetInstance() {
  return tracker_ ? tracker_ : Singleton<RLZTracker>::get();
}

RLZTracker::RLZTracker()
    : first_run_(false),
      send_ping_immediately_(false),
      is_google_default_search_(false),
      is_google_homepage_(false),
      is_google_in_startpages_(false),
      worker_pool_token_(BrowserThread::GetBlockingPool()->GetSequenceToken()),
      already_ran_(false),
      omnibox_used_(false),
      homepage_used_(false),
      app_list_used_(false),
      min_init_delay_(kMinInitDelay) {
}

RLZTracker::~RLZTracker() {
}

// static
bool RLZTracker::InitRlzDelayed(bool first_run,
                                bool send_ping_immediately,
                                base::TimeDelta delay,
                                bool is_google_default_search,
                                bool is_google_homepage,
                                bool is_google_in_startpages) {
  return GetInstance()->Init(first_run, send_ping_immediately, delay,
                             is_google_default_search, is_google_homepage,
                             is_google_in_startpages);
}

// static
bool RLZTracker::InitRlzFromProfileDelayed(Profile* profile,
                                           bool first_run,
                                           bool send_ping_immediately,
                                           base::TimeDelta delay) {
  bool is_google_default_search = false;
  TemplateURLService* template_url_service =
      TemplateURLServiceFactory::GetForProfile(profile);
  if (template_url_service) {
    const TemplateURL* url_template =
        template_url_service->GetDefaultSearchProvider();
    is_google_default_search =
        url_template && url_template->url_ref().HasGoogleBaseURLs(
            template_url_service->search_terms_data());
  }

  PrefService* pref_service = profile->GetPrefs();
  bool is_google_homepage = google_util::IsGoogleHomePageUrl(
      GURL(pref_service->GetString(prefs::kHomePage)));

  bool is_google_in_startpages = false;
#if !defined(OS_IOS)
  // iOS does not have a notion of startpages.
  SessionStartupPref session_startup_prefs =
      StartupBrowserCreator::GetSessionStartupPref(
          *CommandLine::ForCurrentProcess(), profile);
  if (session_startup_prefs.type == SessionStartupPref::URLS) {
    is_google_in_startpages =
        std::count_if(session_startup_prefs.urls.begin(),
                      session_startup_prefs.urls.end(),
                      google_util::IsGoogleHomePageUrl) > 0;
  }
#endif

  if (!InitRlzDelayed(first_run, send_ping_immediately, delay,
                      is_google_default_search, is_google_homepage,
                      is_google_in_startpages)) {
    return false;
  }

#if !defined(OS_IOS)
  // Prime the RLZ cache for the home page access point so that its avaiable
  // for the startup page if needed (i.e., when the startup page is set to
  // the home page).
  GetAccessPointRlz(ChromeHomePage(), NULL);
#endif  // !defined(OS_IOS)

  return true;
}

bool RLZTracker::Init(bool first_run,
                      bool send_ping_immediately,
                      base::TimeDelta delay,
                      bool is_google_default_search,
                      bool is_google_homepage,
                      bool is_google_in_startpages) {
  first_run_ = first_run;
  is_google_default_search_ = is_google_default_search;
  is_google_homepage_ = is_google_homepage;
  is_google_in_startpages_ = is_google_in_startpages;
  send_ping_immediately_ = send_ping_immediately;

  // Enable zero delays for testing.
  if (CommandLine::ForCurrentProcess()->HasSwitch(::switches::kTestType))
    EnableZeroDelayForTesting();

  delay = std::min(kMaxInitDelay, std::max(min_init_delay_, delay));

  if (google_brand::GetBrand(&brand_) && !IsBrandOrganic(brand_)) {
    // Register for notifications from the omnibox so that we can record when
    // the user performs a first search.
    registrar_.Add(this, chrome::NOTIFICATION_OMNIBOX_OPENED_URL,
                   content::NotificationService::AllSources());

#if !defined(OS_IOS)
    // Register for notifications from navigations, to see if the user has used
    // the home page.
    registrar_.Add(this, content::NOTIFICATION_NAV_ENTRY_PENDING,
                   content::NotificationService::AllSources());
#endif  // !defined(OS_IOS)
  }
  google_brand::GetReactivationBrand(&reactivation_brand_);

  net::URLRequestContextGetter* context_getter =
      g_browser_process->system_request_context();

  // Could be NULL; don't run if so.  RLZ will try again next restart.
  if (context_getter) {
    rlz_lib::SetURLRequestContext(context_getter);
    ScheduleDelayedInit(delay);
  }

  return true;
}

void RLZTracker::ScheduleDelayedInit(base::TimeDelta delay) {
  // The RLZTracker is a singleton object that outlives any runnable tasks
  // that will be queued up.
  BrowserThread::GetBlockingPool()->PostDelayedSequencedWorkerTask(
      worker_pool_token_,
      FROM_HERE,
      base::Bind(&RLZTracker::DelayedInit, base::Unretained(this)),
      delay);
}

void RLZTracker::DelayedInit() {
  bool schedule_ping = false;

  // For organic brandcodes do not use rlz at all. Empty brandcode usually
  // means a chromium install. This is ok.
  if (!IsBrandOrganic(brand_)) {
    RecordProductEvents(first_run_, is_google_default_search_,
                        is_google_homepage_, is_google_in_startpages_,
                        already_ran_, omnibox_used_, homepage_used_,
                        app_list_used_);
    schedule_ping = true;
  }

  // If chrome has been reactivated, record the events for this brand
  // as well.
  if (!IsBrandOrganic(reactivation_brand_)) {
    rlz_lib::SupplementaryBranding branding(reactivation_brand_.c_str());
    RecordProductEvents(first_run_, is_google_default_search_,
                        is_google_homepage_, is_google_in_startpages_,
                        already_ran_, omnibox_used_, homepage_used_,
                        app_list_used_);
    schedule_ping = true;
  }

  already_ran_ = true;

  if (schedule_ping)
    ScheduleFinancialPing();
}

void RLZTracker::ScheduleFinancialPing() {
  BrowserThread::GetBlockingPool()->PostSequencedWorkerTaskWithShutdownBehavior(
      worker_pool_token_,
      FROM_HERE,
      base::Bind(&RLZTracker::PingNowImpl, base::Unretained(this)),
      base::SequencedWorkerPool::SKIP_ON_SHUTDOWN);
}

void RLZTracker::PingNowImpl() {
  TRACE_EVENT0("RLZ", "RLZTracker::PingNowImpl");
  base::string16 lang;
  GoogleUpdateSettings::GetLanguage(&lang);
  if (lang.empty())
    lang = base::ASCIIToUTF16("en");
  base::string16 referral;
  GoogleUpdateSettings::GetReferral(&referral);

  if (!IsBrandOrganic(brand_) && SendFinancialPing(brand_, lang, referral)) {
    GoogleUpdateSettings::ClearReferral();

    {
      base::AutoLock lock(cache_lock_);
      rlz_cache_.clear();
    }

    // Prime the RLZ cache for the access points we are interested in.
    GetAccessPointRlz(RLZTracker::ChromeOmnibox(), NULL);
#if !defined(OS_IOS)
    GetAccessPointRlz(RLZTracker::ChromeHomePage(), NULL);
    GetAccessPointRlz(RLZTracker::ChromeAppList(), NULL);
#endif  // !defined(OS_IOS)
  }

  if (!IsBrandOrganic(reactivation_brand_)) {
    rlz_lib::SupplementaryBranding branding(reactivation_brand_.c_str());
    SendFinancialPing(reactivation_brand_, lang, referral);
  }
}

bool RLZTracker::SendFinancialPing(const std::string& brand,
                                   const base::string16& lang,
                                   const base::string16& referral) {
  return ::SendFinancialPing(brand, lang, referral);
}

void RLZTracker::Observe(int type,
                         const content::NotificationSource& source,
                         const content::NotificationDetails& details) {
  switch (type) {
    case chrome::NOTIFICATION_OMNIBOX_OPENED_URL:
      // In M-36, we made NOTIFICATION_OMNIBOX_OPENED_URL fire more often than
      // it did previously.  The RLZ folks want RLZ's "first search" detection
      // to remain as unaffected as possible by this change.  This test is
      // there to keep the old behavior.
      if (!content::Details<OmniboxLog>(details).ptr()->is_popup_open)
        break;
      RecordFirstSearch(ChromeOmnibox());
      registrar_.Remove(this, chrome::NOTIFICATION_OMNIBOX_OPENED_URL,
                        content::NotificationService::AllSources());
      break;
#if !defined(OS_IOS)
    case content::NOTIFICATION_NAV_ENTRY_PENDING: {
      const NavigationEntry* entry =
          content::Details<content::NavigationEntry>(details).ptr();
      if (entry != NULL &&
          ((entry->GetTransitionType() &
            content::PAGE_TRANSITION_HOME_PAGE) != 0)) {
        RecordFirstSearch(ChromeHomePage());
        registrar_.Remove(this, content::NOTIFICATION_NAV_ENTRY_PENDING,
                          content::NotificationService::AllSources());
      }
      break;
    }
#endif  // !defined(OS_IOS)
    default:
      NOTREACHED();
      break;
  }
}

// static
bool RLZTracker::RecordProductEvent(rlz_lib::Product product,
                                    rlz_lib::AccessPoint point,
                                    rlz_lib::Event event_id) {
  return GetInstance()->RecordProductEventImpl(product, point, event_id);
}

bool RLZTracker::RecordProductEventImpl(rlz_lib::Product product,
                                        rlz_lib::AccessPoint point,
                                        rlz_lib::Event event_id) {
  // Make sure we don't access disk outside of the I/O thread.
  // In such case we repost the task on the right thread and return error.
  if (ScheduleRecordProductEvent(product, point, event_id))
    return true;

  bool ret = rlz_lib::RecordProductEvent(product, point, event_id);

  // If chrome has been reactivated, record the event for this brand as well.
  if (!reactivation_brand_.empty()) {
    rlz_lib::SupplementaryBranding branding(reactivation_brand_.c_str());
    ret &= rlz_lib::RecordProductEvent(product, point, event_id);
  }

  return ret;
}

bool RLZTracker::ScheduleRecordProductEvent(rlz_lib::Product product,
                                            rlz_lib::AccessPoint point,
                                            rlz_lib::Event event_id) {
  if (!BrowserThread::CurrentlyOn(BrowserThread::UI))
    return false;

  BrowserThread::GetBlockingPool()->PostSequencedWorkerTaskWithShutdownBehavior(
      worker_pool_token_,
      FROM_HERE,
      base::Bind(base::IgnoreResult(&RLZTracker::RecordProductEvent),
                 product, point, event_id),
      base::SequencedWorkerPool::SKIP_ON_SHUTDOWN);

  return true;
}

void RLZTracker::RecordFirstSearch(rlz_lib::AccessPoint point) {
  // Make sure we don't access disk outside of the I/O thread.
  // In such case we repost the task on the right thread and return error.
  if (ScheduleRecordFirstSearch(point))
    return;

  bool* record_used = GetAccessPointRecord(point);

  // Try to record event now, else set the flag to try later when we
  // attempt the ping.
  if (!RecordProductEvent(rlz_lib::CHROME, point, rlz_lib::FIRST_SEARCH))
    *record_used = true;
  else if (send_ping_immediately_ && point == ChromeOmnibox())
    ScheduleDelayedInit(base::TimeDelta());
}

bool RLZTracker::ScheduleRecordFirstSearch(rlz_lib::AccessPoint point) {
  if (!BrowserThread::CurrentlyOn(BrowserThread::UI))
    return false;
  BrowserThread::GetBlockingPool()->PostSequencedWorkerTaskWithShutdownBehavior(
      worker_pool_token_,
      FROM_HERE,
      base::Bind(&RLZTracker::RecordFirstSearch,
                 base::Unretained(this), point),
      base::SequencedWorkerPool::SKIP_ON_SHUTDOWN);
  return true;
}

bool* RLZTracker::GetAccessPointRecord(rlz_lib::AccessPoint point) {
  if (point == ChromeOmnibox())
    return &omnibox_used_;
#if !defined(OS_IOS)
  if (point == ChromeHomePage())
    return &homepage_used_;
  if (point == ChromeAppList())
    return &app_list_used_;
#endif  // !defined(OS_IOS)
  NOTREACHED();
  return NULL;
}

// static
std::string RLZTracker::GetAccessPointHttpHeader(rlz_lib::AccessPoint point) {
  TRACE_EVENT0("RLZ", "RLZTracker::GetAccessPointHttpHeader");
  std::string extra_headers;
  base::string16 rlz_string;
  RLZTracker::GetAccessPointRlz(point, &rlz_string);
  if (!rlz_string.empty()) {
    net::HttpUtil::AppendHeaderIfMissing("X-Rlz-String",
                                         base::UTF16ToUTF8(rlz_string),
                                         &extra_headers);
  }

  return extra_headers;
}

// GetAccessPointRlz() caches RLZ strings for all access points. If we had
// a successful ping, then we update the cached value.
bool RLZTracker::GetAccessPointRlz(rlz_lib::AccessPoint point,
                                   base::string16* rlz) {
  TRACE_EVENT0("RLZ", "RLZTracker::GetAccessPointRlz");
  return GetInstance()->GetAccessPointRlzImpl(point, rlz);
}

// GetAccessPointRlz() caches RLZ strings for all access points. If we had
// a successful ping, then we update the cached value.
bool RLZTracker::GetAccessPointRlzImpl(rlz_lib::AccessPoint point,
                                       base::string16* rlz) {
  // If the RLZ string for the specified access point is already cached,
  // simply return its value.
  {
    base::AutoLock lock(cache_lock_);
    if (rlz_cache_.find(point) != rlz_cache_.end()) {
      if (rlz)
        *rlz = rlz_cache_[point];
      return true;
    }
  }

  // Make sure we don't access disk outside of the I/O thread.
  // In such case we repost the task on the right thread and return error.
  if (ScheduleGetAccessPointRlz(point))
    return false;

  char str_rlz[rlz_lib::kMaxRlzLength + 1];
  if (!rlz_lib::GetAccessPointRlz(point, str_rlz, rlz_lib::kMaxRlzLength))
    return false;

  base::string16 rlz_local(base::ASCIIToUTF16(std::string(str_rlz)));
  if (rlz)
    *rlz = rlz_local;

  base::AutoLock lock(cache_lock_);
  rlz_cache_[point] = rlz_local;
  return true;
}

bool RLZTracker::ScheduleGetAccessPointRlz(rlz_lib::AccessPoint point) {
  if (!BrowserThread::CurrentlyOn(BrowserThread::UI))
    return false;

  base::string16* not_used = NULL;
  BrowserThread::GetBlockingPool()->PostSequencedWorkerTaskWithShutdownBehavior(
      worker_pool_token_,
      FROM_HERE,
      base::Bind(base::IgnoreResult(&RLZTracker::GetAccessPointRlz), point,
                 not_used),
      base::SequencedWorkerPool::SKIP_ON_SHUTDOWN);
  return true;
}

#if defined(OS_CHROMEOS)
// static
void RLZTracker::ClearRlzState() {
  GetInstance()->ClearRlzStateImpl();
}

void RLZTracker::ClearRlzStateImpl() {
  if (ScheduleClearRlzState())
    return;
  rlz_lib::ClearAllProductEvents(rlz_lib::CHROME);
}

bool RLZTracker::ScheduleClearRlzState() {
  if (!BrowserThread::CurrentlyOn(BrowserThread::UI))
    return false;

  BrowserThread::GetBlockingPool()->PostSequencedWorkerTaskWithShutdownBehavior(
      worker_pool_token_,
      FROM_HERE,
      base::Bind(&RLZTracker::ClearRlzStateImpl,
                 base::Unretained(this)),
      base::SequencedWorkerPool::SKIP_ON_SHUTDOWN);
  return true;
}
#endif

// static
void RLZTracker::CleanupRlz() {
  GetInstance()->rlz_cache_.clear();
  GetInstance()->registrar_.RemoveAll();
  rlz_lib::SetURLRequestContext(NULL);
}

// static
void RLZTracker::EnableZeroDelayForTesting() {
  GetInstance()->min_init_delay_ = base::TimeDelta();
}

#if !defined(OS_IOS)
// static
void RLZTracker::RecordAppListSearch() {
  GetInstance()->RecordFirstSearch(RLZTracker::ChromeAppList());
}
#endif