1// Copyright 2013 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 "content/child/appcache/web_application_cache_host_impl.h"
6
7#include "base/compiler_specific.h"
8#include "base/id_map.h"
9#include "base/strings/string_util.h"
10#include "base/strings/stringprintf.h"
11#include "third_party/WebKit/public/platform/WebURL.h"
12#include "third_party/WebKit/public/platform/WebURLRequest.h"
13#include "third_party/WebKit/public/platform/WebURLResponse.h"
14#include "third_party/WebKit/public/web/WebDataSource.h"
15#include "third_party/WebKit/public/web/WebFrame.h"
16
17using blink::WebApplicationCacheHost;
18using blink::WebApplicationCacheHostClient;
19using blink::WebDataSource;
20using blink::WebFrame;
21using blink::WebURLRequest;
22using blink::WebURL;
23using blink::WebURLResponse;
24using blink::WebVector;
25using appcache::AppCacheBackend;
26using appcache::AppCacheResourceInfo;
27
28namespace content {
29
30namespace {
31
32// Note: the order of the elements in this array must match those
33// of the EventID enum in appcache_interfaces.h.
34const char* kEventNames[] = {
35  "Checking", "Error", "NoUpdate", "Downloading", "Progress",
36  "UpdateReady", "Cached", "Obsolete"
37};
38
39typedef IDMap<WebApplicationCacheHostImpl> HostsMap;
40
41HostsMap* all_hosts() {
42  static HostsMap* map = new HostsMap;
43  return map;
44}
45
46GURL ClearUrlRef(const GURL& url) {
47  if (!url.has_ref())
48    return url;
49  GURL::Replacements replacements;
50  replacements.ClearRef();
51  return url.ReplaceComponents(replacements);
52}
53
54}  // anon namespace
55
56WebApplicationCacheHostImpl* WebApplicationCacheHostImpl::FromId(int id) {
57  return all_hosts()->Lookup(id);
58}
59
60WebApplicationCacheHostImpl* WebApplicationCacheHostImpl::FromFrame(
61    const WebFrame* frame) {
62  if (!frame)
63    return NULL;
64  WebDataSource* data_source = frame->dataSource();
65  if (!data_source)
66    return NULL;
67  return static_cast<WebApplicationCacheHostImpl*>
68      (data_source->applicationCacheHost());
69}
70
71WebApplicationCacheHostImpl::WebApplicationCacheHostImpl(
72    WebApplicationCacheHostClient* client,
73    AppCacheBackend* backend)
74    : client_(client),
75      backend_(backend),
76      host_id_(all_hosts()->Add(this)),
77      status_(appcache::UNCACHED),
78      is_scheme_supported_(false),
79      is_get_method_(false),
80      is_new_master_entry_(MAYBE),
81      was_select_cache_called_(false) {
82  DCHECK(client && backend && (host_id_ != appcache::kNoHostId));
83
84  backend_->RegisterHost(host_id_);
85}
86
87WebApplicationCacheHostImpl::~WebApplicationCacheHostImpl() {
88  backend_->UnregisterHost(host_id_);
89  all_hosts()->Remove(host_id_);
90}
91
92void WebApplicationCacheHostImpl::OnCacheSelected(
93    const appcache::AppCacheInfo& info) {
94  cache_info_ = info;
95  client_->didChangeCacheAssociation();
96}
97
98void WebApplicationCacheHostImpl::OnStatusChanged(appcache::Status status) {
99  // TODO(michaeln): delete me, not used
100}
101
102void WebApplicationCacheHostImpl::OnEventRaised(appcache::EventID event_id) {
103  DCHECK(event_id != appcache::PROGRESS_EVENT);  // See OnProgressEventRaised.
104  DCHECK(event_id != appcache::ERROR_EVENT);  // See OnErrorEventRaised.
105
106  // Emit logging output prior to calling out to script as we can get
107  // deleted within the script event handler.
108  const char* kFormatString = "Application Cache %s event";
109  std::string message = base::StringPrintf(kFormatString,
110                                           kEventNames[event_id]);
111  OnLogMessage(appcache::LOG_INFO, message);
112
113  switch (event_id) {
114    case appcache::CHECKING_EVENT:
115      status_ = appcache::CHECKING;
116      break;
117    case appcache::DOWNLOADING_EVENT:
118      status_ = appcache::DOWNLOADING;
119      break;
120    case appcache::UPDATE_READY_EVENT:
121      status_ = appcache::UPDATE_READY;
122      break;
123    case appcache::CACHED_EVENT:
124    case appcache::NO_UPDATE_EVENT:
125      status_ = appcache::IDLE;
126      break;
127    case appcache::OBSOLETE_EVENT:
128      status_ = appcache::OBSOLETE;
129      break;
130    default:
131      NOTREACHED();
132      break;
133  }
134
135  client_->notifyEventListener(static_cast<EventID>(event_id));
136}
137
138void WebApplicationCacheHostImpl::OnProgressEventRaised(
139    const GURL& url, int num_total, int num_complete) {
140  // Emit logging output prior to calling out to script as we can get
141  // deleted within the script event handler.
142  const char* kFormatString = "Application Cache Progress event (%d of %d) %s";
143  std::string message = base::StringPrintf(kFormatString, num_complete,
144                                           num_total, url.spec().c_str());
145  OnLogMessage(appcache::LOG_INFO, message);
146  status_ = appcache::DOWNLOADING;
147  client_->notifyProgressEventListener(url, num_total, num_complete);
148}
149
150void WebApplicationCacheHostImpl::OnErrorEventRaised(
151    const std::string& message) {
152  // Emit logging output prior to calling out to script as we can get
153  // deleted within the script event handler.
154  const char* kFormatString = "Application Cache Error event: %s";
155  std::string full_message = base::StringPrintf(kFormatString,
156                                                message.c_str());
157  OnLogMessage(appcache::LOG_ERROR, full_message);
158
159  status_ = cache_info_.is_complete ? appcache::IDLE : appcache::UNCACHED;
160  client_->notifyEventListener(static_cast<EventID>(appcache::ERROR_EVENT));
161}
162
163void WebApplicationCacheHostImpl::willStartMainResourceRequest(
164    WebURLRequest& request, const WebFrame* frame) {
165  request.setAppCacheHostID(host_id_);
166
167  original_main_resource_url_ = ClearUrlRef(request.url());
168
169  std::string method = request.httpMethod().utf8();
170  is_get_method_ = (method == appcache::kHttpGETMethod);
171  DCHECK(method == StringToUpperASCII(method));
172
173  if (frame) {
174    const WebFrame* spawning_frame = frame->parent();
175    if (!spawning_frame)
176      spawning_frame = frame->opener();
177    if (!spawning_frame)
178      spawning_frame = frame;
179
180    WebApplicationCacheHostImpl* spawning_host = FromFrame(spawning_frame);
181    if (spawning_host && (spawning_host != this) &&
182        (spawning_host->status_ != appcache::UNCACHED)) {
183      backend_->SetSpawningHostId(host_id_, spawning_host->host_id());
184    }
185  }
186}
187
188void WebApplicationCacheHostImpl::willStartSubResourceRequest(
189    WebURLRequest& request) {
190  request.setAppCacheHostID(host_id_);
191}
192
193void WebApplicationCacheHostImpl::selectCacheWithoutManifest() {
194  if (was_select_cache_called_)
195    return;
196  was_select_cache_called_ = true;
197
198  status_ = (document_response_.appCacheID() == appcache::kNoCacheId) ?
199      appcache::UNCACHED : appcache::CHECKING;
200  is_new_master_entry_ = NO;
201  backend_->SelectCache(host_id_, document_url_,
202                        document_response_.appCacheID(),
203                        GURL());
204}
205
206bool WebApplicationCacheHostImpl::selectCacheWithManifest(
207    const WebURL& manifest_url) {
208  if (was_select_cache_called_)
209    return true;
210  was_select_cache_called_ = true;
211
212  GURL manifest_gurl(ClearUrlRef(manifest_url));
213
214  // 6.9.6 The application cache selection algorithm
215  // Check for new 'master' entries.
216  if (document_response_.appCacheID() == appcache::kNoCacheId) {
217    if (is_scheme_supported_ && is_get_method_ &&
218        (manifest_gurl.GetOrigin() == document_url_.GetOrigin())) {
219      status_ = appcache::CHECKING;
220      is_new_master_entry_ = YES;
221    } else {
222      status_ = appcache::UNCACHED;
223      is_new_master_entry_ = NO;
224      manifest_gurl = GURL();
225    }
226    backend_->SelectCache(
227        host_id_, document_url_, appcache::kNoCacheId, manifest_gurl);
228    return true;
229  }
230
231  DCHECK_EQ(NO, is_new_master_entry_);
232
233  // 6.9.6 The application cache selection algorithm
234  // Check for 'foreign' entries.
235  GURL document_manifest_gurl(document_response_.appCacheManifestURL());
236  if (document_manifest_gurl != manifest_gurl) {
237    backend_->MarkAsForeignEntry(host_id_, document_url_,
238                                 document_response_.appCacheID());
239    status_ = appcache::UNCACHED;
240    return false;  // the navigation will be restarted
241  }
242
243  status_ = appcache::CHECKING;
244
245  // Its a 'master' entry thats already in the cache.
246  backend_->SelectCache(host_id_, document_url_,
247                        document_response_.appCacheID(),
248                        manifest_gurl);
249  return true;
250}
251
252void WebApplicationCacheHostImpl::didReceiveResponseForMainResource(
253    const WebURLResponse& response) {
254  document_response_ = response;
255  document_url_ = ClearUrlRef(document_response_.url());
256  if (document_url_ != original_main_resource_url_)
257    is_get_method_ = true;  // A redirect was involved.
258  original_main_resource_url_ = GURL();
259
260  is_scheme_supported_ =  appcache::IsSchemeSupported(document_url_);
261  if ((document_response_.appCacheID() != appcache::kNoCacheId) ||
262      !is_scheme_supported_ || !is_get_method_)
263    is_new_master_entry_ = NO;
264}
265
266void WebApplicationCacheHostImpl::didReceiveDataForMainResource(
267    const char* data, int len) {
268  if (is_new_master_entry_ == NO)
269    return;
270  // TODO(michaeln): write me
271}
272
273void WebApplicationCacheHostImpl::didFinishLoadingMainResource(bool success) {
274  if (is_new_master_entry_ == NO)
275    return;
276  // TODO(michaeln): write me
277}
278
279WebApplicationCacheHost::Status WebApplicationCacheHostImpl::status() {
280  return static_cast<WebApplicationCacheHost::Status>(status_);
281}
282
283bool WebApplicationCacheHostImpl::startUpdate() {
284  if (!backend_->StartUpdate(host_id_))
285    return false;
286  if (status_ == appcache::IDLE || status_ == appcache::UPDATE_READY)
287    status_ = appcache::CHECKING;
288  else
289    status_ = backend_->GetStatus(host_id_);
290  return true;
291}
292
293bool WebApplicationCacheHostImpl::swapCache() {
294  if (!backend_->SwapCache(host_id_))
295    return false;
296  status_ = backend_->GetStatus(host_id_);
297  return true;
298}
299
300void WebApplicationCacheHostImpl::getAssociatedCacheInfo(
301    WebApplicationCacheHost::CacheInfo* info) {
302  info->manifestURL = cache_info_.manifest_url;
303  if (!cache_info_.is_complete)
304    return;
305  info->creationTime = cache_info_.creation_time.ToDoubleT();
306  info->updateTime = cache_info_.last_update_time.ToDoubleT();
307  info->totalSize = cache_info_.size;
308}
309
310void WebApplicationCacheHostImpl::getResourceList(
311    WebVector<ResourceInfo>* resources) {
312  if (!cache_info_.is_complete)
313    return;
314  std::vector<AppCacheResourceInfo> resource_infos;
315  backend_->GetResourceList(host_id_, &resource_infos);
316
317  WebVector<ResourceInfo> web_resources(resource_infos.size());
318  for (size_t i = 0; i < resource_infos.size(); ++i) {
319    web_resources[i].size = resource_infos[i].size;
320    web_resources[i].isMaster = resource_infos[i].is_master;
321    web_resources[i].isExplicit = resource_infos[i].is_explicit;
322    web_resources[i].isManifest = resource_infos[i].is_manifest;
323    web_resources[i].isForeign = resource_infos[i].is_foreign;
324    web_resources[i].isFallback = resource_infos[i].is_fallback;
325    web_resources[i].url = resource_infos[i].url;
326  }
327  resources->swap(web_resources);
328}
329
330}  // namespace content
331