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
ApplicationCacheGroup(const KURL & manifestURL,bool isCopy)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
~ApplicationCacheGroup()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
cacheForMainRequest(const ResourceRequest & request,DocumentLoader *)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
fallbackCacheForMainRequest(const ResourceRequest & request,DocumentLoader *)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
selectCache(Frame * frame,const KURL & passedManifestURL)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
selectCacheWithoutManifestURL(Frame * frame)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
finishedLoadingMainResource(DocumentLoader * loader)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
failedLoadingMainResource(DocumentLoader * loader)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
stopLoading()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
disassociateDocumentLoader(DocumentLoader * loader)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
cacheDestroyed(ApplicationCache * cache)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
setNewestCache(PassRefPtr<ApplicationCache> newestCache)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
makeObsolete()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
update(Frame * frame,ApplicationCacheUpdateOption updateOption)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
createResourceHandle(const KURL & url,ApplicationCacheResource * newestCachedResource)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
didReceiveResponse(ResourceHandle * handle,const ResourceResponse & response)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
didReceiveData(ResourceHandle * handle,const char * data,int length,int)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
didFinishLoading(ResourceHandle * handle)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
didFail(ResourceHandle * handle,const ResourceError &)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
didReceiveManifestResponse(const ResourceResponse & response)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
didReceiveManifestData(const char * data,int length)602 void ApplicationCacheGroup::didReceiveManifestData(const char* data, int length)
603 {
604 if (m_manifestResource)
605 m_manifestResource->data()->append(data, length);
606 }
607
didFinishLoadingManifest()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
didReachMaxAppCacheSize()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
cacheUpdateFailed()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
manifestNotFound()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
checkIfLoadIsComplete()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
startLoadingEntry()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
deliverDelayedMainResources()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
addEntry(const String & url,unsigned type)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
associateDocumentLoaderWithCache(DocumentLoader * loader,ApplicationCache * cache)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:
ChromeClientCallbackTimer(ApplicationCacheGroup * cacheGroup)924 ChromeClientCallbackTimer(ApplicationCacheGroup* cacheGroup)
925 : m_cacheGroup(cacheGroup)
926 {
927 }
928
929 private:
fired()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
scheduleReachedMaxAppCacheSizeCallback()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:
create(PassRefPtr<DocumentLoader> loader,ApplicationCacheHost::EventID eventID)951 static PassOwnPtr<CallCacheListenerTask> create(PassRefPtr<DocumentLoader> loader, ApplicationCacheHost::EventID eventID)
952 {
953 return new CallCacheListenerTask(loader, eventID);
954 }
955
performTask(ScriptExecutionContext * context)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:
CallCacheListenerTask(PassRefPtr<DocumentLoader> loader,ApplicationCacheHost::EventID eventID)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
postListenerTask(ApplicationCacheHost::EventID eventID,const HashSet<DocumentLoader * > & loaderSet)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
postListenerTask(ApplicationCacheHost::EventID eventID,DocumentLoader * loader)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
clearStorageID()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