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