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 "ApplicationCacheHost.h"
     28 
     29 #if ENABLE(OFFLINE_WEB_APPLICATIONS)
     30 
     31 #include "ApplicationCache.h"
     32 #include "ApplicationCacheGroup.h"
     33 #include "ApplicationCacheResource.h"
     34 #include "DocumentLoader.h"
     35 #include "DOMApplicationCache.h"
     36 #include "Frame.h"
     37 #include "FrameLoader.h"
     38 #include "FrameLoaderClient.h"
     39 #include "MainResourceLoader.h"
     40 #include "ResourceLoader.h"
     41 #include "ResourceRequest.h"
     42 #include "Settings.h"
     43 
     44 namespace WebCore {
     45 
     46 ApplicationCacheHost::ApplicationCacheHost(DocumentLoader* documentLoader)
     47     : m_domApplicationCache(0)
     48     , m_documentLoader(documentLoader)
     49     , m_defersEvents(true)
     50     , m_candidateApplicationCacheGroup(0)
     51 {
     52     ASSERT(m_documentLoader);
     53 }
     54 
     55 ApplicationCacheHost::~ApplicationCacheHost()
     56 {
     57     if (m_applicationCache)
     58         m_applicationCache->group()->disassociateDocumentLoader(m_documentLoader);
     59     else if (m_candidateApplicationCacheGroup)
     60         m_candidateApplicationCacheGroup->disassociateDocumentLoader(m_documentLoader);
     61 }
     62 
     63 void ApplicationCacheHost::selectCacheWithoutManifest()
     64 {
     65     ApplicationCacheGroup::selectCacheWithoutManifestURL(m_documentLoader->frame());
     66 }
     67 
     68 void ApplicationCacheHost::selectCacheWithManifest(const KURL& manifestURL)
     69 {
     70     ApplicationCacheGroup::selectCache(m_documentLoader->frame(), manifestURL);
     71 }
     72 
     73 void ApplicationCacheHost::maybeLoadMainResource(ResourceRequest& request, SubstituteData& substituteData)
     74 {
     75     // Check if this request should be loaded from the application cache
     76     if (!substituteData.isValid() && isApplicationCacheEnabled()) {
     77         ASSERT(!m_mainResourceApplicationCache);
     78 
     79         m_mainResourceApplicationCache = ApplicationCacheGroup::cacheForMainRequest(request, m_documentLoader);
     80 
     81         if (m_mainResourceApplicationCache) {
     82             // Get the resource from the application cache. By definition, cacheForMainRequest() returns a cache that contains the resource.
     83             ApplicationCacheResource* resource = m_mainResourceApplicationCache->resourceForRequest(request);
     84             substituteData = SubstituteData(resource->data(),
     85                                             resource->response().mimeType(),
     86                                             resource->response().textEncodingName(), KURL());
     87         }
     88     }
     89 }
     90 
     91 bool ApplicationCacheHost::maybeLoadFallbackForMainResponse(const ResourceRequest& request, const ResourceResponse& r)
     92 {
     93     if (r.httpStatusCode() / 100 == 4 || r.httpStatusCode() / 100 == 5) {
     94         ASSERT(!m_mainResourceApplicationCache);
     95         if (isApplicationCacheEnabled()) {
     96             m_mainResourceApplicationCache = ApplicationCacheGroup::fallbackCacheForMainRequest(request, documentLoader());
     97 
     98             if (scheduleLoadFallbackResourceFromApplicationCache(documentLoader()->mainResourceLoader(), m_mainResourceApplicationCache.get()))
     99                 return true;
    100         }
    101     }
    102     return false;
    103 }
    104 
    105 bool ApplicationCacheHost::maybeLoadFallbackForMainError(const ResourceRequest& request, const ResourceError& error)
    106 {
    107     if (!error.isCancellation()) {
    108         ASSERT(!m_mainResourceApplicationCache);
    109         if (isApplicationCacheEnabled()) {
    110             m_mainResourceApplicationCache = ApplicationCacheGroup::fallbackCacheForMainRequest(request, m_documentLoader);
    111 
    112             if (scheduleLoadFallbackResourceFromApplicationCache(documentLoader()->mainResourceLoader(), m_mainResourceApplicationCache.get()))
    113                 return true;
    114         }
    115     }
    116     return false;
    117 }
    118 
    119 void ApplicationCacheHost::mainResourceDataReceived(const char*, int, long long, bool)
    120 {
    121     // This method is here to facilitate alternate implemetations of this interface by the host browser.
    122 }
    123 
    124 void ApplicationCacheHost::failedLoadingMainResource()
    125 {
    126     ApplicationCacheGroup* group = m_candidateApplicationCacheGroup;
    127     if (!group && m_applicationCache) {
    128         ASSERT(!mainResourceApplicationCache()); // If the main resource were loaded from a cache, it wouldn't fail.
    129         group = m_applicationCache->group();
    130     }
    131 
    132     if (group)
    133         group->failedLoadingMainResource(m_documentLoader);
    134 }
    135 
    136 void ApplicationCacheHost::finishedLoadingMainResource()
    137 {
    138     ApplicationCacheGroup* group = candidateApplicationCacheGroup();
    139     if (!group && applicationCache() && !mainResourceApplicationCache())
    140         group = applicationCache()->group();
    141 
    142     if (group)
    143         group->finishedLoadingMainResource(m_documentLoader);
    144 }
    145 
    146 bool ApplicationCacheHost::maybeLoadResource(ResourceLoader* loader, ResourceRequest& request, const KURL& originalURL)
    147 {
    148     if (!isApplicationCacheEnabled())
    149         return false;
    150 
    151     if (request.url() != originalURL)
    152         return false;
    153 
    154     ApplicationCacheResource* resource;
    155     if (!shouldLoadResourceFromApplicationCache(request, resource))
    156         return false;
    157 
    158     m_documentLoader->m_pendingSubstituteResources.set(loader, resource);
    159     m_documentLoader->deliverSubstituteResourcesAfterDelay();
    160 
    161     return true;
    162 }
    163 
    164 bool ApplicationCacheHost::maybeLoadFallbackForRedirect(ResourceLoader* resourceLoader, ResourceRequest& request, const ResourceResponse& redirectResponse)
    165 {
    166     if (!redirectResponse.isNull() && !protocolHostAndPortAreEqual(request.url(), redirectResponse.url()))
    167         if (scheduleLoadFallbackResourceFromApplicationCache(resourceLoader))
    168             return true;
    169     return false;
    170 }
    171 
    172 bool ApplicationCacheHost::maybeLoadFallbackForResponse(ResourceLoader* resourceLoader, const ResourceResponse& response)
    173 {
    174     if (response.httpStatusCode() / 100 == 4 || response.httpStatusCode() / 100 == 5)
    175         if (scheduleLoadFallbackResourceFromApplicationCache(resourceLoader))
    176             return true;
    177     return false;
    178 }
    179 
    180 bool ApplicationCacheHost::maybeLoadFallbackForError(ResourceLoader* resourceLoader, const ResourceError& error)
    181 {
    182     if (!error.isCancellation())
    183         if (scheduleLoadFallbackResourceFromApplicationCache(resourceLoader))
    184             return true;
    185     return false;
    186 }
    187 
    188 bool ApplicationCacheHost::maybeLoadSynchronously(ResourceRequest& request, ResourceError& error, ResourceResponse& response, Vector<char>& data)
    189 {
    190     ApplicationCacheResource* resource;
    191     if (shouldLoadResourceFromApplicationCache(request, resource)) {
    192         if (resource) {
    193             response = resource->response();
    194             data.append(resource->data()->data(), resource->data()->size());
    195         } else {
    196             error = documentLoader()->frameLoader()->client()->cannotShowURLError(request);
    197         }
    198         return true;
    199     }
    200     return false;
    201 }
    202 
    203 void ApplicationCacheHost::maybeLoadFallbackSynchronously(const ResourceRequest& request, ResourceError& error, ResourceResponse& response, Vector<char>& data)
    204 {
    205     // If normal loading results in a redirect to a resource with another origin (indicative of a captive portal), or a 4xx or 5xx status code or equivalent,
    206     // or if there were network errors (but not if the user canceled the download), then instead get, from the cache, the resource of the fallback entry
    207     // corresponding to the matched namespace.
    208     if ((!error.isNull() && !error.isCancellation())
    209          || response.httpStatusCode() / 100 == 4 || response.httpStatusCode() / 100 == 5
    210          || !protocolHostAndPortAreEqual(request.url(), response.url())) {
    211         ApplicationCacheResource* resource;
    212         if (getApplicationCacheFallbackResource(request, resource)) {
    213             response = resource->response();
    214             data.clear();
    215             data.append(resource->data()->data(), resource->data()->size());
    216         }
    217     }
    218 }
    219 
    220 bool ApplicationCacheHost::canCacheInPageCache() const
    221 {
    222     return !applicationCache() && !candidateApplicationCacheGroup();
    223 }
    224 
    225 void ApplicationCacheHost::setDOMApplicationCache(DOMApplicationCache* domApplicationCache)
    226 {
    227     ASSERT(!m_domApplicationCache || !domApplicationCache);
    228     m_domApplicationCache = domApplicationCache;
    229 }
    230 
    231 void ApplicationCacheHost::notifyDOMApplicationCache(EventID id)
    232 {
    233     if (m_defersEvents) {
    234         // Events are deferred until document.onload has fired.
    235         m_deferredEvents.append(id);
    236         return;
    237     }
    238     if (m_domApplicationCache) {
    239         ExceptionCode ec = 0;
    240         m_domApplicationCache->dispatchEvent(Event::create(DOMApplicationCache::toEventType(id), false, false), ec);
    241         ASSERT(!ec);
    242     }
    243 }
    244 
    245 void ApplicationCacheHost::stopDeferringEvents()
    246 {
    247     RefPtr<DocumentLoader> protect(documentLoader());
    248     for (unsigned i = 0; i < m_deferredEvents.size(); ++i) {
    249         EventID id = m_deferredEvents[i];
    250         if (m_domApplicationCache) {
    251             ExceptionCode ec = 0;
    252             m_domApplicationCache->dispatchEvent(Event::create(DOMApplicationCache::toEventType(id), false, false), ec);
    253             ASSERT(!ec);
    254         }
    255     }
    256     m_deferredEvents.clear();
    257     m_defersEvents = false;
    258 }
    259 
    260 void ApplicationCacheHost::setCandidateApplicationCacheGroup(ApplicationCacheGroup* group)
    261 {
    262     ASSERT(!m_applicationCache);
    263     m_candidateApplicationCacheGroup = group;
    264 }
    265 
    266 void ApplicationCacheHost::setApplicationCache(PassRefPtr<ApplicationCache> applicationCache)
    267 {
    268     if (m_candidateApplicationCacheGroup) {
    269         ASSERT(!m_applicationCache);
    270         m_candidateApplicationCacheGroup = 0;
    271     }
    272 
    273     m_applicationCache = applicationCache;
    274 }
    275 
    276 bool ApplicationCacheHost::shouldLoadResourceFromApplicationCache(const ResourceRequest& request, ApplicationCacheResource*& resource)
    277 {
    278     ApplicationCache* cache = applicationCache();
    279     if (!cache || !cache->isComplete())
    280         return false;
    281 
    282     // If the resource is not to be fetched using the HTTP GET mechanism or equivalent, or if its URL has a different
    283     // <scheme> component than the application cache's manifest, then fetch the resource normally.
    284     if (!ApplicationCache::requestIsHTTPOrHTTPSGet(request) || !equalIgnoringCase(request.url().protocol(), cache->manifestResource()->url().protocol()))
    285         return false;
    286 
    287     // If the resource's URL is an master entry, the manifest, an explicit entry, or a fallback entry
    288     // in the application cache, then get the resource from the cache (instead of fetching it).
    289     resource = cache->resourceForURL(request.url());
    290 
    291     // Resources that match fallback namespaces or online whitelist entries are fetched from the network,
    292     // unless they are also cached.
    293     if (!resource && (cache->urlMatchesFallbackNamespace(request.url()) || cache->isURLInOnlineWhitelist(request.url())))
    294         return false;
    295 
    296     // Resources that are not present in the manifest will always fail to load (at least, after the
    297     // cache has been primed the first time), making the testing of offline applications simpler.
    298     return true;
    299 }
    300 
    301 bool ApplicationCacheHost::getApplicationCacheFallbackResource(const ResourceRequest& request, ApplicationCacheResource*& resource, ApplicationCache* cache)
    302 {
    303     if (!cache) {
    304         cache = applicationCache();
    305         if (!cache)
    306             return false;
    307     }
    308     if (!cache->isComplete())
    309         return false;
    310 
    311     // If the resource is not a HTTP/HTTPS GET, then abort
    312     if (!ApplicationCache::requestIsHTTPOrHTTPSGet(request))
    313         return false;
    314 
    315     KURL fallbackURL;
    316     if (!cache->urlMatchesFallbackNamespace(request.url(), &fallbackURL))
    317         return false;
    318 
    319     resource = cache->resourceForURL(fallbackURL);
    320     ASSERT(resource);
    321 
    322     return true;
    323 }
    324 
    325 bool ApplicationCacheHost::scheduleLoadFallbackResourceFromApplicationCache(ResourceLoader* loader, ApplicationCache* cache)
    326 {
    327     if (!isApplicationCacheEnabled())
    328         return false;
    329 
    330     ApplicationCacheResource* resource;
    331     if (!getApplicationCacheFallbackResource(loader->request(), resource, cache))
    332         return false;
    333 
    334     m_documentLoader->m_pendingSubstituteResources.set(loader, resource);
    335     m_documentLoader->deliverSubstituteResourcesAfterDelay();
    336 
    337     loader->handle()->cancel();
    338 
    339     return true;
    340 }
    341 
    342 ApplicationCacheHost::Status ApplicationCacheHost::status() const
    343 {
    344     ApplicationCache* cache = applicationCache();
    345     if (!cache)
    346         return UNCACHED;
    347 
    348     switch (cache->group()->updateStatus()) {
    349         case ApplicationCacheGroup::Checking:
    350             return CHECKING;
    351         case ApplicationCacheGroup::Downloading:
    352             return DOWNLOADING;
    353         case ApplicationCacheGroup::Idle: {
    354             if (cache->group()->isObsolete())
    355                 return OBSOLETE;
    356             if (cache != cache->group()->newestCache())
    357                 return UPDATEREADY;
    358             return IDLE;
    359         }
    360     }
    361 
    362     ASSERT_NOT_REACHED();
    363     return UNCACHED;
    364 }
    365 
    366 bool ApplicationCacheHost::update()
    367 {
    368     ApplicationCache* cache = applicationCache();
    369     if (!cache)
    370         return false;
    371     cache->group()->update(m_documentLoader->frame(), ApplicationCacheUpdateWithoutBrowsingContext);
    372     return true;
    373 }
    374 
    375 bool ApplicationCacheHost::swapCache()
    376 {
    377     ApplicationCache* cache = applicationCache();
    378     if (!cache)
    379         return false;
    380 
    381     // If the group of application caches to which cache belongs has the lifecycle status obsolete, unassociate document from cache.
    382     if (cache->group()->isObsolete()) {
    383         cache->group()->disassociateDocumentLoader(m_documentLoader);
    384         return true;
    385     }
    386 
    387     // If there is no newer cache, raise an INVALID_STATE_ERR exception.
    388     ApplicationCache* newestCache = cache->group()->newestCache();
    389     if (cache == newestCache)
    390         return false;
    391 
    392     ASSERT(cache->group() == newestCache->group());
    393     setApplicationCache(newestCache);
    394 
    395     return true;
    396 }
    397 
    398 bool ApplicationCacheHost::isApplicationCacheEnabled()
    399 {
    400     return m_documentLoader->frame()->settings()
    401            && m_documentLoader->frame()->settings()->offlineWebApplicationCacheEnabled();
    402 }
    403 
    404 }  // namespace WebCore
    405 
    406 #endif  // ENABLE(OFFLINE_WEB_APPLICATIONS)
    407