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