web_resource_service.cc revision 3345a6884c488ff3a535c2c9acdd33d74b37e311
1// Copyright (c) 2010 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_util.h" 10#include "base/string_number_conversions.h" 11#include "base/time.h" 12#include "base/utf_string_conversions.h" 13#include "base/values.h" 14#include "chrome/browser/browser_process.h" 15#include "chrome/browser/chrome_thread.h" 16#include "chrome/browser/profile.h" 17#include "chrome/common/chrome_switches.h" 18#include "chrome/common/net/url_fetcher.h" 19#include "chrome/common/notification_service.h" 20#include "chrome/common/notification_type.h" 21#include "chrome/common/pref_names.h" 22#include "googleurl/src/gurl.h" 23#include "net/base/load_flags.h" 24#include "net/url_request/url_request_status.h" 25 26const char* WebResourceService::kCurrentTipPrefName = "current_tip"; 27const char* WebResourceService::kTipCachePrefName = "tips"; 28 29class 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(int 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 kCacheUpdateDelay); 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 url_fetcher_.reset(new URLFetcher(GURL( 61 web_resource_service_->web_resource_server_), 62 URLFetcher::GET, this)); 63 // Do not let url fetcher affect existing state in profile (by setting 64 // cookies, for example. 65 url_fetcher_->set_load_flags(net::LOAD_DISABLE_CACHE | 66 net::LOAD_DO_NOT_SAVE_COOKIES); 67 url_fetcher_->set_request_context(Profile::GetDefaultRequestContext()); 68 url_fetcher_->Start(); 69 } 70 71 // From URLFetcher::Delegate. 72 void OnURLFetchComplete(const URLFetcher* source, 73 const GURL& url, 74 const URLRequestStatus& status, 75 int response_code, 76 const ResponseCookies& cookies, 77 const std::string& data) { 78 // Delete the URLFetcher when this function exits. 79 scoped_ptr<URLFetcher> clean_up_fetcher(url_fetcher_.release()); 80 81 // Don't parse data if attempt to download was unsuccessful. 82 // Stop loading new web resource data, and silently exit. 83 if (!status.is_success() || (response_code != 200)) 84 return; 85 86 web_resource_service_->UpdateResourceCache(data); 87 web_resource_service_->Release(); 88 } 89 90 private: 91 // So that we can delay our start so as not to affect start-up time; also, 92 // so that we can schedule future cache updates. 93 ScopedRunnableMethodFactory<WebResourceFetcher> fetcher_factory_; 94 95 // The tool that fetches the url data from the server. 96 scoped_ptr<URLFetcher> url_fetcher_; 97 98 // Our owner and creator. Ref counted. 99 WebResourceService* web_resource_service_; 100}; 101 102// This class coordinates a web resource unpack and parse task which is run in 103// a separate process. Results are sent back to this class and routed to 104// the WebResourceService. 105class WebResourceService::UnpackerClient 106 : public UtilityProcessHost::Client { 107 public: 108 UnpackerClient(WebResourceService* web_resource_service, 109 const std::string& json_data) 110 : web_resource_service_(web_resource_service), 111 json_data_(json_data), got_response_(false) { 112 } 113 114 void Start() { 115 AddRef(); // balanced in Cleanup. 116 117 // If we don't have a resource_dispatcher_host_, assume we're in 118 // a test and run the unpacker directly in-process. 119 bool use_utility_process = 120 web_resource_service_->resource_dispatcher_host_ != NULL && 121 !CommandLine::ForCurrentProcess()->HasSwitch(switches::kSingleProcess); 122 if (use_utility_process) { 123 ChromeThread::ID thread_id; 124 CHECK(ChromeThread::GetCurrentThreadIdentifier(&thread_id)); 125 ChromeThread::PostTask( 126 ChromeThread::IO, FROM_HERE, 127 NewRunnableMethod(this, &UnpackerClient::StartProcessOnIOThread, 128 web_resource_service_->resource_dispatcher_host_, 129 thread_id)); 130 } else { 131 WebResourceUnpacker unpacker(json_data_); 132 if (unpacker.Run()) { 133 OnUnpackWebResourceSucceeded(*unpacker.parsed_json()); 134 } else { 135 OnUnpackWebResourceFailed(unpacker.error_message()); 136 } 137 } 138 } 139 140 private: 141 ~UnpackerClient() {} 142 143 // UtilityProcessHost::Client 144 virtual void OnProcessCrashed() { 145 if (got_response_) 146 return; 147 148 OnUnpackWebResourceFailed( 149 "Chrome crashed while trying to retrieve web resources."); 150 } 151 152 virtual void OnUnpackWebResourceSucceeded( 153 const DictionaryValue& parsed_json) { 154 web_resource_service_->OnWebResourceUnpacked(parsed_json); 155 Cleanup(); 156 } 157 158 virtual void OnUnpackWebResourceFailed(const std::string& error_message) { 159 web_resource_service_->EndFetch(); 160 Cleanup(); 161 } 162 163 // Release reference and set got_response_. 164 void Cleanup() { 165 if (got_response_) 166 return; 167 168 got_response_ = true; 169 Release(); 170 } 171 172 void StartProcessOnIOThread(ResourceDispatcherHost* rdh, 173 ChromeThread::ID thread_id) { 174 UtilityProcessHost* host = new UtilityProcessHost(rdh, this, thread_id); 175 // TODO(mrc): get proper file path when we start using web resources 176 // that need to be unpacked. 177 host->StartWebResourceUnpacker(json_data_); 178 } 179 180 scoped_refptr<WebResourceService> web_resource_service_; 181 182 // Holds raw JSON string. 183 const std::string& json_data_; 184 185 // True if we got a response from the utility process and have cleaned up 186 // already. 187 bool got_response_; 188}; 189 190// Server for custom logo signals. 191const char* WebResourceService::kDefaultResourceServer = 192 "https://www.google.com/support/chrome/bin/topic/30248/inproduct"; 193 194WebResourceService::WebResourceService(Profile* profile) 195 : prefs_(profile->GetPrefs()), 196 in_fetch_(false) { 197 Init(); 198} 199 200WebResourceService::~WebResourceService() { } 201 202void WebResourceService::Init() { 203 resource_dispatcher_host_ = g_browser_process->resource_dispatcher_host(); 204 web_resource_fetcher_.reset(new WebResourceFetcher(this)); 205 prefs_->RegisterStringPref(prefs::kNTPWebResourceCacheUpdate, "0"); 206 prefs_->RegisterRealPref(prefs::kNTPCustomLogoStart, 0); 207 prefs_->RegisterRealPref(prefs::kNTPCustomLogoEnd, 0); 208 209 if (prefs_->HasPrefPath(prefs::kNTPLogoResourceServer)) { 210 web_resource_server_ = prefs_->GetString(prefs::kNTPLogoResourceServer); 211 return; 212 } 213 214 // If we have not yet set a server, reset and force an immediate update. 215 web_resource_server_ = kDefaultResourceServer; 216 prefs_->SetString(prefs::kNTPWebResourceCacheUpdate, ""); 217} 218 219void WebResourceService::EndFetch() { 220 in_fetch_ = false; 221} 222 223void WebResourceService::OnWebResourceUnpacked( 224 const DictionaryValue& parsed_json) { 225 UnpackLogoSignal(parsed_json); 226 EndFetch(); 227} 228 229void WebResourceService::StartAfterDelay() { 230 int delay = kStartResourceFetchDelay; 231 // Check whether we have ever put a value in the web resource cache; 232 // if so, pull it out and see if it's time to update again. 233 if (prefs_->HasPrefPath(prefs::kNTPWebResourceCacheUpdate)) { 234 std::string last_update_pref = 235 prefs_->GetString(prefs::kNTPWebResourceCacheUpdate); 236 if (!last_update_pref.empty()) { 237 double last_update_value; 238 base::StringToDouble(last_update_pref, &last_update_value); 239 int ms_until_update = kCacheUpdateDelay - 240 static_cast<int>((base::Time::Now() - base::Time::FromDoubleT( 241 last_update_value)).InMilliseconds()); 242 243 delay = ms_until_update > kCacheUpdateDelay ? 244 kCacheUpdateDelay : (ms_until_update < kStartResourceFetchDelay ? 245 kStartResourceFetchDelay : ms_until_update); 246 } 247 } 248 249 // Start fetch and wait for UpdateResourceCache. 250 web_resource_fetcher_->StartAfterDelay(static_cast<int>(delay)); 251} 252 253void WebResourceService::UpdateResourceCache(const std::string& json_data) { 254 UnpackerClient* client = new UnpackerClient(this, json_data); 255 client->Start(); 256 257 // Update resource server and cache update time in preferences. 258 prefs_->SetString(prefs::kNTPWebResourceCacheUpdate, 259 base::DoubleToString(base::Time::Now().ToDoubleT())); 260 prefs_->SetString(prefs::kNTPLogoResourceServer, web_resource_server_); 261} 262 263void WebResourceService::UnpackTips(const DictionaryValue& parsed_json) { 264 // Get dictionary of cached preferences. 265 web_resource_cache_ = 266 prefs_->GetMutableDictionary(prefs::kNTPWebResourceCache); 267 268 // The list of individual tips. 269 ListValue* tip_holder = new ListValue(); 270 web_resource_cache_->Set(WebResourceService::kTipCachePrefName, tip_holder); 271 272 DictionaryValue* topic_dict; 273 ListValue* answer_list; 274 std::string topic_id; 275 std::string answer_id; 276 std::string inproduct; 277 int tip_counter = 0; 278 279 if (parsed_json.GetDictionary("topic", &topic_dict)) { 280 if (topic_dict->GetString("topic_id", &topic_id)) 281 web_resource_cache_->SetString("topic_id", topic_id); 282 if (topic_dict->GetList("answers", &answer_list)) { 283 for (ListValue::const_iterator tip_iter = answer_list->begin(); 284 tip_iter != answer_list->end(); ++tip_iter) { 285 if (!(*tip_iter)->IsType(Value::TYPE_DICTIONARY)) 286 continue; 287 DictionaryValue* a_dic = 288 static_cast<DictionaryValue*>(*tip_iter); 289 if (a_dic->GetString("inproduct", &inproduct)) { 290 tip_holder->Append(Value::CreateStringValue(inproduct)); 291 } 292 tip_counter++; 293 } 294 // If tips exist, set current index to 0. 295 if (!inproduct.empty()) { 296 web_resource_cache_->SetInteger( 297 WebResourceService::kCurrentTipPrefName, 0); 298 } 299 } 300 } 301} 302 303void WebResourceService::UnpackLogoSignal(const DictionaryValue& parsed_json) { 304 DictionaryValue* topic_dict; 305 ListValue* answer_list; 306 double old_logo_start = 0; 307 double old_logo_end = 0; 308 double logo_start = 0; 309 double logo_end = 0; 310 311 // Check for preexisting start and end values. 312 if (prefs_->HasPrefPath(prefs::kNTPCustomLogoStart) && 313 prefs_->HasPrefPath(prefs::kNTPCustomLogoEnd)) { 314 old_logo_start = prefs_->GetReal(prefs::kNTPCustomLogoStart); 315 old_logo_end = prefs_->GetReal(prefs::kNTPCustomLogoEnd); 316 } 317 318 // Check for newly received start and end values. 319 if (parsed_json.GetDictionary("topic", &topic_dict)) { 320 if (topic_dict->GetList("answers", &answer_list)) { 321 std::string logo_start_string = ""; 322 std::string logo_end_string = ""; 323 for (ListValue::const_iterator tip_iter = answer_list->begin(); 324 tip_iter != answer_list->end(); ++tip_iter) { 325 if (!(*tip_iter)->IsType(Value::TYPE_DICTIONARY)) 326 continue; 327 DictionaryValue* a_dic = 328 static_cast<DictionaryValue*>(*tip_iter); 329 std::string logo_signal; 330 if (a_dic->GetString("name", &logo_signal)) { 331 if (logo_signal == "custom_logo_start") { 332 a_dic->GetString("inproduct", &logo_start_string); 333 } else if (logo_signal == "custom_logo_end") { 334 a_dic->GetString("inproduct", &logo_end_string); 335 } 336 } 337 } 338 if (!logo_start_string.empty() && 339 logo_start_string.length() > 0 && 340 !logo_end_string.empty() && 341 logo_end_string.length() > 0) { 342 base::Time start_time; 343 base::Time end_time; 344 if (base::Time::FromString( 345 ASCIIToWide(logo_start_string).c_str(), &start_time) && 346 base::Time::FromString( 347 ASCIIToWide(logo_end_string).c_str(), &end_time)) { 348 logo_start = start_time.ToDoubleT(); 349 logo_end = end_time.ToDoubleT(); 350 } 351 } 352 } 353 } 354 355 // If logo start or end times have changed, trigger a theme change so that 356 // the logo on the NTP is updated. This check is outside the reading of the 357 // web resource data, because the absence of dates counts as a triggering 358 // change if there were dates before. 359 if (!(old_logo_start == logo_start) || 360 !(old_logo_end == logo_end)) { 361 prefs_->SetReal(prefs::kNTPCustomLogoStart, logo_start); 362 prefs_->SetReal(prefs::kNTPCustomLogoEnd, logo_end); 363 NotificationService* service = NotificationService::current(); 364 service->Notify(NotificationType::BROWSER_THEME_CHANGED, 365 Source<WebResourceService>(this), 366 NotificationService::NoDetails()); 367 } 368} 369