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
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(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.
114class 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
199WebResourceService::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
226WebResourceService::~WebResourceService() { }
227
228void 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
241void WebResourceService::EndFetch() {
242  in_fetch_ = false;
243}
244
245void WebResourceService::OnWebResourceUnpacked(
246  const DictionaryValue& parsed_json) {
247  Unpack(parsed_json);
248  EndFetch();
249}
250
251void 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
261void 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
283void 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