// 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.
#include "chrome/browser/web_resource/web_resource_service.h"
#include "base/command_line.h"
#include "base/file_path.h"
#include "base/string_number_conversions.h"
#include "base/string_util.h"
#include "base/threading/thread_restrictions.h"
#include "base/time.h"
#include "base/utf_string_conversions.h"
#include "base/values.h"
#include "chrome/browser/browser_process.h"
#include "chrome/browser/prefs/pref_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/web_resource/web_resource_unpacker.h"
#include "content/browser/browser_thread.h"
#include "content/common/notification_service.h"
#include "googleurl/src/gurl.h"
#include "net/base/load_flags.h"
#include "net/url_request/url_request_status.h"
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(int64 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;
std::string web_resource_server =
web_resource_service_->web_resource_server_;
if (web_resource_service_->apply_locale_to_url_) {
std::string locale = g_browser_process->GetApplicationLocale();
web_resource_server.append(locale);
}
url_fetcher_.reset(new URLFetcher(GURL(
web_resource_server),
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);
net::URLRequestContextGetter* url_request_context_getter =
web_resource_service_->profile_->GetRequestContext();
url_fetcher_->set_request_context(url_request_context_getter);
url_fetcher_->Start();
}
// From URLFetcher::Delegate.
void OnURLFetchComplete(const URLFetcher* source,
const GURL& url,
const net::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.
// TODO(willchan): Look for a better signal of whether we're in a unit test
// or not. Using |resource_dispatcher_host_| for this is pretty lame.
// 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,
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(BrowserThread::ID thread_id) {
UtilityProcessHost* host = new UtilityProcessHost(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_;
};
WebResourceService::WebResourceService(
Profile* profile,
PrefService* prefs,
const char* web_resource_server,
bool apply_locale_to_url,
NotificationType::Type notification_type,
const char* last_update_time_pref_name,
int start_fetch_delay,
int cache_update_delay)
: prefs_(prefs),
profile_(profile),
ALLOW_THIS_IN_INITIALIZER_LIST(service_factory_(this)),
in_fetch_(false),
web_resource_server_(web_resource_server),
apply_locale_to_url_(apply_locale_to_url),
notification_type_(notification_type),
last_update_time_pref_name_(last_update_time_pref_name),
start_fetch_delay_(start_fetch_delay),
cache_update_delay_(cache_update_delay),
web_resource_update_scheduled_(false) {
DCHECK(prefs);
DCHECK(profile);
prefs_->RegisterStringPref(last_update_time_pref_name, "0");
resource_dispatcher_host_ = g_browser_process->resource_dispatcher_host();
web_resource_fetcher_.reset(new WebResourceFetcher(this));
}
WebResourceService::~WebResourceService() { }
void WebResourceService::PostNotification(int64 delay_ms) {
if (web_resource_update_scheduled_)
return;
if (delay_ms > 0) {
web_resource_update_scheduled_ = true;
MessageLoop::current()->PostDelayedTask(FROM_HERE,
service_factory_.NewRunnableMethod(
&WebResourceService::WebResourceStateChange), delay_ms);
} else if (delay_ms == 0) {
WebResourceStateChange();
}
}
void WebResourceService::EndFetch() {
in_fetch_ = false;
}
void WebResourceService::OnWebResourceUnpacked(
const DictionaryValue& parsed_json) {
Unpack(parsed_json);
EndFetch();
}
void WebResourceService::WebResourceStateChange() {
web_resource_update_scheduled_ = false;
if (notification_type_ == NotificationType::NOTIFICATION_TYPE_COUNT)
return;
NotificationService* service = NotificationService::current();
service->Notify(notification_type_,
Source<WebResourceService>(this),
NotificationService::NoDetails());
}
void WebResourceService::StartAfterDelay() {
int64 delay = start_fetch_delay_;
// 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(last_update_time_pref_name_)) {
std::string last_update_pref =
prefs_->GetString(last_update_time_pref_name_);
if (!last_update_pref.empty()) {
double last_update_value;
base::StringToDouble(last_update_pref, &last_update_value);
int64 ms_until_update = cache_update_delay_ -
static_cast<int64>((base::Time::Now() - base::Time::FromDoubleT(
last_update_value)).InMilliseconds());
delay = ms_until_update > cache_update_delay_ ?
cache_update_delay_ : (ms_until_update < start_fetch_delay_ ?
start_fetch_delay_ : ms_until_update);
}
}
// Start fetch and wait for UpdateResourceCache.
web_resource_fetcher_->StartAfterDelay(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(last_update_time_pref_name_,
base::DoubleToString(base::Time::Now().ToDoubleT()));
}