Home | History | Annotate | Download | only in appcache
      1 /*
      2  * Copyright (C) 2008, 2009 Apple Inc. All Rights Reserved.
      3  *
      4  * Redistribution and use in source and binary forms, with or without
      5  * modification, are permitted provided that the following conditions
      6  * are met:
      7  * 1. Redistributions of source code must retain the above copyright
      8  *    notice, this list of conditions and the following disclaimer.
      9  * 2. Redistributions in binary form must reproduce the above copyright
     10  *    notice, this list of conditions and the following disclaimer in the
     11  *    documentation and/or other materials provided with the distribution.
     12  *
     13  * THIS SOFTWARE IS PROVIDED BY APPLE INC. ``AS IS'' AND ANY
     14  * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
     15  * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
     16  * PURPOSE ARE DISCLAIMED.  IN NO EVENT SHALL APPLE INC. OR
     17  * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
     18  * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
     19  * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
     20  * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY
     21  * OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
     22  * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
     23  * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
     24  */
     25 
     26 #include "config.h"
     27 #include "ApplicationCacheGroup.h"
     28 
     29 #if ENABLE(OFFLINE_WEB_APPLICATIONS)
     30 
     31 #include "ApplicationCache.h"
     32 #include "ApplicationCacheHost.h"
     33 #include "ApplicationCacheResource.h"
     34 #include "ApplicationCacheStorage.h"
     35 #include "Chrome.h"
     36 #include "ChromeClient.h"
     37 #include "DocumentLoader.h"
     38 #include "DOMApplicationCache.h"
     39 #include "DOMWindow.h"
     40 #include "Frame.h"
     41 #include "FrameLoader.h"
     42 #include "MainResourceLoader.h"
     43 #include "ManifestParser.h"
     44 #include "Page.h"
     45 #include "Settings.h"
     46 #include <wtf/HashMap.h>
     47 
     48 namespace WebCore {
     49 
     50 ApplicationCacheGroup::ApplicationCacheGroup(const KURL& manifestURL, bool isCopy)
     51     : m_manifestURL(manifestURL)
     52     , m_updateStatus(Idle)
     53     , m_downloadingPendingMasterResourceLoadersCount(0)
     54     , m_frame(0)
     55     , m_storageID(0)
     56     , m_isObsolete(false)
     57     , m_completionType(None)
     58     , m_isCopy(isCopy)
     59     , m_calledReachedMaxAppCacheSize(false)
     60 {
     61 }
     62 
     63 ApplicationCacheGroup::~ApplicationCacheGroup()
     64 {
     65     if (m_isCopy) {
     66         ASSERT(m_newestCache);
     67         ASSERT(m_caches.size() == 1);
     68         ASSERT(m_caches.contains(m_newestCache.get()));
     69         ASSERT(!m_cacheBeingUpdated);
     70         ASSERT(m_associatedDocumentLoaders.isEmpty());
     71         ASSERT(m_pendingMasterResourceLoaders.isEmpty());
     72         ASSERT(m_newestCache->group() == this);
     73 
     74         return;
     75     }
     76 
     77     ASSERT(!m_newestCache);
     78     ASSERT(m_caches.isEmpty());
     79 
     80     stopLoading();
     81 
     82     cacheStorage().cacheGroupDestroyed(this);
     83 }
     84 
     85 ApplicationCache* ApplicationCacheGroup::cacheForMainRequest(const ResourceRequest& request, DocumentLoader*)
     86 {
     87     if (!ApplicationCache::requestIsHTTPOrHTTPSGet(request))
     88         return 0;
     89 
     90     KURL url(request.url());
     91     if (url.hasFragmentIdentifier())
     92         url.removeFragmentIdentifier();
     93 
     94     if (ApplicationCacheGroup* group = cacheStorage().cacheGroupForURL(url)) {
     95         ASSERT(group->newestCache());
     96         ASSERT(!group->isObsolete());
     97 
     98         return group->newestCache();
     99     }
    100 
    101     return 0;
    102 }
    103 
    104 ApplicationCache* ApplicationCacheGroup::fallbackCacheForMainRequest(const ResourceRequest& request, DocumentLoader*)
    105 {
    106     if (!ApplicationCache::requestIsHTTPOrHTTPSGet(request))
    107         return 0;
    108 
    109     KURL url(request.url());
    110     if (url.hasFragmentIdentifier())
    111         url.removeFragmentIdentifier();
    112 
    113     if (ApplicationCacheGroup* group = cacheStorage().fallbackCacheGroupForURL(url)) {
    114         ASSERT(group->newestCache());
    115         ASSERT(!group->isObsolete());
    116 
    117         return group->newestCache();
    118     }
    119 
    120     return 0;
    121 }
    122 
    123 void ApplicationCacheGroup::selectCache(Frame* frame, const KURL& passedManifestURL)
    124 {
    125     ASSERT(frame && frame->page());
    126 
    127     if (!frame->settings()->offlineWebApplicationCacheEnabled())
    128         return;
    129 
    130     DocumentLoader* documentLoader = frame->loader()->documentLoader();
    131     ASSERT(!documentLoader->applicationCacheHost()->applicationCache());
    132 
    133     if (passedManifestURL.isNull()) {
    134         selectCacheWithoutManifestURL(frame);
    135         return;
    136     }
    137 
    138     KURL manifestURL(passedManifestURL);
    139     if (manifestURL.hasFragmentIdentifier())
    140         manifestURL.removeFragmentIdentifier();
    141 
    142     ApplicationCache* mainResourceCache = documentLoader->applicationCacheHost()->mainResourceApplicationCache();
    143 
    144     if (mainResourceCache) {
    145         if (manifestURL == mainResourceCache->group()->m_manifestURL) {
    146             mainResourceCache->group()->associateDocumentLoaderWithCache(documentLoader, mainResourceCache);
    147             mainResourceCache->group()->update(frame, ApplicationCacheUpdateWithBrowsingContext);
    148         } else {
    149             // The main resource was loaded from cache, so the cache must have an entry for it. Mark it as foreign.
    150             KURL documentURL(documentLoader->url());
    151             if (documentURL.hasFragmentIdentifier())
    152                 documentURL.removeFragmentIdentifier();
    153             ApplicationCacheResource* resource = mainResourceCache->resourceForURL(documentURL);
    154             bool inStorage = resource->storageID();
    155             resource->addType(ApplicationCacheResource::Foreign);
    156             if (inStorage)
    157                 cacheStorage().storeUpdatedType(resource, mainResourceCache);
    158 
    159             // Restart the current navigation from the top of the navigation algorithm, undoing any changes that were made
    160             // as part of the initial load.
    161             // The navigation will not result in the same resource being loaded, because "foreign" entries are never picked during navigation.
    162             frame->redirectScheduler()->scheduleLocationChange(documentLoader->url(), frame->loader()->referrer(), true);
    163         }
    164 
    165         return;
    166     }
    167 
    168     // The resource was loaded from the network, check if it is a HTTP/HTTPS GET.
    169     const ResourceRequest& request = frame->loader()->activeDocumentLoader()->request();
    170 
    171     if (!ApplicationCache::requestIsHTTPOrHTTPSGet(request))
    172         return;
    173 
    174     // Check that the resource URL has the same scheme/host/port as the manifest URL.
    175     if (!protocolHostAndPortAreEqual(manifestURL, request.url()))
    176         return;
    177 
    178     // Don't change anything on disk if private browsing is enabled.
    179     if (!frame->settings() || frame->settings()->privateBrowsingEnabled()) {
    180         postListenerTask(ApplicationCacheHost::CHECKING_EVENT, documentLoader);
    181         postListenerTask(ApplicationCacheHost::ERROR_EVENT, documentLoader);
    182         return;
    183     }
    184 
    185     ApplicationCacheGroup* group = cacheStorage().findOrCreateCacheGroup(manifestURL);
    186 
    187     documentLoader->applicationCacheHost()->setCandidateApplicationCacheGroup(group);
    188     group->m_pendingMasterResourceLoaders.add(documentLoader);
    189     group->m_downloadingPendingMasterResourceLoadersCount++;
    190 
    191     ASSERT(!group->m_cacheBeingUpdated || group->m_updateStatus != Idle);
    192     group->update(frame, ApplicationCacheUpdateWithBrowsingContext);
    193 }
    194 
    195 void ApplicationCacheGroup::selectCacheWithoutManifestURL(Frame* frame)
    196 {
    197     if (!frame->settings()->offlineWebApplicationCacheEnabled())
    198         return;
    199 
    200     DocumentLoader* documentLoader = frame->loader()->documentLoader();
    201     ASSERT(!documentLoader->applicationCacheHost()->applicationCache());
    202 
    203     ApplicationCache* mainResourceCache = documentLoader->applicationCacheHost()->mainResourceApplicationCache();
    204 
    205     if (mainResourceCache) {
    206         mainResourceCache->group()->associateDocumentLoaderWithCache(documentLoader, mainResourceCache);
    207         mainResourceCache->group()->update(frame, ApplicationCacheUpdateWithBrowsingContext);
    208     }
    209 }
    210 
    211 void ApplicationCacheGroup::finishedLoadingMainResource(DocumentLoader* loader)
    212 {
    213     ASSERT(m_pendingMasterResourceLoaders.contains(loader));
    214     ASSERT(m_completionType == None || m_pendingEntries.isEmpty());
    215     KURL url = loader->url();
    216     if (url.hasFragmentIdentifier())
    217         url.removeFragmentIdentifier();
    218 
    219     switch (m_completionType) {
    220     case None:
    221         // The main resource finished loading before the manifest was ready. It will be handled via dispatchMainResources() later.
    222         return;
    223     case NoUpdate:
    224         ASSERT(!m_cacheBeingUpdated);
    225         associateDocumentLoaderWithCache(loader, m_newestCache.get());
    226 
    227         if (ApplicationCacheResource* resource = m_newestCache->resourceForURL(url)) {
    228             if (!(resource->type() & ApplicationCacheResource::Master)) {
    229                 resource->addType(ApplicationCacheResource::Master);
    230                 ASSERT(!resource->storageID());
    231             }
    232         } else
    233             m_newestCache->addResource(ApplicationCacheResource::create(url, loader->response(), ApplicationCacheResource::Master, loader->mainResourceData()));
    234 
    235         break;
    236     case Failure:
    237         // Cache update has been a failure, so there is no reason to keep the document associated with the incomplete cache
    238         // (its main resource was not cached yet, so it is likely that the application changed significantly server-side).
    239         ASSERT(!m_cacheBeingUpdated); // Already cleared out by stopLoading().
    240         loader->applicationCacheHost()->setApplicationCache(0); // Will unset candidate, too.
    241         m_associatedDocumentLoaders.remove(loader);
    242         postListenerTask(ApplicationCacheHost::ERROR_EVENT, loader);
    243         break;
    244     case Completed:
    245         ASSERT(m_associatedDocumentLoaders.contains(loader));
    246 
    247         if (ApplicationCacheResource* resource = m_cacheBeingUpdated->resourceForURL(url)) {
    248             if (!(resource->type() & ApplicationCacheResource::Master)) {
    249                 resource->addType(ApplicationCacheResource::Master);
    250                 ASSERT(!resource->storageID());
    251             }
    252         } else
    253             m_cacheBeingUpdated->addResource(ApplicationCacheResource::create(url, loader->response(), ApplicationCacheResource::Master, loader->mainResourceData()));
    254         // The "cached" event will be posted to all associated documents once update is complete.
    255         break;
    256     }
    257 
    258     m_downloadingPendingMasterResourceLoadersCount--;
    259     checkIfLoadIsComplete();
    260 }
    261 
    262 void ApplicationCacheGroup::failedLoadingMainResource(DocumentLoader* loader)
    263 {
    264     ASSERT(m_pendingMasterResourceLoaders.contains(loader));
    265     ASSERT(m_completionType == None || m_pendingEntries.isEmpty());
    266 
    267     switch (m_completionType) {
    268     case None:
    269         // The main resource finished loading before the manifest was ready. It will be handled via dispatchMainResources() later.
    270         return;
    271     case NoUpdate:
    272         ASSERT(!m_cacheBeingUpdated);
    273 
    274         // The manifest didn't change, and we have a relevant cache - but the main resource download failed mid-way, so it cannot be stored to the cache,
    275         // and the loader does not get associated to it. If there are other main resources being downloaded for this cache group, they may still succeed.
    276         postListenerTask(ApplicationCacheHost::ERROR_EVENT, loader);
    277 
    278         break;
    279     case Failure:
    280         // Cache update failed, too.
    281         ASSERT(!m_cacheBeingUpdated); // Already cleared out by stopLoading().
    282         ASSERT(!loader->applicationCacheHost()->applicationCache() || loader->applicationCacheHost()->applicationCache() == m_cacheBeingUpdated);
    283 
    284         loader->applicationCacheHost()->setApplicationCache(0); // Will unset candidate, too.
    285         m_associatedDocumentLoaders.remove(loader);
    286         postListenerTask(ApplicationCacheHost::ERROR_EVENT, loader);
    287         break;
    288     case Completed:
    289         // The cache manifest didn't list this main resource, and all cache entries were already updated successfully - but the main resource failed to load,
    290         // so it cannot be stored to the cache. If there are other main resources being downloaded for this cache group, they may still succeed.
    291         ASSERT(m_associatedDocumentLoaders.contains(loader));
    292         ASSERT(loader->applicationCacheHost()->applicationCache() == m_cacheBeingUpdated);
    293         ASSERT(!loader->applicationCacheHost()->candidateApplicationCacheGroup());
    294         m_associatedDocumentLoaders.remove(loader);
    295         loader->applicationCacheHost()->setApplicationCache(0);
    296 
    297         postListenerTask(ApplicationCacheHost::ERROR_EVENT, loader);
    298 
    299         break;
    300     }
    301 
    302     m_downloadingPendingMasterResourceLoadersCount--;
    303     checkIfLoadIsComplete();
    304 }
    305 
    306 void ApplicationCacheGroup::stopLoading()
    307 {
    308     if (m_manifestHandle) {
    309         ASSERT(!m_currentHandle);
    310 
    311         m_manifestHandle->setClient(0);
    312         m_manifestHandle->cancel();
    313         m_manifestHandle = 0;
    314     }
    315 
    316     if (m_currentHandle) {
    317         ASSERT(!m_manifestHandle);
    318         ASSERT(m_cacheBeingUpdated);
    319 
    320         m_currentHandle->setClient(0);
    321         m_currentHandle->cancel();
    322         m_currentHandle = 0;
    323     }
    324 
    325     m_cacheBeingUpdated = 0;
    326     m_pendingEntries.clear();
    327 }
    328 
    329 void ApplicationCacheGroup::disassociateDocumentLoader(DocumentLoader* loader)
    330 {
    331     HashSet<DocumentLoader*>::iterator it = m_associatedDocumentLoaders.find(loader);
    332     if (it != m_associatedDocumentLoaders.end())
    333         m_associatedDocumentLoaders.remove(it);
    334 
    335     m_pendingMasterResourceLoaders.remove(loader);
    336 
    337     loader->applicationCacheHost()->setApplicationCache(0); // Will set candidate to 0, too.
    338 
    339     if (!m_associatedDocumentLoaders.isEmpty() || !m_pendingMasterResourceLoaders.isEmpty())
    340         return;
    341 
    342     if (m_caches.isEmpty()) {
    343         // There is an initial cache attempt in progress.
    344         ASSERT(!m_newestCache);
    345         // Delete ourselves, causing the cache attempt to be stopped.
    346         delete this;
    347         return;
    348     }
    349 
    350     ASSERT(m_caches.contains(m_newestCache.get()));
    351 
    352     // Release our reference to the newest cache. This could cause us to be deleted.
    353     // Any ongoing updates will be stopped from destructor.
    354     m_newestCache.release();
    355 }
    356 
    357 void ApplicationCacheGroup::cacheDestroyed(ApplicationCache* cache)
    358 {
    359     if (!m_caches.contains(cache))
    360         return;
    361 
    362     m_caches.remove(cache);
    363 
    364     if (m_caches.isEmpty()) {
    365         ASSERT(m_associatedDocumentLoaders.isEmpty());
    366         ASSERT(m_pendingMasterResourceLoaders.isEmpty());
    367         delete this;
    368     }
    369 }
    370 
    371 void ApplicationCacheGroup::setNewestCache(PassRefPtr<ApplicationCache> newestCache)
    372 {
    373     m_newestCache = newestCache;
    374 
    375     m_caches.add(m_newestCache.get());
    376     m_newestCache->setGroup(this);
    377 }
    378 
    379 void ApplicationCacheGroup::makeObsolete()
    380 {
    381     if (isObsolete())
    382         return;
    383 
    384     m_isObsolete = true;
    385     cacheStorage().cacheGroupMadeObsolete(this);
    386     ASSERT(!m_storageID);
    387 }
    388 
    389 void ApplicationCacheGroup::update(Frame* frame, ApplicationCacheUpdateOption updateOption)
    390 {
    391     if (m_updateStatus == Checking || m_updateStatus == Downloading) {
    392         if (updateOption == ApplicationCacheUpdateWithBrowsingContext) {
    393             postListenerTask(ApplicationCacheHost::CHECKING_EVENT, frame->loader()->documentLoader());
    394             if (m_updateStatus == Downloading)
    395                 postListenerTask(ApplicationCacheHost::DOWNLOADING_EVENT, frame->loader()->documentLoader());
    396         }
    397         return;
    398     }
    399 
    400     // Don't change anything on disk if private browsing is enabled.
    401     if (!frame->settings() || frame->settings()->privateBrowsingEnabled()) {
    402         ASSERT(m_pendingMasterResourceLoaders.isEmpty());
    403         ASSERT(m_pendingEntries.isEmpty());
    404         ASSERT(!m_cacheBeingUpdated);
    405         postListenerTask(ApplicationCacheHost::CHECKING_EVENT, frame->loader()->documentLoader());
    406         postListenerTask(ApplicationCacheHost::NOUPDATE_EVENT, frame->loader()->documentLoader());
    407         return;
    408     }
    409 
    410     ASSERT(!m_frame);
    411     m_frame = frame;
    412 
    413     m_updateStatus = Checking;
    414 
    415     postListenerTask(ApplicationCacheHost::CHECKING_EVENT, m_associatedDocumentLoaders);
    416     if (!m_newestCache) {
    417         ASSERT(updateOption == ApplicationCacheUpdateWithBrowsingContext);
    418         postListenerTask(ApplicationCacheHost::CHECKING_EVENT, frame->loader()->documentLoader());
    419     }
    420 
    421     ASSERT(!m_manifestHandle);
    422     ASSERT(!m_manifestResource);
    423     ASSERT(m_completionType == None);
    424 
    425     // FIXME: Handle defer loading
    426     m_manifestHandle = createResourceHandle(m_manifestURL, m_newestCache ? m_newestCache->manifestResource() : 0);
    427 }
    428 
    429 PassRefPtr<ResourceHandle> ApplicationCacheGroup::createResourceHandle(const KURL& url, ApplicationCacheResource* newestCachedResource)
    430 {
    431     ResourceRequest request(url);
    432     m_frame->loader()->applyUserAgent(request);
    433     request.setHTTPHeaderField("Cache-Control", "max-age=0");
    434 
    435     if (newestCachedResource) {
    436         const String& lastModified = newestCachedResource->response().httpHeaderField("Last-Modified");
    437         const String& eTag = newestCachedResource->response().httpHeaderField("ETag");
    438         if (!lastModified.isEmpty() || !eTag.isEmpty()) {
    439             if (!lastModified.isEmpty())
    440                 request.setHTTPHeaderField("If-Modified-Since", lastModified);
    441             if (!eTag.isEmpty())
    442                 request.setHTTPHeaderField("If-None-Match", eTag);
    443         }
    444     }
    445 
    446     return ResourceHandle::create(request, this, m_frame, false, true, false);
    447 }
    448 
    449 void ApplicationCacheGroup::didReceiveResponse(ResourceHandle* handle, const ResourceResponse& response)
    450 {
    451     if (handle == m_manifestHandle) {
    452         didReceiveManifestResponse(response);
    453         return;
    454     }
    455 
    456     ASSERT(handle == m_currentHandle);
    457 
    458     KURL url(handle->request().url());
    459     if (url.hasFragmentIdentifier())
    460         url.removeFragmentIdentifier();
    461 
    462     ASSERT(!m_currentResource);
    463     ASSERT(m_pendingEntries.contains(url));
    464 
    465     unsigned type = m_pendingEntries.get(url);
    466 
    467     // If this is an initial cache attempt, we should not get master resources delivered here.
    468     if (!m_newestCache)
    469         ASSERT(!(type & ApplicationCacheResource::Master));
    470 
    471     if (m_newestCache && response.httpStatusCode() == 304) { // Not modified.
    472         ApplicationCacheResource* newestCachedResource = m_newestCache->resourceForURL(url);
    473         if (newestCachedResource) {
    474             m_cacheBeingUpdated->addResource(ApplicationCacheResource::create(url, newestCachedResource->response(), type, newestCachedResource->data()));
    475             m_pendingEntries.remove(m_currentHandle->request().url());
    476             m_currentHandle->cancel();
    477             m_currentHandle = 0;
    478             // Load the next resource, if any.
    479             startLoadingEntry();
    480             return;
    481         }
    482         // The server could return 304 for an unconditional request - in this case, we handle the response as a normal error.
    483     }
    484 
    485     if (response.httpStatusCode() / 100 != 2 || response.url() != m_currentHandle->request().url()) {
    486         if ((type & ApplicationCacheResource::Explicit) || (type & ApplicationCacheResource::Fallback)) {
    487             // Note that cacheUpdateFailed() can cause the cache group to be deleted.
    488             cacheUpdateFailed();
    489         } else if (response.httpStatusCode() == 404 || response.httpStatusCode() == 410) {
    490             // Skip this resource. It is dropped from the cache.
    491             m_currentHandle->cancel();
    492             m_currentHandle = 0;
    493             m_pendingEntries.remove(url);
    494             // Load the next resource, if any.
    495             startLoadingEntry();
    496         } else {
    497             // Copy the resource and its metadata from the newest application cache in cache group whose completeness flag is complete, and act
    498             // as if that was the fetched resource, ignoring the resource obtained from the network.
    499             ASSERT(m_newestCache);
    500             ApplicationCacheResource* newestCachedResource = m_newestCache->resourceForURL(handle->request().url());
    501             ASSERT(newestCachedResource);
    502             m_cacheBeingUpdated->addResource(ApplicationCacheResource::create(url, newestCachedResource->response(), type, newestCachedResource->data()));
    503             m_pendingEntries.remove(m_currentHandle->request().url());
    504             m_currentHandle->cancel();
    505             m_currentHandle = 0;
    506             // Load the next resource, if any.
    507             startLoadingEntry();
    508         }
    509         return;
    510     }
    511 
    512     m_currentResource = ApplicationCacheResource::create(url, response, type);
    513 }
    514 
    515 void ApplicationCacheGroup::didReceiveData(ResourceHandle* handle, const char* data, int length, int)
    516 {
    517     if (handle == m_manifestHandle) {
    518         didReceiveManifestData(data, length);
    519         return;
    520     }
    521 
    522     ASSERT(handle == m_currentHandle);
    523 
    524     ASSERT(m_currentResource);
    525     m_currentResource->data()->append(data, length);
    526 }
    527 
    528 void ApplicationCacheGroup::didFinishLoading(ResourceHandle* handle)
    529 {
    530     if (handle == m_manifestHandle) {
    531         didFinishLoadingManifest();
    532         return;
    533     }
    534 
    535     ASSERT(m_currentHandle == handle);
    536     ASSERT(m_pendingEntries.contains(handle->request().url()));
    537 
    538     m_pendingEntries.remove(handle->request().url());
    539 
    540     ASSERT(m_cacheBeingUpdated);
    541 
    542     m_cacheBeingUpdated->addResource(m_currentResource.release());
    543     m_currentHandle = 0;
    544 
    545     // Load the next resource, if any.
    546     startLoadingEntry();
    547 }
    548 
    549 void ApplicationCacheGroup::didFail(ResourceHandle* handle, const ResourceError&)
    550 {
    551     if (handle == m_manifestHandle) {
    552         cacheUpdateFailed();
    553         return;
    554     }
    555 
    556     unsigned type = m_currentResource ? m_currentResource->type() : m_pendingEntries.get(handle->request().url());
    557     KURL url(handle->request().url());
    558     if (url.hasFragmentIdentifier())
    559         url.removeFragmentIdentifier();
    560 
    561     ASSERT(!m_currentResource || !m_pendingEntries.contains(url));
    562     m_currentResource = 0;
    563     m_pendingEntries.remove(url);
    564 
    565     if ((type & ApplicationCacheResource::Explicit) || (type & ApplicationCacheResource::Fallback)) {
    566         // Note that cacheUpdateFailed() can cause the cache group to be deleted.
    567         cacheUpdateFailed();
    568     } else {
    569         // Copy the resource and its metadata from the newest application cache in cache group whose completeness flag is complete, and act
    570         // as if that was the fetched resource, ignoring the resource obtained from the network.
    571         ASSERT(m_newestCache);
    572         ApplicationCacheResource* newestCachedResource = m_newestCache->resourceForURL(url);
    573         ASSERT(newestCachedResource);
    574         m_cacheBeingUpdated->addResource(ApplicationCacheResource::create(url, newestCachedResource->response(), type, newestCachedResource->data()));
    575         // Load the next resource, if any.
    576         startLoadingEntry();
    577     }
    578 }
    579 
    580 void ApplicationCacheGroup::didReceiveManifestResponse(const ResourceResponse& response)
    581 {
    582     ASSERT(!m_manifestResource);
    583     ASSERT(m_manifestHandle);
    584 
    585     if (response.httpStatusCode() == 404 || response.httpStatusCode() == 410) {
    586         manifestNotFound();
    587         return;
    588     }
    589 
    590     if (response.httpStatusCode() == 304)
    591         return;
    592 
    593     if (response.httpStatusCode() / 100 != 2 || response.url() != m_manifestHandle->request().url() || !equalIgnoringCase(response.mimeType(), "text/cache-manifest")) {
    594         cacheUpdateFailed();
    595         return;
    596     }
    597 
    598     m_manifestResource = ApplicationCacheResource::create(m_manifestHandle->request().url(), response,
    599                                                           ApplicationCacheResource::Manifest);
    600 }
    601 
    602 void ApplicationCacheGroup::didReceiveManifestData(const char* data, int length)
    603 {
    604     if (m_manifestResource)
    605         m_manifestResource->data()->append(data, length);
    606 }
    607 
    608 void ApplicationCacheGroup::didFinishLoadingManifest()
    609 {
    610     bool isUpgradeAttempt = m_newestCache;
    611 
    612     if (!isUpgradeAttempt && !m_manifestResource) {
    613         // The server returned 304 Not Modified even though we didn't send a conditional request.
    614         cacheUpdateFailed();
    615         return;
    616     }
    617 
    618     m_manifestHandle = 0;
    619 
    620     // Check if the manifest was not modified.
    621     if (isUpgradeAttempt) {
    622         ApplicationCacheResource* newestManifest = m_newestCache->manifestResource();
    623         ASSERT(newestManifest);
    624 
    625         if (!m_manifestResource || // The resource will be null if HTTP response was 304 Not Modified.
    626             (newestManifest->data()->size() == m_manifestResource->data()->size() && !memcmp(newestManifest->data()->data(), m_manifestResource->data()->data(), newestManifest->data()->size()))) {
    627 
    628             m_completionType = NoUpdate;
    629             m_manifestResource = 0;
    630             deliverDelayedMainResources();
    631 
    632             return;
    633         }
    634     }
    635 
    636     Manifest manifest;
    637     if (!parseManifest(m_manifestURL, m_manifestResource->data()->data(), m_manifestResource->data()->size(), manifest)) {
    638         cacheUpdateFailed();
    639         return;
    640     }
    641 
    642     ASSERT(!m_cacheBeingUpdated);
    643     m_cacheBeingUpdated = ApplicationCache::create();
    644     m_cacheBeingUpdated->setGroup(this);
    645 
    646     HashSet<DocumentLoader*>::const_iterator masterEnd = m_pendingMasterResourceLoaders.end();
    647     for (HashSet<DocumentLoader*>::const_iterator iter = m_pendingMasterResourceLoaders.begin(); iter != masterEnd; ++iter)
    648         associateDocumentLoaderWithCache(*iter, m_cacheBeingUpdated.get());
    649 
    650     // We have the manifest, now download the resources.
    651     m_updateStatus = Downloading;
    652 
    653     postListenerTask(ApplicationCacheHost::DOWNLOADING_EVENT, m_associatedDocumentLoaders);
    654 
    655     ASSERT(m_pendingEntries.isEmpty());
    656 
    657     if (isUpgradeAttempt) {
    658         ApplicationCache::ResourceMap::const_iterator end = m_newestCache->end();
    659         for (ApplicationCache::ResourceMap::const_iterator it = m_newestCache->begin(); it != end; ++it) {
    660             unsigned type = it->second->type();
    661             if (type & ApplicationCacheResource::Master)
    662                 addEntry(it->first, type);
    663         }
    664     }
    665 
    666     HashSet<String>::const_iterator end = manifest.explicitURLs.end();
    667     for (HashSet<String>::const_iterator it = manifest.explicitURLs.begin(); it != end; ++it)
    668         addEntry(*it, ApplicationCacheResource::Explicit);
    669 
    670     size_t fallbackCount = manifest.fallbackURLs.size();
    671     for (size_t i = 0; i  < fallbackCount; ++i)
    672         addEntry(manifest.fallbackURLs[i].second, ApplicationCacheResource::Fallback);
    673 
    674     m_cacheBeingUpdated->setOnlineWhitelist(manifest.onlineWhitelistedURLs);
    675     m_cacheBeingUpdated->setFallbackURLs(manifest.fallbackURLs);
    676     m_cacheBeingUpdated->setAllowsAllNetworkRequests(manifest.allowAllNetworkRequests);
    677 
    678     startLoadingEntry();
    679 }
    680 
    681 void ApplicationCacheGroup::didReachMaxAppCacheSize()
    682 {
    683     ASSERT(m_frame);
    684     ASSERT(m_cacheBeingUpdated);
    685     m_frame->page()->chrome()->client()->reachedMaxAppCacheSize(cacheStorage().spaceNeeded(m_cacheBeingUpdated->estimatedSizeInStorage()));
    686     m_calledReachedMaxAppCacheSize = true;
    687     checkIfLoadIsComplete();
    688 }
    689 
    690 void ApplicationCacheGroup::cacheUpdateFailed()
    691 {
    692     stopLoading();
    693     m_manifestResource = 0;
    694 
    695     // Wait for master resource loads to finish.
    696     m_completionType = Failure;
    697     deliverDelayedMainResources();
    698 }
    699 
    700 void ApplicationCacheGroup::manifestNotFound()
    701 {
    702     makeObsolete();
    703 
    704     postListenerTask(ApplicationCacheHost::OBSOLETE_EVENT, m_associatedDocumentLoaders);
    705     postListenerTask(ApplicationCacheHost::ERROR_EVENT, m_pendingMasterResourceLoaders);
    706 
    707     stopLoading();
    708 
    709     ASSERT(m_pendingEntries.isEmpty());
    710     m_manifestResource = 0;
    711 
    712     while (!m_pendingMasterResourceLoaders.isEmpty()) {
    713         HashSet<DocumentLoader*>::iterator it = m_pendingMasterResourceLoaders.begin();
    714 
    715         ASSERT((*it)->applicationCacheHost()->candidateApplicationCacheGroup() == this);
    716         ASSERT(!(*it)->applicationCacheHost()->applicationCache());
    717         (*it)->applicationCacheHost()->setCandidateApplicationCacheGroup(0);
    718         m_pendingMasterResourceLoaders.remove(it);
    719     }
    720 
    721     m_downloadingPendingMasterResourceLoadersCount = 0;
    722     m_updateStatus = Idle;
    723     m_frame = 0;
    724 
    725     if (m_caches.isEmpty()) {
    726         ASSERT(m_associatedDocumentLoaders.isEmpty());
    727         ASSERT(!m_cacheBeingUpdated);
    728         delete this;
    729     }
    730 }
    731 
    732 void ApplicationCacheGroup::checkIfLoadIsComplete()
    733 {
    734     if (m_manifestHandle || !m_pendingEntries.isEmpty() || m_downloadingPendingMasterResourceLoadersCount)
    735         return;
    736 
    737     // We're done, all resources have finished downloading (successfully or not).
    738 
    739     bool isUpgradeAttempt = m_newestCache;
    740 
    741     switch (m_completionType) {
    742     case None:
    743         ASSERT_NOT_REACHED();
    744         return;
    745     case NoUpdate:
    746         ASSERT(isUpgradeAttempt);
    747         ASSERT(!m_cacheBeingUpdated);
    748 
    749         // The storage could have been manually emptied by the user.
    750         if (!m_storageID)
    751             cacheStorage().storeNewestCache(this);
    752 
    753         postListenerTask(ApplicationCacheHost::NOUPDATE_EVENT, m_associatedDocumentLoaders);
    754         break;
    755     case Failure:
    756         ASSERT(!m_cacheBeingUpdated);
    757         postListenerTask(ApplicationCacheHost::ERROR_EVENT, m_associatedDocumentLoaders);
    758         if (m_caches.isEmpty()) {
    759             ASSERT(m_associatedDocumentLoaders.isEmpty());
    760             delete this;
    761             return;
    762         }
    763         break;
    764     case Completed: {
    765         // FIXME: Fetch the resource from manifest URL again, and check whether it is identical to the one used for update (in case the application was upgraded server-side in the meanwhile). (<rdar://problem/6467625>)
    766 
    767         ASSERT(m_cacheBeingUpdated);
    768         if (m_manifestResource)
    769             m_cacheBeingUpdated->setManifestResource(m_manifestResource.release());
    770         else {
    771             // We can get here as a result of retrying the Complete step, following
    772             // a failure of the cache storage to save the newest cache due to hitting
    773             // the maximum size. In such a case, m_manifestResource may be 0, as
    774             // the manifest was already set on the newest cache object.
    775             ASSERT(cacheStorage().isMaximumSizeReached() && m_calledReachedMaxAppCacheSize);
    776         }
    777 
    778         RefPtr<ApplicationCache> oldNewestCache = (m_newestCache == m_cacheBeingUpdated) ? 0 : m_newestCache;
    779 
    780         setNewestCache(m_cacheBeingUpdated.release());
    781         if (cacheStorage().storeNewestCache(this)) {
    782             // New cache stored, now remove the old cache.
    783             if (oldNewestCache)
    784                 cacheStorage().remove(oldNewestCache.get());
    785             // Fire the success events.
    786             postListenerTask(isUpgradeAttempt ? ApplicationCacheHost::UPDATEREADY_EVENT : ApplicationCacheHost::CACHED_EVENT, m_associatedDocumentLoaders);
    787         } else {
    788             if (cacheStorage().isMaximumSizeReached() && !m_calledReachedMaxAppCacheSize) {
    789                 // We ran out of space. All the changes in the cache storage have
    790                 // been rolled back. We roll back to the previous state in here,
    791                 // as well, call the chrome client asynchronously and retry to
    792                 // save the new cache.
    793 
    794                 // Save a reference to the new cache.
    795                 m_cacheBeingUpdated = m_newestCache.release();
    796                 if (oldNewestCache) {
    797                     // Reinstate the oldNewestCache.
    798                     setNewestCache(oldNewestCache.release());
    799                 }
    800                 scheduleReachedMaxAppCacheSizeCallback();
    801                 return;
    802             } else {
    803                 // Run the "cache failure steps"
    804                 // Fire the error events to all pending master entries, as well any other cache hosts
    805                 // currently associated with a cache in this group.
    806                 postListenerTask(ApplicationCacheHost::ERROR_EVENT, m_associatedDocumentLoaders);
    807                 // Disassociate the pending master entries from the failed new cache. Note that
    808                 // all other loaders in the m_associatedDocumentLoaders are still associated with
    809                 // some other cache in this group. They are not associated with the failed new cache.
    810 
    811                 // Need to copy loaders, because the cache group may be destroyed at the end of iteration.
    812                 Vector<DocumentLoader*> loaders;
    813                 copyToVector(m_pendingMasterResourceLoaders, loaders);
    814                 size_t count = loaders.size();
    815                 for (size_t i = 0; i != count; ++i)
    816                     disassociateDocumentLoader(loaders[i]); // This can delete this group.
    817 
    818                 // Reinstate the oldNewestCache, if there was one.
    819                 if (oldNewestCache) {
    820                     // This will discard the failed new cache.
    821                     setNewestCache(oldNewestCache.release());
    822                 } else {
    823                     // We must have been deleted by the last call to disassociateDocumentLoader().
    824                     return;
    825                 }
    826             }
    827         }
    828         break;
    829     }
    830     }
    831 
    832     // Empty cache group's list of pending master entries.
    833     m_pendingMasterResourceLoaders.clear();
    834     m_completionType = None;
    835     m_updateStatus = Idle;
    836     m_frame = 0;
    837     m_calledReachedMaxAppCacheSize = false;
    838 }
    839 
    840 void ApplicationCacheGroup::startLoadingEntry()
    841 {
    842     ASSERT(m_cacheBeingUpdated);
    843 
    844     if (m_pendingEntries.isEmpty()) {
    845         m_completionType = Completed;
    846         deliverDelayedMainResources();
    847         return;
    848     }
    849 
    850     EntryMap::const_iterator it = m_pendingEntries.begin();
    851 
    852     postListenerTask(ApplicationCacheHost::PROGRESS_EVENT, m_associatedDocumentLoaders);
    853 
    854     ASSERT(!m_currentHandle);
    855 
    856     m_currentHandle = createResourceHandle(KURL(ParsedURLString, it->first), m_newestCache ? m_newestCache->resourceForURL(it->first) : 0);
    857 }
    858 
    859 void ApplicationCacheGroup::deliverDelayedMainResources()
    860 {
    861     // Need to copy loaders, because the cache group may be destroyed at the end of iteration.
    862     Vector<DocumentLoader*> loaders;
    863     copyToVector(m_pendingMasterResourceLoaders, loaders);
    864     size_t count = loaders.size();
    865     for (size_t i = 0; i != count; ++i) {
    866         DocumentLoader* loader = loaders[i];
    867         if (loader->isLoadingMainResource())
    868             continue;
    869 
    870         const ResourceError& error = loader->mainDocumentError();
    871         if (error.isNull())
    872             finishedLoadingMainResource(loader);
    873         else
    874             failedLoadingMainResource(loader);
    875     }
    876     if (!count)
    877         checkIfLoadIsComplete();
    878 }
    879 
    880 void ApplicationCacheGroup::addEntry(const String& url, unsigned type)
    881 {
    882     ASSERT(m_cacheBeingUpdated);
    883     ASSERT(!KURL(ParsedURLString, url).hasFragmentIdentifier());
    884 
    885     // Don't add the URL if we already have an master resource in the cache
    886     // (i.e., the main resource finished loading before the manifest).
    887     if (ApplicationCacheResource* resource = m_cacheBeingUpdated->resourceForURL(url)) {
    888         ASSERT(resource->type() & ApplicationCacheResource::Master);
    889         ASSERT(!m_frame->loader()->documentLoader()->isLoadingMainResource());
    890 
    891         resource->addType(type);
    892         return;
    893     }
    894 
    895     // Don't add the URL if it's the same as the manifest URL.
    896     ASSERT(m_manifestResource);
    897     if (m_manifestResource->url() == url) {
    898         m_manifestResource->addType(type);
    899         return;
    900     }
    901 
    902     pair<EntryMap::iterator, bool> result = m_pendingEntries.add(url, type);
    903 
    904     if (!result.second)
    905         result.first->second |= type;
    906 }
    907 
    908 void ApplicationCacheGroup::associateDocumentLoaderWithCache(DocumentLoader* loader, ApplicationCache* cache)
    909 {
    910     // If teardown started already, revive the group.
    911     if (!m_newestCache && !m_cacheBeingUpdated)
    912         m_newestCache = cache;
    913 
    914     ASSERT(!m_isObsolete);
    915 
    916     loader->applicationCacheHost()->setApplicationCache(cache);
    917 
    918     ASSERT(!m_associatedDocumentLoaders.contains(loader));
    919     m_associatedDocumentLoaders.add(loader);
    920 }
    921 
    922 class ChromeClientCallbackTimer: public TimerBase {
    923 public:
    924     ChromeClientCallbackTimer(ApplicationCacheGroup* cacheGroup)
    925         : m_cacheGroup(cacheGroup)
    926     {
    927     }
    928 
    929 private:
    930     virtual void fired()
    931     {
    932         m_cacheGroup->didReachMaxAppCacheSize();
    933         delete this;
    934     }
    935     // Note that there is no need to use a RefPtr here. The ApplicationCacheGroup instance is guaranteed
    936     // to be alive when the timer fires since invoking the ChromeClient callback is part of its normal
    937     // update machinery and nothing can yet cause it to get deleted.
    938     ApplicationCacheGroup* m_cacheGroup;
    939 };
    940 
    941 void ApplicationCacheGroup::scheduleReachedMaxAppCacheSizeCallback()
    942 {
    943     ASSERT(isMainThread());
    944     ChromeClientCallbackTimer* timer = new ChromeClientCallbackTimer(this);
    945     timer->startOneShot(0);
    946     // The timer will delete itself once it fires.
    947 }
    948 
    949 class CallCacheListenerTask : public ScriptExecutionContext::Task {
    950 public:
    951     static PassOwnPtr<CallCacheListenerTask> create(PassRefPtr<DocumentLoader> loader, ApplicationCacheHost::EventID eventID)
    952     {
    953         return new CallCacheListenerTask(loader, eventID);
    954     }
    955 
    956     virtual void performTask(ScriptExecutionContext* context)
    957     {
    958 
    959         ASSERT_UNUSED(context, context->isDocument());
    960         Frame* frame = m_documentLoader->frame();
    961         if (!frame)
    962             return;
    963 
    964         ASSERT(frame->loader()->documentLoader() == m_documentLoader.get());
    965 
    966         m_documentLoader->applicationCacheHost()->notifyDOMApplicationCache(m_eventID);
    967     }
    968 
    969 private:
    970     CallCacheListenerTask(PassRefPtr<DocumentLoader> loader, ApplicationCacheHost::EventID eventID)
    971         : m_documentLoader(loader)
    972         , m_eventID(eventID)
    973     {
    974     }
    975 
    976     RefPtr<DocumentLoader> m_documentLoader;
    977     ApplicationCacheHost::EventID m_eventID;
    978 };
    979 
    980 void ApplicationCacheGroup::postListenerTask(ApplicationCacheHost::EventID eventID, const HashSet<DocumentLoader*>& loaderSet)
    981 {
    982     HashSet<DocumentLoader*>::const_iterator loaderSetEnd = loaderSet.end();
    983     for (HashSet<DocumentLoader*>::const_iterator iter = loaderSet.begin(); iter != loaderSetEnd; ++iter)
    984         postListenerTask(eventID, *iter);
    985 }
    986 
    987 void ApplicationCacheGroup::postListenerTask(ApplicationCacheHost::EventID eventID, DocumentLoader* loader)
    988 {
    989     Frame* frame = loader->frame();
    990     if (!frame)
    991         return;
    992 
    993     ASSERT(frame->loader()->documentLoader() == loader);
    994 
    995     frame->document()->postTask(CallCacheListenerTask::create(loader, eventID));
    996 }
    997 
    998 void ApplicationCacheGroup::clearStorageID()
    999 {
   1000     m_storageID = 0;
   1001 
   1002     HashSet<ApplicationCache*>::const_iterator end = m_caches.end();
   1003     for (HashSet<ApplicationCache*>::const_iterator it = m_caches.begin(); it != end; ++it)
   1004         (*it)->clearStorageID();
   1005 }
   1006 
   1007 
   1008 }
   1009 
   1010 #endif // ENABLE(OFFLINE_WEB_APPLICATIONS)
   1011