15821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)// Copyright (c) 2011 The Chromium Authors. All rights reserved.
25821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)// Use of this source code is governed by a BSD-style license that can be
35821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)// found in the LICENSE file.
45821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
5868fa2fe829687343ffae624259930155e16dbd8Torne (Richard Coles)#include "webkit/browser/appcache/appcache_request_handler.h"
65821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
75821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)#include "net/url_request/url_request.h"
85821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)#include "net/url_request/url_request_job.h"
9868fa2fe829687343ffae624259930155e16dbd8Torne (Richard Coles)#include "webkit/browser/appcache/appcache.h"
105d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)#include "webkit/browser/appcache/appcache_backend_impl.h"
11868fa2fe829687343ffae624259930155e16dbd8Torne (Richard Coles)#include "webkit/browser/appcache/appcache_policy.h"
12868fa2fe829687343ffae624259930155e16dbd8Torne (Richard Coles)#include "webkit/browser/appcache/appcache_url_request_job.h"
135821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
145821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)namespace appcache {
155821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
165821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)AppCacheRequestHandler::AppCacheRequestHandler(
175821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    AppCacheHost* host, ResourceType::Type resource_type)
185821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    : host_(host), resource_type_(resource_type),
195821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)      is_waiting_for_cache_selection_(false), found_group_id_(0),
205821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)      found_cache_id_(0), found_network_namespace_(false),
215821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)      cache_entry_not_found_(false), maybe_load_resource_executed_(false) {
225821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  DCHECK(host_);
235821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  host_->AddObserver(this);
245821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)}
255821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
265821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)AppCacheRequestHandler::~AppCacheRequestHandler() {
275821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  if (host_) {
285821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    storage()->CancelDelegateCallbacks(this);
295821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    host_->RemoveObserver(this);
305821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  }
315821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)}
325821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
335821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)AppCacheStorage* AppCacheRequestHandler::storage() const {
345821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  DCHECK(host_);
351e9bf3e0803691d0a228da41fc608347b6db4340Torne (Richard Coles)  return host_->storage();
365821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)}
375821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
385821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)AppCacheURLRequestJob* AppCacheRequestHandler::MaybeLoadResource(
395821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    net::URLRequest* request, net::NetworkDelegate* network_delegate) {
405821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  maybe_load_resource_executed_ = true;
415821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  if (!host_ || !IsSchemeAndMethodSupported(request) || cache_entry_not_found_)
425821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    return NULL;
435821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
445821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  // This method can get called multiple times over the life
455821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  // of a request. The case we detect here is having scheduled
465821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  // delivery of a "network response" using a job setup on an
475821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  // earlier call thru this method. To send the request thru
485821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  // to the network involves restarting the request altogether,
495821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  // which will call thru to our interception layer again.
505821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  // This time thru, we return NULL so the request hits the wire.
51868fa2fe829687343ffae624259930155e16dbd8Torne (Richard Coles)  if (job_.get()) {
525821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    DCHECK(job_->is_delivering_network_response() ||
535821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)           job_->cache_entry_not_found());
545821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    if (job_->cache_entry_not_found())
555821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)      cache_entry_not_found_ = true;
565821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    job_ = NULL;
575821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    storage()->CancelDelegateCallbacks(this);
585821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    return NULL;
595821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  }
605821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
615821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  // Clear out our 'found' fields since we're starting a request for a
625821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  // new resource, any values in those fields are no longer valid.
635821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  found_entry_ = AppCacheEntry();
645821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  found_fallback_entry_ = AppCacheEntry();
65f8ee788a64d60abd8f2d742a5fdedde054ecd910Torne (Richard Coles)  found_cache_id_ = kAppCacheNoCacheId;
665821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  found_manifest_url_ = GURL();
675821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  found_network_namespace_ = false;
685821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
695821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  if (is_main_resource())
705821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    MaybeLoadMainResource(request, network_delegate);
715821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  else
725821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    MaybeLoadSubResource(request, network_delegate);
735821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
745821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  // If its been setup to deliver a network response, we can just delete
755821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  // it now and return NULL instead to achieve that since it couldn't
765821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  // have been started yet.
77868fa2fe829687343ffae624259930155e16dbd8Torne (Richard Coles)  if (job_.get() && job_->is_delivering_network_response()) {
785821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    DCHECK(!job_->has_been_started());
795821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    job_ = NULL;
805821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  }
815821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
82868fa2fe829687343ffae624259930155e16dbd8Torne (Richard Coles)  return job_.get();
835821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)}
845821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
855821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)AppCacheURLRequestJob* AppCacheRequestHandler::MaybeLoadFallbackForRedirect(
865821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    net::URLRequest* request,
875821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    net::NetworkDelegate* network_delegate,
885821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    const GURL& location) {
895821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  if (!host_ || !IsSchemeAndMethodSupported(request) || cache_entry_not_found_)
905821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    return NULL;
915821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  if (is_main_resource())
925821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    return NULL;
935821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  // TODO(vabr) This is a temporary fix (see crbug/141114). We should get rid of
945821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  // it once a more general solution to crbug/121325 is in place.
955821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  if (!maybe_load_resource_executed_)
965821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    return NULL;
975821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  if (request->url().GetOrigin() == location.GetOrigin())
985821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    return NULL;
995821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
100868fa2fe829687343ffae624259930155e16dbd8Torne (Richard Coles)  DCHECK(!job_.get());  // our jobs never generate redirects
1015821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
1025821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  if (found_fallback_entry_.has_response_id()) {
1035821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    // 6.9.6, step 4: If this results in a redirect to another origin,
1045821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    // get the resource of the fallback entry.
1057d4cd473f85ac64c3747c96c277f9e506a0d2246Torne (Richard Coles)    job_ = new AppCacheURLRequestJob(request, network_delegate,
106effb81e5f8246d0db0270817048dc992db66e9fbBen Murdoch                                     storage(), host_, is_main_resource());
1075821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    DeliverAppCachedResponse(
1085821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)        found_fallback_entry_, found_cache_id_, found_group_id_,
1095821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)        found_manifest_url_,  true, found_namespace_entry_url_);
1105821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  } else if (!found_network_namespace_) {
1115821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    // 6.9.6, step 6: Fail the resource load.
1127d4cd473f85ac64c3747c96c277f9e506a0d2246Torne (Richard Coles)    job_ = new AppCacheURLRequestJob(request, network_delegate,
113effb81e5f8246d0db0270817048dc992db66e9fbBen Murdoch                                     storage(), host_, is_main_resource());
1145821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    DeliverErrorResponse();
1155821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  } else {
1165821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    // 6.9.6 step 3 and 5: Fetch the resource normally.
1175821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  }
1185821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
119868fa2fe829687343ffae624259930155e16dbd8Torne (Richard Coles)  return job_.get();
1205821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)}
1215821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
1225821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)AppCacheURLRequestJob* AppCacheRequestHandler::MaybeLoadFallbackForResponse(
1235821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    net::URLRequest* request, net::NetworkDelegate* network_delegate) {
1245821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  if (!host_ || !IsSchemeAndMethodSupported(request) || cache_entry_not_found_)
1255821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    return NULL;
1265821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  if (!found_fallback_entry_.has_response_id())
1275821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    return NULL;
1285821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
1295821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  if (request->status().status() == net::URLRequestStatus::CANCELED) {
1305821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    // 6.9.6, step 4: But not if the user canceled the download.
1315821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    return NULL;
1325821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  }
1335821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
1345821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  // We don't fallback for responses that we delivered.
135868fa2fe829687343ffae624259930155e16dbd8Torne (Richard Coles)  if (job_.get()) {
1365821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    DCHECK(!job_->is_delivering_network_response());
1375821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    return NULL;
1385821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  }
1395821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
1405821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  if (request->status().is_success()) {
1415821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    int code_major = request->GetResponseCode() / 100;
1425821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    if (code_major !=4 && code_major != 5)
1435821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)      return NULL;
1445821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
1455821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    // Servers can override the fallback behavior with a response header.
1465821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    const std::string kFallbackOverrideHeader(
1475821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)        "x-chromium-appcache-fallback-override");
1485821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    const std::string kFallbackOverrideValue(
1495821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)        "disallow-fallback");
1505821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    std::string header_value;
1515821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    request->GetResponseHeaderByName(kFallbackOverrideHeader, &header_value);
1525821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    if (header_value == kFallbackOverrideValue)
1535821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)      return NULL;
1545821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  }
1555821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
1565821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  // 6.9.6, step 4: If this results in a 4xx or 5xx status code
1575821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  // or there were network errors, get the resource of the fallback entry.
1587d4cd473f85ac64c3747c96c277f9e506a0d2246Torne (Richard Coles)  job_ = new AppCacheURLRequestJob(request, network_delegate,
159effb81e5f8246d0db0270817048dc992db66e9fbBen Murdoch                                   storage(), host_, is_main_resource());
1605821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  DeliverAppCachedResponse(
1615821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)      found_fallback_entry_, found_cache_id_, found_group_id_,
1625821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)      found_manifest_url_, true, found_namespace_entry_url_);
163868fa2fe829687343ffae624259930155e16dbd8Torne (Richard Coles)  return job_.get();
1645821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)}
1655821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
1665d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)void AppCacheRequestHandler::GetExtraResponseInfo(
1675d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)    int64* cache_id, GURL* manifest_url) {
1685d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)  if (job_.get() && job_->is_delivering_appcache_response()) {
1695d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)    *cache_id = job_->cache_id();
1705d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)    *manifest_url = job_->manifest_url();
1715d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)  }
1725d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)}
1735d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)
1745d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)void AppCacheRequestHandler::PrepareForCrossSiteTransfer(int old_process_id) {
1755d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)  if (!host_)
1765d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)    return;
1775d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)  AppCacheBackendImpl* backend = host_->service()->GetBackend(old_process_id);
1785d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)  host_for_cross_site_transfer_ = backend->TransferHostOut(host_->host_id());
1795d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)  DCHECK_EQ(host_, host_for_cross_site_transfer_.get());
1805d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)}
1815d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)
1825d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)void AppCacheRequestHandler::CompleteCrossSiteTransfer(
1835d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)    int new_process_id, int new_host_id) {
1845d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)  if (!host_for_cross_site_transfer_.get())
1855d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)    return;
1865d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)  DCHECK_EQ(host_, host_for_cross_site_transfer_.get());
1875d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)  AppCacheBackendImpl* backend = host_->service()->GetBackend(new_process_id);
1885d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)  backend->TransferHostIn(new_host_id, host_for_cross_site_transfer_.Pass());
1895d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)}
1905d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)
1915821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)void AppCacheRequestHandler::OnDestructionImminent(AppCacheHost* host) {
1925821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  storage()->CancelDelegateCallbacks(this);
1935821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  host_ = NULL;  // no need to RemoveObserver, the host is being deleted
1945821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
1955821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  // Since the host is being deleted, we don't have to complete any job
1965821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  // that is current running. It's destined for the bit bucket anyway.
197868fa2fe829687343ffae624259930155e16dbd8Torne (Richard Coles)  if (job_.get()) {
1985821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    job_->Kill();
1995821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    job_ = NULL;
2005821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  }
2015821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)}
2025821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
2035821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)void AppCacheRequestHandler::DeliverAppCachedResponse(
2045821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    const AppCacheEntry& entry, int64 cache_id, int64 group_id,
2055821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    const GURL& manifest_url,  bool is_fallback,
2065821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    const GURL& namespace_entry_url) {
207868fa2fe829687343ffae624259930155e16dbd8Torne (Richard Coles)  DCHECK(host_ && job_.get() && job_->is_waiting());
2085821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  DCHECK(entry.has_response_id());
2095821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
2105821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  if (ResourceType::IsFrame(resource_type_) && !namespace_entry_url.is_empty())
2115821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    host_->NotifyMainResourceIsNamespaceEntry(namespace_entry_url);
2125821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
2135821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  job_->DeliverAppCachedResponse(manifest_url, group_id, cache_id,
2145821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)                                 entry, is_fallback);
2155821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)}
2165821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
2175821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)void AppCacheRequestHandler::DeliverErrorResponse() {
218868fa2fe829687343ffae624259930155e16dbd8Torne (Richard Coles)  DCHECK(job_.get() && job_->is_waiting());
2195821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  job_->DeliverErrorResponse();
2205821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)}
2215821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
2225821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)void AppCacheRequestHandler::DeliverNetworkResponse() {
223868fa2fe829687343ffae624259930155e16dbd8Torne (Richard Coles)  DCHECK(job_.get() && job_->is_waiting());
2245821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  job_->DeliverNetworkResponse();
2255821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)}
2265821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
2275821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)// Main-resource handling ----------------------------------------------
2285821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
2295821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)void AppCacheRequestHandler::MaybeLoadMainResource(
2305821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    net::URLRequest* request, net::NetworkDelegate* network_delegate) {
231868fa2fe829687343ffae624259930155e16dbd8Torne (Richard Coles)  DCHECK(!job_.get());
2325821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  DCHECK(host_);
2335821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
2345821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  const AppCacheHost* spawning_host =
2355821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)      ResourceType::IsSharedWorker(resource_type_) ?
2365821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)          host_ : host_->GetSpawningHost();
2375821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  GURL preferred_manifest_url = spawning_host ?
2385821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)      spawning_host->preferred_manifest_url() : GURL();
2395821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
2405821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  // We may have to wait for our storage query to complete, but
2415821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  // this query can also complete syncrhonously.
2427d4cd473f85ac64c3747c96c277f9e506a0d2246Torne (Richard Coles)  job_ = new AppCacheURLRequestJob(request, network_delegate,
243effb81e5f8246d0db0270817048dc992db66e9fbBen Murdoch                                   storage(), host_, is_main_resource());
2445821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  storage()->FindResponseForMainRequest(
2455821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)      request->url(), preferred_manifest_url, this);
2465821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)}
2475821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
2485821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)void AppCacheRequestHandler::OnMainResponseFound(
2495821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    const GURL& url, const AppCacheEntry& entry,
2505821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    const GURL& namespace_entry_url, const AppCacheEntry& fallback_entry,
2515821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    int64 cache_id, int64 group_id, const GURL& manifest_url) {
252868fa2fe829687343ffae624259930155e16dbd8Torne (Richard Coles)  DCHECK(job_.get());
2535821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  DCHECK(host_);
2545821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  DCHECK(is_main_resource());
2555821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  DCHECK(!entry.IsForeign());
2565821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  DCHECK(!fallback_entry.IsForeign());
2575821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  DCHECK(!(entry.has_response_id() && fallback_entry.has_response_id()));
2585821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
259868fa2fe829687343ffae624259930155e16dbd8Torne (Richard Coles)  if (!job_.get())
2605821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    return;
2615821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
2625821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  AppCachePolicy* policy = host_->service()->appcache_policy();
2635821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  bool was_blocked_by_policy = !manifest_url.is_empty() && policy &&
2645821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)      !policy->CanLoadAppCache(manifest_url, host_->first_party_url());
2655821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
2665821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  if (was_blocked_by_policy) {
2675821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    if (ResourceType::IsFrame(resource_type_)) {
2685821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)      host_->NotifyMainResourceBlocked(manifest_url);
2695821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    } else {
2705821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)      DCHECK(ResourceType::IsSharedWorker(resource_type_));
2715821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)      host_->frontend()->OnContentBlocked(host_->host_id(), manifest_url);
2725821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    }
2735821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    DeliverNetworkResponse();
2745821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    return;
2755821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  }
2765821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
277f8ee788a64d60abd8f2d742a5fdedde054ecd910Torne (Richard Coles)  if (ResourceType::IsFrame(resource_type_) && cache_id != kAppCacheNoCacheId) {
2785821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    // AppCacheHost loads and holds a reference to the main resource cache
2795821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    // for two reasons, firstly to preload the cache into the working set
2805821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    // in advance of subresource loads happening, secondly to prevent the
2815821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    // AppCache from falling out of the working set on frame navigations.
2825821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    host_->LoadMainResourceCache(cache_id);
2835821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    host_->set_preferred_manifest_url(manifest_url);
2845821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  }
2855821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
2865821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  // 6.11.1 Navigating across documents, steps 10 and 14.
2875821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
2885821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  found_entry_ = entry;
2895821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  found_namespace_entry_url_ = namespace_entry_url;
2905821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  found_fallback_entry_ = fallback_entry;
2915821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  found_cache_id_ = cache_id;
2925821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  found_group_id_ = group_id;
2935821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  found_manifest_url_ = manifest_url;
2945821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  found_network_namespace_ = false;  // not applicable to main requests
2955821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
2965821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  if (found_entry_.has_response_id()) {
2975821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    DCHECK(!found_fallback_entry_.has_response_id());
2985821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    DeliverAppCachedResponse(
2995821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)        found_entry_, found_cache_id_, found_group_id_, found_manifest_url_,
3005821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)        false, found_namespace_entry_url_);
3015821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  } else {
3025821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    DeliverNetworkResponse();
3035821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  }
3045821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)}
3055821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
3065821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)// Sub-resource handling ----------------------------------------------
3075821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
3085821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)void AppCacheRequestHandler::MaybeLoadSubResource(
3095821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    net::URLRequest* request, net::NetworkDelegate* network_delegate) {
310868fa2fe829687343ffae624259930155e16dbd8Torne (Richard Coles)  DCHECK(!job_.get());
3115821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
3125821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  if (host_->is_selection_pending()) {
3135821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    // We have to wait until cache selection is complete and the
3145821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    // selected cache is loaded.
3155821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    is_waiting_for_cache_selection_ = true;
3167d4cd473f85ac64c3747c96c277f9e506a0d2246Torne (Richard Coles)    job_ = new AppCacheURLRequestJob(request, network_delegate,
317effb81e5f8246d0db0270817048dc992db66e9fbBen Murdoch                                     storage(), host_, is_main_resource());
3185821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    return;
3195821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  }
3205821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
3215821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  if (!host_->associated_cache() ||
322effb81e5f8246d0db0270817048dc992db66e9fbBen Murdoch      !host_->associated_cache()->is_complete() ||
323effb81e5f8246d0db0270817048dc992db66e9fbBen Murdoch      host_->associated_cache()->owning_group()->is_being_deleted()) {
3245821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    return;
3255821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  }
3265821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
3277d4cd473f85ac64c3747c96c277f9e506a0d2246Torne (Richard Coles)  job_ = new AppCacheURLRequestJob(request, network_delegate,
328effb81e5f8246d0db0270817048dc992db66e9fbBen Murdoch                                   storage(), host_, is_main_resource());
3295821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  ContinueMaybeLoadSubResource();
3305821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)}
3315821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
3325821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)void AppCacheRequestHandler::ContinueMaybeLoadSubResource() {
3335821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  // 6.9.6 Changes to the networking model
3345821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  // If the resource is not to be fetched using the HTTP GET mechanism or
3355821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  // equivalent ... then fetch the resource normally.
336868fa2fe829687343ffae624259930155e16dbd8Torne (Richard Coles)  DCHECK(job_.get());
337868fa2fe829687343ffae624259930155e16dbd8Torne (Richard Coles)  DCHECK(host_->associated_cache() && host_->associated_cache()->is_complete());
3385821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
3395821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  const GURL& url = job_->request()->url();
3405821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  AppCache* cache = host_->associated_cache();
3415821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  storage()->FindResponseForSubRequest(
3425821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)      host_->associated_cache(), url,
3435821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)      &found_entry_, &found_fallback_entry_, &found_network_namespace_);
3445821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
3455821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  if (found_entry_.has_response_id()) {
3465821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    // Step 2: If there's an entry, get it instead.
3475821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    DCHECK(!found_network_namespace_ &&
3485821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)           !found_fallback_entry_.has_response_id());
3495821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    found_cache_id_ = cache->cache_id();
3505821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    found_group_id_ = cache->owning_group()->group_id();
3515821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    found_manifest_url_ = cache->owning_group()->manifest_url();
3525821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    DeliverAppCachedResponse(
3535821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)        found_entry_, found_cache_id_, found_group_id_, found_manifest_url_,
3545821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)        false, GURL());
3555821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    return;
3565821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  }
3575821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
3585821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  if (found_fallback_entry_.has_response_id()) {
3595821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    // Step 4: Fetch the resource normally, if this results
3605821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    // in certain conditions, then use the fallback.
3615821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    DCHECK(!found_network_namespace_ &&
3625821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)           !found_entry_.has_response_id());
3635821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    found_cache_id_ = cache->cache_id();
3645821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    found_manifest_url_ = cache->owning_group()->manifest_url();
3655821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    DeliverNetworkResponse();
3665821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    return;
3675821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  }
3685821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
3695821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  if (found_network_namespace_) {
3705821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    // Step 3 and 5: Fetch the resource normally.
3715821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    DCHECK(!found_entry_.has_response_id() &&
3725821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)           !found_fallback_entry_.has_response_id());
3735821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    DeliverNetworkResponse();
3745821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    return;
3755821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  }
3765821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
3775821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  // Step 6: Fail the resource load.
3785821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  DeliverErrorResponse();
3795821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)}
3805821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
3815821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)void AppCacheRequestHandler::OnCacheSelectionComplete(AppCacheHost* host) {
3825821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  DCHECK(host == host_);
3835821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  if (is_main_resource())
3845821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    return;
3855821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  if (!is_waiting_for_cache_selection_)
3865821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    return;
3875821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
3885821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  is_waiting_for_cache_selection_ = false;
3895821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
3905821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  if (!host_->associated_cache() ||
3915821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)      !host_->associated_cache()->is_complete()) {
3925821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    DeliverNetworkResponse();
3935821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    return;
3945821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  }
3955821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
3965821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  ContinueMaybeLoadSubResource();
3975821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)}
3985821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
3995821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)}  // namespace appcache
400