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