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