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