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