blob: cdee19751637556e1733537ec9f7f7734c5c6028 [file] [log] [blame]
// Copyright (c) 2010 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.
#include "chrome/browser/web_resource/web_resource_service.h"
#include "base/command_line.h"
#include "base/file_path.h"
#include "base/string_util.h"
#include "base/string_number_conversions.h"
#include "base/time.h"
#include "base/utf_string_conversions.h"
#include "base/values.h"
#include "chrome/browser/browser_process.h"
#include "chrome/browser/browser_thread.h"
#include "chrome/browser/extensions/extension_service.h"
#include "chrome/browser/profiles/profile.h"
#include "chrome/browser/sync/sync_ui_util.h"
#include "chrome/common/chrome_switches.h"
#include "chrome/common/extensions/extension.h"
#include "chrome/common/net/url_fetcher.h"
#include "chrome/common/notification_service.h"
#include "chrome/common/notification_type.h"
#include "chrome/common/pref_names.h"
#include "googleurl/src/gurl.h"
#include "net/base/load_flags.h"
#include "net/url_request/url_request_status.h"
namespace {
// Delay on first fetch so we don't interfere with startup.
static const int kStartResourceFetchDelay = 5000;
// Delay between calls to update the cache (48 hours).
static const int kCacheUpdateDelay = 48 * 60 * 60 * 1000;
} // namespace
const char* WebResourceService::kCurrentTipPrefName = "current_tip";
const char* WebResourceService::kTipCachePrefName = "tips";
class WebResourceService::WebResourceFetcher
: public URLFetcher::Delegate {
public:
explicit WebResourceFetcher(WebResourceService* web_resource_service) :
ALLOW_THIS_IN_INITIALIZER_LIST(fetcher_factory_(this)),
web_resource_service_(web_resource_service) {
}
// Delay initial load of resource data into cache so as not to interfere
// with startup time.
void StartAfterDelay(int delay_ms) {
MessageLoop::current()->PostDelayedTask(FROM_HERE,
fetcher_factory_.NewRunnableMethod(&WebResourceFetcher::StartFetch),
delay_ms);
}
// Initializes the fetching of data from the resource server. Data
// load calls OnURLFetchComplete.
void StartFetch() {
// Balanced in OnURLFetchComplete.
web_resource_service_->AddRef();
// First, put our next cache load on the MessageLoop.
MessageLoop::current()->PostDelayedTask(FROM_HERE,
fetcher_factory_.NewRunnableMethod(&WebResourceFetcher::StartFetch),
web_resource_service_->cache_update_delay());
// If we are still fetching data, exit.
if (web_resource_service_->in_fetch_)
return;
else
web_resource_service_->in_fetch_ = true;
url_fetcher_.reset(new URLFetcher(GURL(
kDefaultWebResourceServer),
URLFetcher::GET, this));
// Do not let url fetcher affect existing state in profile (by setting
// cookies, for example.
url_fetcher_->set_load_flags(net::LOAD_DISABLE_CACHE |
net::LOAD_DO_NOT_SAVE_COOKIES);
url_fetcher_->set_request_context(Profile::GetDefaultRequestContext());
url_fetcher_->Start();
}
// From URLFetcher::Delegate.
void OnURLFetchComplete(const URLFetcher* source,
const GURL& url,
const URLRequestStatus& status,
int response_code,
const ResponseCookies& cookies,
const std::string& data) {
// Delete the URLFetcher when this function exits.
scoped_ptr<URLFetcher> clean_up_fetcher(url_fetcher_.release());
// Don't parse data if attempt to download was unsuccessful.
// Stop loading new web resource data, and silently exit.
if (!status.is_success() || (response_code != 200))
return;
web_resource_service_->UpdateResourceCache(data);
web_resource_service_->Release();
}
private:
// So that we can delay our start so as not to affect start-up time; also,
// so that we can schedule future cache updates.
ScopedRunnableMethodFactory<WebResourceFetcher> fetcher_factory_;
// The tool that fetches the url data from the server.
scoped_ptr<URLFetcher> url_fetcher_;
// Our owner and creator. Ref counted.
WebResourceService* web_resource_service_;
};
// This class coordinates a web resource unpack and parse task which is run in
// a separate process. Results are sent back to this class and routed to
// the WebResourceService.
class WebResourceService::UnpackerClient
: public UtilityProcessHost::Client {
public:
UnpackerClient(WebResourceService* web_resource_service,
const std::string& json_data)
: web_resource_service_(web_resource_service),
json_data_(json_data), got_response_(false) {
}
void Start() {
AddRef(); // balanced in Cleanup.
// If we don't have a resource_dispatcher_host_, assume we're in
// a test and run the unpacker directly in-process.
bool use_utility_process =
web_resource_service_->resource_dispatcher_host_ != NULL &&
!CommandLine::ForCurrentProcess()->HasSwitch(switches::kSingleProcess);
if (use_utility_process) {
BrowserThread::ID thread_id;
CHECK(BrowserThread::GetCurrentThreadIdentifier(&thread_id));
BrowserThread::PostTask(
BrowserThread::IO, FROM_HERE,
NewRunnableMethod(this, &UnpackerClient::StartProcessOnIOThread,
web_resource_service_->resource_dispatcher_host_,
thread_id));
} else {
WebResourceUnpacker unpacker(json_data_);
if (unpacker.Run()) {
OnUnpackWebResourceSucceeded(*unpacker.parsed_json());
} else {
OnUnpackWebResourceFailed(unpacker.error_message());
}
}
}
private:
~UnpackerClient() {}
// UtilityProcessHost::Client
virtual void OnProcessCrashed(int exit_code) {
if (got_response_)
return;
OnUnpackWebResourceFailed(
"Chrome crashed while trying to retrieve web resources.");
}
virtual void OnUnpackWebResourceSucceeded(
const DictionaryValue& parsed_json) {
web_resource_service_->OnWebResourceUnpacked(parsed_json);
Cleanup();
}
virtual void OnUnpackWebResourceFailed(const std::string& error_message) {
web_resource_service_->EndFetch();
Cleanup();
}
// Release reference and set got_response_.
void Cleanup() {
if (got_response_)
return;
got_response_ = true;
Release();
}
void StartProcessOnIOThread(ResourceDispatcherHost* rdh,
BrowserThread::ID thread_id) {
UtilityProcessHost* host = new UtilityProcessHost(rdh, this, thread_id);
// TODO(mrc): get proper file path when we start using web resources
// that need to be unpacked.
host->StartWebResourceUnpacker(json_data_);
}
scoped_refptr<WebResourceService> web_resource_service_;
// Holds raw JSON string.
const std::string& json_data_;
// True if we got a response from the utility process and have cleaned up
// already.
bool got_response_;
};
// Server for dynamically loaded NTP HTML elements. TODO(mirandac): append
// locale for future usage, when we're serving localizable strings.
const char* WebResourceService::kDefaultWebResourceServer =
"https://www.google.com/support/chrome/bin/topic/30248/inproduct";
WebResourceService::WebResourceService(Profile* profile)
: prefs_(profile->GetPrefs()),
profile_(profile),
ALLOW_THIS_IN_INITIALIZER_LIST(service_factory_(this)),
in_fetch_(false),
web_resource_update_scheduled_(false) {
Init();
}
WebResourceService::~WebResourceService() { }
void WebResourceService::Init() {
cache_update_delay_ = kCacheUpdateDelay;
resource_dispatcher_host_ = g_browser_process->resource_dispatcher_host();
web_resource_fetcher_.reset(new WebResourceFetcher(this));
prefs_->RegisterStringPref(prefs::kNTPWebResourceCacheUpdate, "0");
prefs_->RegisterRealPref(prefs::kNTPCustomLogoStart, 0);
prefs_->RegisterRealPref(prefs::kNTPCustomLogoEnd, 0);
prefs_->RegisterRealPref(prefs::kNTPPromoStart, 0);
prefs_->RegisterRealPref(prefs::kNTPPromoEnd, 0);
prefs_->RegisterStringPref(prefs::kNTPPromoLine, std::string());
prefs_->RegisterBooleanPref(prefs::kNTPPromoClosed, false);
// If the promo start is in the future, set a notification task to invalidate
// the NTP cache at the time of the promo start.
double promo_start = prefs_->GetReal(prefs::kNTPPromoStart);
double promo_end = prefs_->GetReal(prefs::kNTPPromoEnd);
ScheduleNotification(promo_start, promo_end);
}
void WebResourceService::EndFetch() {
in_fetch_ = false;
}
void WebResourceService::OnWebResourceUnpacked(
const DictionaryValue& parsed_json) {
UnpackLogoSignal(parsed_json);
if (WebResourceServiceUtil::CanShowPromo(profile_))
UnpackPromoSignal(parsed_json);
EndFetch();
}
void WebResourceService::WebResourceStateChange() {
web_resource_update_scheduled_ = false;
NotificationService* service = NotificationService::current();
service->Notify(NotificationType::WEB_RESOURCE_STATE_CHANGED,
Source<WebResourceService>(this),
NotificationService::NoDetails());
}
void WebResourceService::ScheduleNotification(double promo_start,
double promo_end) {
if (promo_start > 0 && promo_end > 0 && !web_resource_update_scheduled_) {
int ms_until_start =
static_cast<int>((base::Time::FromDoubleT(
promo_start) - base::Time::Now()).InMilliseconds());
int ms_until_end =
static_cast<int>((base::Time::FromDoubleT(
promo_end) - base::Time::Now()).InMilliseconds());
if (ms_until_start > 0) {
web_resource_update_scheduled_ = true;
MessageLoop::current()->PostDelayedTask(FROM_HERE,
service_factory_.NewRunnableMethod(
&WebResourceService::WebResourceStateChange),
ms_until_start);
}
if (ms_until_end > 0) {
web_resource_update_scheduled_ = true;
MessageLoop::current()->PostDelayedTask(FROM_HERE,
service_factory_.NewRunnableMethod(
&WebResourceService::WebResourceStateChange),
ms_until_end);
if (ms_until_start <= 0) {
// Notify immediately if time is between start and end.
WebResourceStateChange();
}
}
}
}
void WebResourceService::StartAfterDelay() {
int delay = kStartResourceFetchDelay;
// Check whether we have ever put a value in the web resource cache;
// if so, pull it out and see if it's time to update again.
if (prefs_->HasPrefPath(prefs::kNTPWebResourceCacheUpdate)) {
std::string last_update_pref =
prefs_->GetString(prefs::kNTPWebResourceCacheUpdate);
if (!last_update_pref.empty()) {
double last_update_value;
base::StringToDouble(last_update_pref, &last_update_value);
int ms_until_update = cache_update_delay_ -
static_cast<int>((base::Time::Now() - base::Time::FromDoubleT(
last_update_value)).InMilliseconds());
delay = ms_until_update > cache_update_delay_ ?
cache_update_delay_ : (ms_until_update < kStartResourceFetchDelay ?
kStartResourceFetchDelay : ms_until_update);
}
}
// Start fetch and wait for UpdateResourceCache.
web_resource_fetcher_->StartAfterDelay(static_cast<int>(delay));
}
void WebResourceService::UpdateResourceCache(const std::string& json_data) {
UnpackerClient* client = new UnpackerClient(this, json_data);
client->Start();
// Set cache update time in preferences.
prefs_->SetString(prefs::kNTPWebResourceCacheUpdate,
base::DoubleToString(base::Time::Now().ToDoubleT()));
}
void WebResourceService::UnpackTips(const DictionaryValue& parsed_json) {
// Get dictionary of cached preferences.
web_resource_cache_ =
prefs_->GetMutableDictionary(prefs::kNTPWebResourceCache);
// The list of individual tips.
ListValue* tip_holder = new ListValue();
web_resource_cache_->Set(WebResourceService::kTipCachePrefName, tip_holder);
DictionaryValue* topic_dict;
ListValue* answer_list;
std::string topic_id;
std::string answer_id;
std::string inproduct;
int tip_counter = 0;
if (parsed_json.GetDictionary("topic", &topic_dict)) {
if (topic_dict->GetString("topic_id", &topic_id))
web_resource_cache_->SetString("topic_id", topic_id);
if (topic_dict->GetList("answers", &answer_list)) {
for (ListValue::const_iterator tip_iter = answer_list->begin();
tip_iter != answer_list->end(); ++tip_iter) {
if (!(*tip_iter)->IsType(Value::TYPE_DICTIONARY))
continue;
DictionaryValue* a_dic =
static_cast<DictionaryValue*>(*tip_iter);
if (a_dic->GetString("inproduct", &inproduct)) {
tip_holder->Append(Value::CreateStringValue(inproduct));
}
tip_counter++;
}
// If tips exist, set current index to 0.
if (!inproduct.empty()) {
web_resource_cache_->SetInteger(
WebResourceService::kCurrentTipPrefName, 0);
}
}
}
}
void WebResourceService::UnpackPromoSignal(const DictionaryValue& parsed_json) {
DictionaryValue* topic_dict;
ListValue* answer_list;
double old_promo_start = 0;
double old_promo_end = 0;
double promo_start = 0;
double promo_end = 0;
// Check for preexisting start and end values.
if (prefs_->HasPrefPath(prefs::kNTPPromoStart) &&
prefs_->HasPrefPath(prefs::kNTPPromoEnd)) {
old_promo_start = prefs_->GetReal(prefs::kNTPPromoStart);
old_promo_end = prefs_->GetReal(prefs::kNTPPromoEnd);
}
// Check for newly received start and end values.
if (parsed_json.GetDictionary("topic", &topic_dict)) {
if (topic_dict->GetList("answers", &answer_list)) {
std::string promo_start_string = "";
std::string promo_end_string = "";
std::string promo_string = "";
for (ListValue::const_iterator tip_iter = answer_list->begin();
tip_iter != answer_list->end(); ++tip_iter) {
if (!(*tip_iter)->IsType(Value::TYPE_DICTIONARY))
continue;
DictionaryValue* a_dic =
static_cast<DictionaryValue*>(*tip_iter);
std::string promo_signal;
if (a_dic->GetString("name", &promo_signal)) {
if (promo_signal == "promo_start") {
a_dic->GetString("inproduct", &promo_start_string);
a_dic->GetString("tooltip", &promo_string);
prefs_->SetString(prefs::kNTPPromoLine, promo_string);
} else if (promo_signal == "promo_end") {
a_dic->GetString("inproduct", &promo_end_string);
}
}
}
if (!promo_start_string.empty() &&
promo_start_string.length() > 0 &&
!promo_end_string.empty() &&
promo_end_string.length() > 0) {
base::Time start_time;
base::Time end_time;
if (base::Time::FromString(
ASCIIToWide(promo_start_string).c_str(), &start_time) &&
base::Time::FromString(
ASCIIToWide(promo_end_string).c_str(), &end_time)) {
promo_start = start_time.ToDoubleT();
promo_end = end_time.ToDoubleT();
}
}
}
}
// If start or end times have changed, trigger a new web resource
// notification, so that the logo on the NTP is updated. This check is
// outside the reading of the web resource data, because the absence of
// dates counts as a triggering change if there were dates before.
if (!(old_promo_start == promo_start) ||
!(old_promo_end == promo_end)) {
prefs_->SetReal(prefs::kNTPPromoStart, promo_start);
prefs_->SetReal(prefs::kNTPPromoEnd, promo_end);
ScheduleNotification(promo_start, promo_end);
}
}
void WebResourceService::UnpackLogoSignal(const DictionaryValue& parsed_json) {
DictionaryValue* topic_dict;
ListValue* answer_list;
double old_logo_start = 0;
double old_logo_end = 0;
double logo_start = 0;
double logo_end = 0;
// Check for preexisting start and end values.
if (prefs_->HasPrefPath(prefs::kNTPCustomLogoStart) &&
prefs_->HasPrefPath(prefs::kNTPCustomLogoEnd)) {
old_logo_start = prefs_->GetReal(prefs::kNTPCustomLogoStart);
old_logo_end = prefs_->GetReal(prefs::kNTPCustomLogoEnd);
}
// Check for newly received start and end values.
if (parsed_json.GetDictionary("topic", &topic_dict)) {
if (topic_dict->GetList("answers", &answer_list)) {
std::string logo_start_string = "";
std::string logo_end_string = "";
for (ListValue::const_iterator tip_iter = answer_list->begin();
tip_iter != answer_list->end(); ++tip_iter) {
if (!(*tip_iter)->IsType(Value::TYPE_DICTIONARY))
continue;
DictionaryValue* a_dic =
static_cast<DictionaryValue*>(*tip_iter);
std::string logo_signal;
if (a_dic->GetString("name", &logo_signal)) {
if (logo_signal == "custom_logo_start") {
a_dic->GetString("inproduct", &logo_start_string);
} else if (logo_signal == "custom_logo_end") {
a_dic->GetString("inproduct", &logo_end_string);
}
}
}
if (!logo_start_string.empty() &&
logo_start_string.length() > 0 &&
!logo_end_string.empty() &&
logo_end_string.length() > 0) {
base::Time start_time;
base::Time end_time;
if (base::Time::FromString(
ASCIIToWide(logo_start_string).c_str(), &start_time) &&
base::Time::FromString(
ASCIIToWide(logo_end_string).c_str(), &end_time)) {
logo_start = start_time.ToDoubleT();
logo_end = end_time.ToDoubleT();
}
}
}
}
// If logo start or end times have changed, trigger a new web resource
// notification, so that the logo on the NTP is updated. This check is
// outside the reading of the web resource data, because the absence of
// dates counts as a triggering change if there were dates before.
if (!(old_logo_start == logo_start) ||
!(old_logo_end == logo_end)) {
prefs_->SetReal(prefs::kNTPCustomLogoStart, logo_start);
prefs_->SetReal(prefs::kNTPCustomLogoEnd, logo_end);
NotificationService* service = NotificationService::current();
service->Notify(NotificationType::WEB_RESOURCE_STATE_CHANGED,
Source<WebResourceService>(this),
NotificationService::NoDetails());
}
}
namespace WebResourceServiceUtil {
bool CanShowPromo(Profile* profile) {
bool promo_closed = false;
PrefService* prefs = profile->GetPrefs();
if (prefs->HasPrefPath(prefs::kNTPPromoClosed))
promo_closed = prefs->GetBoolean(prefs::kNTPPromoClosed);
bool has_extensions = false;
ExtensionService* extensions_service = profile->GetExtensionService();
if (extensions_service) {
const ExtensionList* extensions = extensions_service->extensions();
for (ExtensionList::const_iterator iter = extensions->begin();
iter != extensions->end();
++iter) {
if ((*iter)->location() == Extension::INTERNAL) {
has_extensions = true;
break;
}
}
}
// Note that HasProfileSyncService() will be false for ChromeOS, so
// promo_options will only be true if the user has an extension installed.
// See http://crosbug/10209
bool promo_options =
(profile->HasProfileSyncService() &&
sync_ui_util::GetStatus(
profile->GetProfileSyncService()) == sync_ui_util::SYNCED) ||
has_extensions;
return !promo_closed &&
promo_options &&
g_browser_process->GetApplicationLocale() == "en-US";
}
} // namespace WebResourceService