1 // Copyright (c) 2011 The Chromium Authors. All rights reserved. 2 // Use of this source code is governed by a BSD-style license that can be 3 // found in the LICENSE file. 4 5 #include "chrome/browser/web_resource/web_resource_service.h" 6 7 #include "base/command_line.h" 8 #include "base/file_path.h" 9 #include "base/string_number_conversions.h" 10 #include "base/string_util.h" 11 #include "base/threading/thread_restrictions.h" 12 #include "base/time.h" 13 #include "base/utf_string_conversions.h" 14 #include "base/values.h" 15 #include "chrome/browser/browser_process.h" 16 #include "chrome/browser/prefs/pref_service.h" 17 #include "chrome/browser/profiles/profile.h" 18 #include "chrome/browser/sync/sync_ui_util.h" 19 #include "chrome/common/chrome_switches.h" 20 #include "chrome/common/extensions/extension.h" 21 #include "chrome/common/net/url_fetcher.h" 22 #include "chrome/common/web_resource/web_resource_unpacker.h" 23 #include "content/browser/browser_thread.h" 24 #include "content/common/notification_service.h" 25 #include "googleurl/src/gurl.h" 26 #include "net/base/load_flags.h" 27 #include "net/url_request/url_request_status.h" 28 29 class WebResourceService::WebResourceFetcher 30 : public URLFetcher::Delegate { 31 public: 32 explicit WebResourceFetcher(WebResourceService* web_resource_service) : 33 ALLOW_THIS_IN_INITIALIZER_LIST(fetcher_factory_(this)), 34 web_resource_service_(web_resource_service) { 35 } 36 37 // Delay initial load of resource data into cache so as not to interfere 38 // with startup time. 39 void StartAfterDelay(int64 delay_ms) { 40 MessageLoop::current()->PostDelayedTask(FROM_HERE, 41 fetcher_factory_.NewRunnableMethod(&WebResourceFetcher::StartFetch), 42 delay_ms); 43 } 44 45 // Initializes the fetching of data from the resource server. Data 46 // load calls OnURLFetchComplete. 47 void StartFetch() { 48 // Balanced in OnURLFetchComplete. 49 web_resource_service_->AddRef(); 50 // First, put our next cache load on the MessageLoop. 51 MessageLoop::current()->PostDelayedTask(FROM_HERE, 52 fetcher_factory_.NewRunnableMethod(&WebResourceFetcher::StartFetch), 53 web_resource_service_->cache_update_delay_); 54 // If we are still fetching data, exit. 55 if (web_resource_service_->in_fetch_) 56 return; 57 else 58 web_resource_service_->in_fetch_ = true; 59 60 std::string web_resource_server = 61 web_resource_service_->web_resource_server_; 62 if (web_resource_service_->apply_locale_to_url_) { 63 std::string locale = g_browser_process->GetApplicationLocale(); 64 web_resource_server.append(locale); 65 } 66 67 url_fetcher_.reset(new URLFetcher(GURL( 68 web_resource_server), 69 URLFetcher::GET, this)); 70 // Do not let url fetcher affect existing state in profile (by setting 71 // cookies, for example. 72 url_fetcher_->set_load_flags(net::LOAD_DISABLE_CACHE | 73 net::LOAD_DO_NOT_SAVE_COOKIES); 74 net::URLRequestContextGetter* url_request_context_getter = 75 web_resource_service_->profile_->GetRequestContext(); 76 url_fetcher_->set_request_context(url_request_context_getter); 77 url_fetcher_->Start(); 78 } 79 80 // From URLFetcher::Delegate. 81 void OnURLFetchComplete(const URLFetcher* source, 82 const GURL& url, 83 const net::URLRequestStatus& status, 84 int response_code, 85 const ResponseCookies& cookies, 86 const std::string& data) { 87 // Delete the URLFetcher when this function exits. 88 scoped_ptr<URLFetcher> clean_up_fetcher(url_fetcher_.release()); 89 90 // Don't parse data if attempt to download was unsuccessful. 91 // Stop loading new web resource data, and silently exit. 92 if (!status.is_success() || (response_code != 200)) 93 return; 94 95 web_resource_service_->UpdateResourceCache(data); 96 web_resource_service_->Release(); 97 } 98 99 private: 100 // So that we can delay our start so as not to affect start-up time; also, 101 // so that we can schedule future cache updates. 102 ScopedRunnableMethodFactory<WebResourceFetcher> fetcher_factory_; 103 104 // The tool that fetches the url data from the server. 105 scoped_ptr<URLFetcher> url_fetcher_; 106 107 // Our owner and creator. Ref counted. 108 WebResourceService* web_resource_service_; 109 }; 110 111 // This class coordinates a web resource unpack and parse task which is run in 112 // a separate process. Results are sent back to this class and routed to 113 // the WebResourceService. 114 class WebResourceService::UnpackerClient 115 : public UtilityProcessHost::Client { 116 public: 117 UnpackerClient(WebResourceService* web_resource_service, 118 const std::string& json_data) 119 : web_resource_service_(web_resource_service), 120 json_data_(json_data), got_response_(false) { 121 } 122 123 void Start() { 124 AddRef(); // balanced in Cleanup. 125 126 // TODO(willchan): Look for a better signal of whether we're in a unit test 127 // or not. Using |resource_dispatcher_host_| for this is pretty lame. 128 // If we don't have a resource_dispatcher_host_, assume we're in 129 // a test and run the unpacker directly in-process. 130 bool use_utility_process = 131 web_resource_service_->resource_dispatcher_host_ != NULL && 132 !CommandLine::ForCurrentProcess()->HasSwitch(switches::kSingleProcess); 133 if (use_utility_process) { 134 BrowserThread::ID thread_id; 135 CHECK(BrowserThread::GetCurrentThreadIdentifier(&thread_id)); 136 BrowserThread::PostTask( 137 BrowserThread::IO, FROM_HERE, 138 NewRunnableMethod(this, &UnpackerClient::StartProcessOnIOThread, 139 thread_id)); 140 } else { 141 WebResourceUnpacker unpacker(json_data_); 142 if (unpacker.Run()) { 143 OnUnpackWebResourceSucceeded(*unpacker.parsed_json()); 144 } else { 145 OnUnpackWebResourceFailed(unpacker.error_message()); 146 } 147 } 148 } 149 150 private: 151 ~UnpackerClient() {} 152 153 // UtilityProcessHost::Client 154 virtual void OnProcessCrashed(int exit_code) { 155 if (got_response_) 156 return; 157 158 OnUnpackWebResourceFailed( 159 "Chrome crashed while trying to retrieve web resources."); 160 } 161 162 virtual void OnUnpackWebResourceSucceeded( 163 const DictionaryValue& parsed_json) { 164 web_resource_service_->OnWebResourceUnpacked(parsed_json); 165 Cleanup(); 166 } 167 168 virtual void OnUnpackWebResourceFailed(const std::string& error_message) { 169 web_resource_service_->EndFetch(); 170 Cleanup(); 171 } 172 173 // Release reference and set got_response_. 174 void Cleanup() { 175 if (got_response_) 176 return; 177 178 got_response_ = true; 179 Release(); 180 } 181 182 void StartProcessOnIOThread(BrowserThread::ID thread_id) { 183 UtilityProcessHost* host = new UtilityProcessHost(this, thread_id); 184 // TODO(mrc): get proper file path when we start using web resources 185 // that need to be unpacked. 186 host->StartWebResourceUnpacker(json_data_); 187 } 188 189 scoped_refptr<WebResourceService> web_resource_service_; 190 191 // Holds raw JSON string. 192 const std::string& json_data_; 193 194 // True if we got a response from the utility process and have cleaned up 195 // already. 196 bool got_response_; 197 }; 198 199 WebResourceService::WebResourceService( 200 Profile* profile, 201 PrefService* prefs, 202 const char* web_resource_server, 203 bool apply_locale_to_url, 204 NotificationType::Type notification_type, 205 const char* last_update_time_pref_name, 206 int start_fetch_delay, 207 int cache_update_delay) 208 : prefs_(prefs), 209 profile_(profile), 210 ALLOW_THIS_IN_INITIALIZER_LIST(service_factory_(this)), 211 in_fetch_(false), 212 web_resource_server_(web_resource_server), 213 apply_locale_to_url_(apply_locale_to_url), 214 notification_type_(notification_type), 215 last_update_time_pref_name_(last_update_time_pref_name), 216 start_fetch_delay_(start_fetch_delay), 217 cache_update_delay_(cache_update_delay), 218 web_resource_update_scheduled_(false) { 219 DCHECK(prefs); 220 DCHECK(profile); 221 prefs_->RegisterStringPref(last_update_time_pref_name, "0"); 222 resource_dispatcher_host_ = g_browser_process->resource_dispatcher_host(); 223 web_resource_fetcher_.reset(new WebResourceFetcher(this)); 224 } 225 226 WebResourceService::~WebResourceService() { } 227 228 void WebResourceService::PostNotification(int64 delay_ms) { 229 if (web_resource_update_scheduled_) 230 return; 231 if (delay_ms > 0) { 232 web_resource_update_scheduled_ = true; 233 MessageLoop::current()->PostDelayedTask(FROM_HERE, 234 service_factory_.NewRunnableMethod( 235 &WebResourceService::WebResourceStateChange), delay_ms); 236 } else if (delay_ms == 0) { 237 WebResourceStateChange(); 238 } 239 } 240 241 void WebResourceService::EndFetch() { 242 in_fetch_ = false; 243 } 244 245 void WebResourceService::OnWebResourceUnpacked( 246 const DictionaryValue& parsed_json) { 247 Unpack(parsed_json); 248 EndFetch(); 249 } 250 251 void WebResourceService::WebResourceStateChange() { 252 web_resource_update_scheduled_ = false; 253 if (notification_type_ == NotificationType::NOTIFICATION_TYPE_COUNT) 254 return; 255 NotificationService* service = NotificationService::current(); 256 service->Notify(notification_type_, 257 Source<WebResourceService>(this), 258 NotificationService::NoDetails()); 259 } 260 261 void WebResourceService::StartAfterDelay() { 262 int64 delay = start_fetch_delay_; 263 // Check whether we have ever put a value in the web resource cache; 264 // if so, pull it out and see if it's time to update again. 265 if (prefs_->HasPrefPath(last_update_time_pref_name_)) { 266 std::string last_update_pref = 267 prefs_->GetString(last_update_time_pref_name_); 268 if (!last_update_pref.empty()) { 269 double last_update_value; 270 base::StringToDouble(last_update_pref, &last_update_value); 271 int64 ms_until_update = cache_update_delay_ - 272 static_cast<int64>((base::Time::Now() - base::Time::FromDoubleT( 273 last_update_value)).InMilliseconds()); 274 delay = ms_until_update > cache_update_delay_ ? 275 cache_update_delay_ : (ms_until_update < start_fetch_delay_ ? 276 start_fetch_delay_ : ms_until_update); 277 } 278 } 279 // Start fetch and wait for UpdateResourceCache. 280 web_resource_fetcher_->StartAfterDelay(delay); 281 } 282 283 void WebResourceService::UpdateResourceCache(const std::string& json_data) { 284 UnpackerClient* client = new UnpackerClient(this, json_data); 285 client->Start(); 286 287 // Set cache update time in preferences. 288 prefs_->SetString(last_update_time_pref_name_, 289 base::DoubleToString(base::Time::Now().ToDoubleT())); 290 } 291