Home | History | Annotate | Download | only in appcache
      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 "webkit/browser/appcache/appcache_host.h"
      6 
      7 #include "base/logging.h"
      8 #include "base/strings/string_util.h"
      9 #include "base/strings/stringprintf.h"
     10 #include "net/url_request/url_request.h"
     11 #include "webkit/browser/appcache/appcache.h"
     12 #include "webkit/browser/appcache/appcache_backend_impl.h"
     13 #include "webkit/browser/appcache/appcache_policy.h"
     14 #include "webkit/browser/appcache/appcache_request_handler.h"
     15 #include "webkit/browser/quota/quota_manager_proxy.h"
     16 
     17 namespace appcache {
     18 
     19 namespace {
     20 
     21 void FillCacheInfo(const AppCache* cache,
     22                    const GURL& manifest_url,
     23                    AppCacheStatus status, AppCacheInfo* info) {
     24   info->manifest_url = manifest_url;
     25   info->status = status;
     26 
     27   if (!cache)
     28     return;
     29 
     30   info->cache_id = cache->cache_id();
     31 
     32   if (!cache->is_complete())
     33     return;
     34 
     35   DCHECK(cache->owning_group());
     36   info->is_complete = true;
     37   info->group_id = cache->owning_group()->group_id();
     38   info->last_update_time = cache->update_time();
     39   info->creation_time = cache->owning_group()->creation_time();
     40   info->size = cache->cache_size();
     41 }
     42 
     43 }  // Anonymous namespace
     44 
     45 AppCacheHost::AppCacheHost(int host_id, AppCacheFrontend* frontend,
     46                            AppCacheServiceImpl* service)
     47     : host_id_(host_id),
     48       spawning_host_id_(kAppCacheNoHostId), spawning_process_id_(0),
     49       parent_host_id_(kAppCacheNoHostId), parent_process_id_(0),
     50       pending_main_resource_cache_id_(kAppCacheNoCacheId),
     51       pending_selected_cache_id_(kAppCacheNoCacheId),
     52       frontend_(frontend), service_(service),
     53       storage_(service->storage()),
     54       pending_callback_param_(NULL),
     55       main_resource_was_namespace_entry_(false),
     56       main_resource_blocked_(false),
     57       associated_cache_info_pending_(false) {
     58   service_->AddObserver(this);
     59 }
     60 
     61 AppCacheHost::~AppCacheHost() {
     62   service_->RemoveObserver(this);
     63   FOR_EACH_OBSERVER(Observer, observers_, OnDestructionImminent(this));
     64   if (associated_cache_.get())
     65     associated_cache_->UnassociateHost(this);
     66   if (group_being_updated_.get())
     67     group_being_updated_->RemoveUpdateObserver(this);
     68   storage()->CancelDelegateCallbacks(this);
     69   if (service()->quota_manager_proxy() && !origin_in_use_.is_empty())
     70     service()->quota_manager_proxy()->NotifyOriginNoLongerInUse(origin_in_use_);
     71 }
     72 
     73 void AppCacheHost::AddObserver(Observer* observer) {
     74   observers_.AddObserver(observer);
     75 }
     76 
     77 void AppCacheHost::RemoveObserver(Observer* observer) {
     78   observers_.RemoveObserver(observer);
     79 }
     80 
     81 void AppCacheHost::SelectCache(const GURL& document_url,
     82                                const int64 cache_document_was_loaded_from,
     83                                const GURL& manifest_url) {
     84   DCHECK(pending_start_update_callback_.is_null() &&
     85          pending_swap_cache_callback_.is_null() &&
     86          pending_get_status_callback_.is_null() &&
     87          !is_selection_pending());
     88 
     89   origin_in_use_ = document_url.GetOrigin();
     90   if (service()->quota_manager_proxy() && !origin_in_use_.is_empty())
     91     service()->quota_manager_proxy()->NotifyOriginInUse(origin_in_use_);
     92 
     93   if (main_resource_blocked_)
     94     frontend_->OnContentBlocked(host_id_,
     95                                 blocked_manifest_url_);
     96 
     97   // 6.9.6 The application cache selection algorithm.
     98   // The algorithm is started here and continues in FinishCacheSelection,
     99   // after cache or group loading is complete.
    100   // Note: Foreign entries are detected on the client side and
    101   // MarkAsForeignEntry is called in that case, so that detection
    102   // step is skipped here. See WebApplicationCacheHostImpl.cc
    103 
    104   if (cache_document_was_loaded_from != kAppCacheNoCacheId) {
    105     LoadSelectedCache(cache_document_was_loaded_from);
    106     return;
    107   }
    108 
    109   if (!manifest_url.is_empty() &&
    110       (manifest_url.GetOrigin() == document_url.GetOrigin())) {
    111     DCHECK(!first_party_url_.is_empty());
    112     AppCachePolicy* policy = service()->appcache_policy();
    113     if (policy &&
    114         !policy->CanCreateAppCache(manifest_url, first_party_url_)) {
    115       FinishCacheSelection(NULL, NULL);
    116       std::vector<int> host_ids(1, host_id_);
    117       frontend_->OnEventRaised(host_ids, APPCACHE_CHECKING_EVENT);
    118       frontend_->OnErrorEventRaised(
    119           host_ids,
    120           AppCacheErrorDetails(
    121               "Cache creation was blocked by the content policy",
    122               APPCACHE_POLICY_ERROR,
    123               GURL(),
    124               0,
    125               false /*is_cross_origin*/));
    126       frontend_->OnContentBlocked(host_id_, manifest_url);
    127       return;
    128     }
    129 
    130     // Note: The client detects if the document was not loaded using HTTP GET
    131     // and invokes SelectCache without a manifest url, so that detection step
    132     // is also skipped here. See WebApplicationCacheHostImpl.cc
    133     set_preferred_manifest_url(manifest_url);
    134     new_master_entry_url_ = document_url;
    135     LoadOrCreateGroup(manifest_url);
    136     return;
    137   }
    138 
    139   // TODO(michaeln): If there was a manifest URL, the user agent may report
    140   // to the user that it was ignored, to aid in application development.
    141   FinishCacheSelection(NULL, NULL);
    142 }
    143 
    144 void AppCacheHost::SelectCacheForWorker(int parent_process_id,
    145                                         int parent_host_id) {
    146   DCHECK(pending_start_update_callback_.is_null() &&
    147          pending_swap_cache_callback_.is_null() &&
    148          pending_get_status_callback_.is_null() &&
    149          !is_selection_pending());
    150 
    151   parent_process_id_ = parent_process_id;
    152   parent_host_id_ = parent_host_id;
    153   FinishCacheSelection(NULL, NULL);
    154 }
    155 
    156 void AppCacheHost::SelectCacheForSharedWorker(int64 appcache_id) {
    157   DCHECK(pending_start_update_callback_.is_null() &&
    158          pending_swap_cache_callback_.is_null() &&
    159          pending_get_status_callback_.is_null() &&
    160          !is_selection_pending());
    161 
    162   if (appcache_id != kAppCacheNoCacheId) {
    163     LoadSelectedCache(appcache_id);
    164     return;
    165   }
    166   FinishCacheSelection(NULL, NULL);
    167 }
    168 
    169 // TODO(michaeln): change method name to MarkEntryAsForeign for consistency
    170 void AppCacheHost::MarkAsForeignEntry(const GURL& document_url,
    171                                       int64 cache_document_was_loaded_from) {
    172   // The document url is not the resource url in the fallback case.
    173   storage()->MarkEntryAsForeign(
    174       main_resource_was_namespace_entry_ ? namespace_entry_url_ : document_url,
    175       cache_document_was_loaded_from);
    176   SelectCache(document_url, kAppCacheNoCacheId, GURL());
    177 }
    178 
    179 void AppCacheHost::GetStatusWithCallback(const GetStatusCallback& callback,
    180                                          void* callback_param) {
    181   DCHECK(pending_start_update_callback_.is_null() &&
    182          pending_swap_cache_callback_.is_null() &&
    183          pending_get_status_callback_.is_null());
    184 
    185   pending_get_status_callback_ = callback;
    186   pending_callback_param_ = callback_param;
    187   if (is_selection_pending())
    188     return;
    189 
    190   DoPendingGetStatus();
    191 }
    192 
    193 void AppCacheHost::DoPendingGetStatus() {
    194   DCHECK_EQ(false, pending_get_status_callback_.is_null());
    195 
    196   pending_get_status_callback_.Run(GetStatus(), pending_callback_param_);
    197   pending_get_status_callback_.Reset();
    198   pending_callback_param_ = NULL;
    199 }
    200 
    201 void AppCacheHost::StartUpdateWithCallback(const StartUpdateCallback& callback,
    202                                            void* callback_param) {
    203   DCHECK(pending_start_update_callback_.is_null() &&
    204          pending_swap_cache_callback_.is_null() &&
    205          pending_get_status_callback_.is_null());
    206 
    207   pending_start_update_callback_ = callback;
    208   pending_callback_param_ = callback_param;
    209   if (is_selection_pending())
    210     return;
    211 
    212   DoPendingStartUpdate();
    213 }
    214 
    215 void AppCacheHost::DoPendingStartUpdate() {
    216   DCHECK_EQ(false, pending_start_update_callback_.is_null());
    217 
    218   // 6.9.8 Application cache API
    219   bool success = false;
    220   if (associated_cache_.get() && associated_cache_->owning_group()) {
    221     AppCacheGroup* group = associated_cache_->owning_group();
    222     if (!group->is_obsolete() && !group->is_being_deleted()) {
    223       success = true;
    224       group->StartUpdate();
    225     }
    226   }
    227 
    228   pending_start_update_callback_.Run(success, pending_callback_param_);
    229   pending_start_update_callback_.Reset();
    230   pending_callback_param_ = NULL;
    231 }
    232 
    233 void AppCacheHost::SwapCacheWithCallback(const SwapCacheCallback& callback,
    234                                          void* callback_param) {
    235   DCHECK(pending_start_update_callback_.is_null() &&
    236          pending_swap_cache_callback_.is_null() &&
    237          pending_get_status_callback_.is_null());
    238 
    239   pending_swap_cache_callback_ = callback;
    240   pending_callback_param_ = callback_param;
    241   if (is_selection_pending())
    242     return;
    243 
    244   DoPendingSwapCache();
    245 }
    246 
    247 void AppCacheHost::DoPendingSwapCache() {
    248   DCHECK_EQ(false, pending_swap_cache_callback_.is_null());
    249 
    250   // 6.9.8 Application cache API
    251   bool success = false;
    252   if (associated_cache_.get() && associated_cache_->owning_group()) {
    253     if (associated_cache_->owning_group()->is_obsolete()) {
    254       success = true;
    255       AssociateNoCache(GURL());
    256     } else if (swappable_cache_.get()) {
    257       DCHECK(swappable_cache_.get() ==
    258              swappable_cache_->owning_group()->newest_complete_cache());
    259       success = true;
    260       AssociateCompleteCache(swappable_cache_.get());
    261     }
    262   }
    263 
    264   pending_swap_cache_callback_.Run(success, pending_callback_param_);
    265   pending_swap_cache_callback_.Reset();
    266   pending_callback_param_ = NULL;
    267 }
    268 
    269 void AppCacheHost::SetSpawningHostId(
    270     int spawning_process_id, int spawning_host_id) {
    271   spawning_process_id_ = spawning_process_id;
    272   spawning_host_id_ = spawning_host_id;
    273 }
    274 
    275 const AppCacheHost* AppCacheHost::GetSpawningHost() const {
    276   AppCacheBackendImpl* backend = service_->GetBackend(spawning_process_id_);
    277   return backend ? backend->GetHost(spawning_host_id_) : NULL;
    278 }
    279 
    280 AppCacheHost* AppCacheHost::GetParentAppCacheHost() const {
    281   DCHECK(is_for_dedicated_worker());
    282   AppCacheBackendImpl* backend = service_->GetBackend(parent_process_id_);
    283   return backend ? backend->GetHost(parent_host_id_) : NULL;
    284 }
    285 
    286 AppCacheRequestHandler* AppCacheHost::CreateRequestHandler(
    287     net::URLRequest* request,
    288     ResourceType::Type resource_type) {
    289   if (is_for_dedicated_worker()) {
    290     AppCacheHost* parent_host = GetParentAppCacheHost();
    291     if (parent_host)
    292       return parent_host->CreateRequestHandler(request, resource_type);
    293     return NULL;
    294   }
    295 
    296   if (AppCacheRequestHandler::IsMainResourceType(resource_type)) {
    297     // Store the first party origin so that it can be used later in SelectCache
    298     // for checking whether the creation of the appcache is allowed.
    299     first_party_url_ = request->first_party_for_cookies();
    300     return new AppCacheRequestHandler(this, resource_type);
    301   }
    302 
    303   if ((associated_cache() && associated_cache()->is_complete()) ||
    304       is_selection_pending()) {
    305     return new AppCacheRequestHandler(this, resource_type);
    306   }
    307   return NULL;
    308 }
    309 
    310 void AppCacheHost::GetResourceList(
    311     AppCacheResourceInfoVector* resource_infos) {
    312   if (associated_cache_.get() && associated_cache_->is_complete())
    313     associated_cache_->ToResourceInfoVector(resource_infos);
    314 }
    315 
    316 AppCacheStatus AppCacheHost::GetStatus() {
    317   // 6.9.8 Application cache API
    318   AppCache* cache = associated_cache();
    319   if (!cache)
    320     return APPCACHE_STATUS_UNCACHED;
    321 
    322   // A cache without an owning group represents the cache being constructed
    323   // during the application cache update process.
    324   if (!cache->owning_group())
    325     return APPCACHE_STATUS_DOWNLOADING;
    326 
    327   if (cache->owning_group()->is_obsolete())
    328     return APPCACHE_STATUS_OBSOLETE;
    329   if (cache->owning_group()->update_status() == AppCacheGroup::CHECKING)
    330     return APPCACHE_STATUS_CHECKING;
    331   if (cache->owning_group()->update_status() == AppCacheGroup::DOWNLOADING)
    332     return APPCACHE_STATUS_DOWNLOADING;
    333   if (swappable_cache_.get())
    334     return APPCACHE_STATUS_UPDATE_READY;
    335   return APPCACHE_STATUS_IDLE;
    336 }
    337 
    338 void AppCacheHost::LoadOrCreateGroup(const GURL& manifest_url) {
    339   DCHECK(manifest_url.is_valid());
    340   pending_selected_manifest_url_ = manifest_url;
    341   storage()->LoadOrCreateGroup(manifest_url, this);
    342 }
    343 
    344 void AppCacheHost::OnGroupLoaded(AppCacheGroup* group,
    345                                  const GURL& manifest_url) {
    346   DCHECK(manifest_url == pending_selected_manifest_url_);
    347   pending_selected_manifest_url_ = GURL();
    348   FinishCacheSelection(NULL, group);
    349 }
    350 
    351 void AppCacheHost::LoadSelectedCache(int64 cache_id) {
    352   DCHECK(cache_id != kAppCacheNoCacheId);
    353   pending_selected_cache_id_ = cache_id;
    354   storage()->LoadCache(cache_id, this);
    355 }
    356 
    357 void AppCacheHost::OnCacheLoaded(AppCache* cache, int64 cache_id) {
    358   if (cache_id == pending_main_resource_cache_id_) {
    359     pending_main_resource_cache_id_ = kAppCacheNoCacheId;
    360     main_resource_cache_ = cache;
    361   } else if (cache_id == pending_selected_cache_id_) {
    362     pending_selected_cache_id_ = kAppCacheNoCacheId;
    363     FinishCacheSelection(cache, NULL);
    364   }
    365 }
    366 
    367 void AppCacheHost::FinishCacheSelection(
    368     AppCache *cache, AppCacheGroup* group) {
    369   DCHECK(!associated_cache());
    370 
    371   // 6.9.6 The application cache selection algorithm
    372   if (cache) {
    373     // If document was loaded from an application cache, Associate document
    374     // with the application cache from which it was loaded. Invoke the
    375     // application cache update process for that cache and with the browsing
    376     // context being navigated.
    377     DCHECK(cache->owning_group());
    378     DCHECK(new_master_entry_url_.is_empty());
    379     DCHECK_EQ(cache->owning_group()->manifest_url(), preferred_manifest_url_);
    380     AppCacheGroup* owing_group = cache->owning_group();
    381     const char* kFormatString =
    382         "Document was loaded from Application Cache with manifest %s";
    383     frontend_->OnLogMessage(
    384         host_id_, APPCACHE_LOG_INFO,
    385         base::StringPrintf(
    386             kFormatString, owing_group->manifest_url().spec().c_str()));
    387     AssociateCompleteCache(cache);
    388     if (!owing_group->is_obsolete() && !owing_group->is_being_deleted()) {
    389       owing_group->StartUpdateWithHost(this);
    390       ObserveGroupBeingUpdated(owing_group);
    391     }
    392   } else if (group && !group->is_being_deleted()) {
    393     // If document was loaded using HTTP GET or equivalent, and, there is a
    394     // manifest URL, and manifest URL has the same origin as document.
    395     // Invoke the application cache update process for manifest URL, with
    396     // the browsing context being navigated, and with document and the
    397     // resource from which document was loaded as the new master resourse.
    398     DCHECK(!group->is_obsolete());
    399     DCHECK(new_master_entry_url_.is_valid());
    400     DCHECK_EQ(group->manifest_url(), preferred_manifest_url_);
    401     const char* kFormatString = group->HasCache() ?
    402         "Adding master entry to Application Cache with manifest %s" :
    403         "Creating Application Cache with manifest %s";
    404     frontend_->OnLogMessage(
    405         host_id_, APPCACHE_LOG_INFO,
    406         base::StringPrintf(kFormatString,
    407                            group->manifest_url().spec().c_str()));
    408     // The UpdateJob may produce one for us later.
    409     AssociateNoCache(preferred_manifest_url_);
    410     group->StartUpdateWithNewMasterEntry(this, new_master_entry_url_);
    411     ObserveGroupBeingUpdated(group);
    412   } else {
    413     // Otherwise, the Document is not associated with any application cache.
    414     new_master_entry_url_ = GURL();
    415     AssociateNoCache(GURL());
    416   }
    417 
    418   // Respond to pending callbacks now that we have a selection.
    419   if (!pending_get_status_callback_.is_null())
    420     DoPendingGetStatus();
    421   else if (!pending_start_update_callback_.is_null())
    422     DoPendingStartUpdate();
    423   else if (!pending_swap_cache_callback_.is_null())
    424     DoPendingSwapCache();
    425 
    426   FOR_EACH_OBSERVER(Observer, observers_, OnCacheSelectionComplete(this));
    427 }
    428 
    429 void AppCacheHost::OnServiceReinitialized(
    430     AppCacheStorageReference* old_storage_ref) {
    431   // We continue to use the disabled instance, but arrange for its
    432   // deletion when its no longer needed.
    433   if (old_storage_ref->storage() == storage())
    434     disabled_storage_reference_ = old_storage_ref;
    435 }
    436 
    437 void AppCacheHost::ObserveGroupBeingUpdated(AppCacheGroup* group) {
    438   DCHECK(!group_being_updated_.get());
    439   group_being_updated_ = group;
    440   newest_cache_of_group_being_updated_ = group->newest_complete_cache();
    441   group->AddUpdateObserver(this);
    442 }
    443 
    444 void AppCacheHost::OnUpdateComplete(AppCacheGroup* group) {
    445   DCHECK_EQ(group, group_being_updated_);
    446   group->RemoveUpdateObserver(this);
    447 
    448   // Add a reference to the newest complete cache.
    449   SetSwappableCache(group);
    450 
    451   group_being_updated_ = NULL;
    452   newest_cache_of_group_being_updated_ = NULL;
    453 
    454   if (associated_cache_info_pending_ && associated_cache_.get() &&
    455       associated_cache_->is_complete()) {
    456     AppCacheInfo info;
    457     FillCacheInfo(
    458         associated_cache_.get(), preferred_manifest_url_, GetStatus(), &info);
    459     associated_cache_info_pending_ = false;
    460     frontend_->OnCacheSelected(host_id_, info);
    461   }
    462 }
    463 
    464 void AppCacheHost::SetSwappableCache(AppCacheGroup* group) {
    465   if (!group) {
    466     swappable_cache_ = NULL;
    467   } else {
    468     AppCache* new_cache = group->newest_complete_cache();
    469     if (new_cache != associated_cache_.get())
    470       swappable_cache_ = new_cache;
    471     else
    472       swappable_cache_ = NULL;
    473   }
    474 }
    475 
    476 void AppCacheHost::LoadMainResourceCache(int64 cache_id) {
    477   DCHECK(cache_id != kAppCacheNoCacheId);
    478   if (pending_main_resource_cache_id_ == cache_id ||
    479       (main_resource_cache_.get() &&
    480        main_resource_cache_->cache_id() == cache_id)) {
    481     return;
    482   }
    483   pending_main_resource_cache_id_ = cache_id;
    484   storage()->LoadCache(cache_id, this);
    485 }
    486 
    487 void AppCacheHost::NotifyMainResourceIsNamespaceEntry(
    488     const GURL& namespace_entry_url) {
    489   main_resource_was_namespace_entry_ = true;
    490   namespace_entry_url_ = namespace_entry_url;
    491 }
    492 
    493 void AppCacheHost::NotifyMainResourceBlocked(const GURL& manifest_url) {
    494   main_resource_blocked_ = true;
    495   blocked_manifest_url_ = manifest_url;
    496 }
    497 
    498 void AppCacheHost::PrepareForTransfer() {
    499   // This can only happen prior to the document having been loaded.
    500   DCHECK(!associated_cache());
    501   DCHECK(!is_selection_pending());
    502   DCHECK(!group_being_updated_);
    503   host_id_ = kAppCacheNoHostId;
    504   frontend_ = NULL;
    505 }
    506 
    507 void AppCacheHost::CompleteTransfer(int host_id, AppCacheFrontend* frontend) {
    508   host_id_ = host_id;
    509   frontend_ = frontend;
    510 }
    511 
    512 void AppCacheHost::AssociateNoCache(const GURL& manifest_url) {
    513   // manifest url can be empty.
    514   AssociateCacheHelper(NULL, manifest_url);
    515 }
    516 
    517 void AppCacheHost::AssociateIncompleteCache(AppCache* cache,
    518                                             const GURL& manifest_url) {
    519   DCHECK(cache && !cache->is_complete());
    520   DCHECK(!manifest_url.is_empty());
    521   AssociateCacheHelper(cache, manifest_url);
    522 }
    523 
    524 void AppCacheHost::AssociateCompleteCache(AppCache* cache) {
    525   DCHECK(cache && cache->is_complete());
    526   AssociateCacheHelper(cache, cache->owning_group()->manifest_url());
    527 }
    528 
    529 void AppCacheHost::AssociateCacheHelper(AppCache* cache,
    530                                         const GURL& manifest_url) {
    531   if (associated_cache_.get()) {
    532     associated_cache_->UnassociateHost(this);
    533   }
    534 
    535   associated_cache_ = cache;
    536   SetSwappableCache(cache ? cache->owning_group() : NULL);
    537   associated_cache_info_pending_ = cache && !cache->is_complete();
    538   AppCacheInfo info;
    539   if (cache)
    540     cache->AssociateHost(this);
    541 
    542   FillCacheInfo(cache, manifest_url, GetStatus(), &info);
    543   frontend_->OnCacheSelected(host_id_, info);
    544 }
    545 
    546 }  // namespace appcache
    547