Rubber-stamped by Darin Adler.
[WebKit-https.git] / WebCore / loader / appcache / ApplicationCacheGroup.cpp
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 "ApplicationCacheResource.h"
33 #include "ApplicationCacheStorage.h"
34 #include "DocumentLoader.h"
35 #include "DOMApplicationCache.h"
36 #include "DOMWindow.h"
37 #include "Frame.h"
38 #include "FrameLoader.h"
39 #include "MainResourceLoader.h"
40 #include "ManifestParser.h"
41 #include "Page.h"
42 #include "Settings.h"
43 #include <wtf/HashMap.h>
44
45 namespace WebCore {
46
47 ApplicationCacheGroup::ApplicationCacheGroup(const KURL& manifestURL, bool isCopy)
48     : m_manifestURL(manifestURL)
49     , m_updateStatus(Idle)
50     , m_frame(0)
51     , m_storageID(0)
52     , m_isObsolete(false)
53     , m_completionType(None)
54     , m_isCopy(isCopy)
55 {
56 }
57
58 ApplicationCacheGroup::~ApplicationCacheGroup()
59 {
60     if (m_isCopy) {
61         ASSERT(m_newestCache);
62         ASSERT(m_caches.size() == 1);
63         ASSERT(m_caches.contains(m_newestCache.get()));
64         ASSERT(!m_cacheBeingUpdated);
65         ASSERT(m_associatedDocumentLoaders.isEmpty());
66         ASSERT(m_pendingMasterResourceLoaders.isEmpty());
67         ASSERT(m_newestCache->group() == this);
68         
69         return;
70     }
71                
72     ASSERT(!m_newestCache);
73     ASSERT(m_caches.isEmpty());
74     
75     stopLoading();
76     
77     cacheStorage().cacheGroupDestroyed(this);
78 }
79     
80 ApplicationCache* ApplicationCacheGroup::cacheForMainRequest(const ResourceRequest& request, DocumentLoader*)
81 {
82     if (!ApplicationCache::requestIsHTTPOrHTTPSGet(request))
83         return 0;
84
85     if (ApplicationCacheGroup* group = cacheStorage().cacheGroupForURL(request.url())) {
86         ASSERT(group->newestCache());
87         ASSERT(!group->isObsolete());
88         
89         return group->newestCache();
90     }
91     
92     return 0;
93 }
94     
95 ApplicationCache* ApplicationCacheGroup::fallbackCacheForMainRequest(const ResourceRequest& request, DocumentLoader*)
96 {
97     if (!ApplicationCache::requestIsHTTPOrHTTPSGet(request))
98         return 0;
99
100     if (ApplicationCacheGroup* group = cacheStorage().fallbackCacheGroupForURL(request.url())) {
101         ASSERT(group->newestCache());
102         ASSERT(!group->isObsolete());
103
104         return group->newestCache();
105     }
106     
107     return 0;
108 }
109
110 void ApplicationCacheGroup::selectCache(Frame* frame, const KURL& manifestURL)
111 {
112     ASSERT(frame && frame->page());
113     
114     if (!frame->settings()->offlineWebApplicationCacheEnabled())
115         return;
116     
117     DocumentLoader* documentLoader = frame->loader()->documentLoader();
118     ASSERT(!documentLoader->applicationCache());
119
120     if (manifestURL.isNull()) {
121         selectCacheWithoutManifestURL(frame);        
122         return;
123     }
124     
125     ApplicationCache* mainResourceCache = documentLoader->mainResourceApplicationCache();
126     
127     if (mainResourceCache) {
128         if (manifestURL == mainResourceCache->group()->m_manifestURL) {
129             mainResourceCache->group()->associateDocumentLoaderWithCache(documentLoader, mainResourceCache);
130             mainResourceCache->group()->update(frame, ApplicationCacheUpdateWithBrowsingContext);
131         } else {
132             // The main resource was loaded from cache, so the cache must have an entry for it. Mark it as foreign.
133             ApplicationCacheResource* resource = mainResourceCache->resourceForURL(documentLoader->url());
134             bool inStorage = resource->storageID();
135             resource->addType(ApplicationCacheResource::Foreign);
136             if (inStorage)
137                 cacheStorage().storeUpdatedType(resource, mainResourceCache);
138
139             // Restart the current navigation from the top of the navigation algorithm, undoing any changes that were made
140             // as part of the initial load.
141             // The navigation will not result in the same resource being loaded, because "foreign" entries are never picked during navigation.
142             frame->loader()->scheduleLocationChange(documentLoader->url(), frame->loader()->referrer(), true);
143         }
144         
145         return;
146     }
147     
148     // The resource was loaded from the network, check if it is a HTTP/HTTPS GET.    
149     const ResourceRequest& request = frame->loader()->activeDocumentLoader()->request();
150
151     if (!ApplicationCache::requestIsHTTPOrHTTPSGet(request))
152         return;
153
154     // Check that the resource URL has the same scheme/host/port as the manifest URL.
155     if (!protocolHostAndPortAreEqual(manifestURL, request.url()))
156         return;
157             
158     ApplicationCacheGroup* group = cacheStorage().findOrCreateCacheGroup(manifestURL);
159
160     documentLoader->setCandidateApplicationCacheGroup(group);
161     group->m_pendingMasterResourceLoaders.add(documentLoader);
162
163     ASSERT(!group->m_cacheBeingUpdated || group->m_updateStatus != Idle);
164     group->update(frame, ApplicationCacheUpdateWithBrowsingContext);
165 }
166
167 void ApplicationCacheGroup::selectCacheWithoutManifestURL(Frame* frame)
168 {
169     if (!frame->settings()->offlineWebApplicationCacheEnabled())
170         return;
171
172     DocumentLoader* documentLoader = frame->loader()->documentLoader();
173     ASSERT(!documentLoader->applicationCache());
174
175     ApplicationCache* mainResourceCache = documentLoader->mainResourceApplicationCache();
176
177     if (mainResourceCache) {
178         mainResourceCache->group()->associateDocumentLoaderWithCache(documentLoader, mainResourceCache);
179         mainResourceCache->group()->update(frame, ApplicationCacheUpdateWithBrowsingContext);
180     }
181 }
182
183 void ApplicationCacheGroup::finishedLoadingMainResource(DocumentLoader* loader)
184 {
185     ASSERT(m_pendingMasterResourceLoaders.contains(loader));
186     ASSERT(m_completionType == None || m_pendingEntries.isEmpty());
187     const KURL& url = loader->originalURL();
188
189     switch (m_completionType) {
190     case None:
191         // The main resource finished loading before the manifest was ready. It will be handled via dispatchMainResources() later.
192         return;
193     case NoUpdate:
194         ASSERT(!m_cacheBeingUpdated);
195         associateDocumentLoaderWithCache(loader, m_newestCache.get());
196
197         if (ApplicationCacheResource* resource = m_newestCache->resourceForURL(url)) {
198             if (!(resource->type() & ApplicationCacheResource::Master)) {
199                 resource->addType(ApplicationCacheResource::Master);
200                 ASSERT(!resource->storageID());
201             }
202         } else
203             m_newestCache->addResource(ApplicationCacheResource::create(url, loader->response(), ApplicationCacheResource::Master, loader->mainResourceData()));
204
205         break;
206     case Failure:
207         // Cache update has been a failure, so there is no reason to keep the document associated with the incomplete cache
208         // (its main resource was not cached yet, so it is likely that the application changed significantly server-side).
209         ASSERT(!m_cacheBeingUpdated); // Already cleared out by stopLoading().
210         loader->setApplicationCache(0); // Will unset candidate, too.
211         m_associatedDocumentLoaders.remove(loader);
212         postListenerTask(&DOMApplicationCache::callErrorListener, loader);
213         break;
214     case Completed:
215         ASSERT(m_associatedDocumentLoaders.contains(loader));
216
217         if (ApplicationCacheResource* resource = m_cacheBeingUpdated->resourceForURL(url)) {
218             if (!(resource->type() & ApplicationCacheResource::Master)) {
219                 resource->addType(ApplicationCacheResource::Master);
220                 ASSERT(!resource->storageID());
221             }
222         } else
223             m_cacheBeingUpdated->addResource(ApplicationCacheResource::create(url, loader->response(), ApplicationCacheResource::Master, loader->mainResourceData()));
224         // The "cached" event will be posted to all associated documents once update is complete.
225         break;
226     }
227
228     m_pendingMasterResourceLoaders.remove(loader);
229     checkIfLoadIsComplete();
230 }
231
232 void ApplicationCacheGroup::failedLoadingMainResource(DocumentLoader* loader)
233 {
234     ASSERT(m_pendingMasterResourceLoaders.contains(loader));
235     ASSERT(m_completionType == None || m_pendingEntries.isEmpty());
236
237     switch (m_completionType) {
238     case None:
239         // The main resource finished loading before the manifest was ready. It will be handled via dispatchMainResources() later.
240         return;
241     case NoUpdate:
242         ASSERT(!m_cacheBeingUpdated);
243
244         // 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,
245         // 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.
246         postListenerTask(&DOMApplicationCache::callErrorListener, loader);
247
248         break;
249     case Failure:
250         // Cache update failed, too.
251         ASSERT(!m_cacheBeingUpdated); // Already cleared out by stopLoading().
252         ASSERT(!loader->applicationCache() || loader->applicationCache() == m_cacheBeingUpdated);
253
254         loader->setApplicationCache(0); // Will unset candidate, too.
255         m_associatedDocumentLoaders.remove(loader);
256         postListenerTask(&DOMApplicationCache::callErrorListener, loader);
257         break;
258     case Completed:
259         // The cache manifest didn't list this main resource, and all cache entries were already updated successfully - but the main resource failed to load,
260         // so it cannot be stored to the cache. If there are other main resources being downloaded for this cache group, they may still succeed.
261         ASSERT(m_associatedDocumentLoaders.contains(loader));
262         ASSERT(loader->applicationCache() == m_cacheBeingUpdated);
263         ASSERT(!loader->candidateApplicationCacheGroup());
264         m_associatedDocumentLoaders.remove(loader);
265         loader->setApplicationCache(0);
266
267         postListenerTask(&DOMApplicationCache::callErrorListener, loader);
268
269         break;
270     }
271
272     m_pendingMasterResourceLoaders.remove(loader);
273     checkIfLoadIsComplete();
274 }
275
276 void ApplicationCacheGroup::stopLoading()
277 {
278     if (m_manifestHandle) {
279         ASSERT(!m_currentHandle);
280
281         m_manifestHandle->setClient(0);
282         m_manifestHandle->cancel();
283         m_manifestHandle = 0;
284     }
285     
286     if (m_currentHandle) {
287         ASSERT(!m_manifestHandle);
288         ASSERT(m_cacheBeingUpdated);
289
290         m_currentHandle->setClient(0);
291         m_currentHandle->cancel();
292         m_currentHandle = 0;
293     }    
294     
295     m_cacheBeingUpdated = 0;
296     m_pendingEntries.clear();
297 }    
298
299 void ApplicationCacheGroup::disassociateDocumentLoader(DocumentLoader* loader)
300 {
301     HashSet<DocumentLoader*>::iterator it = m_associatedDocumentLoaders.find(loader);
302     if (it != m_associatedDocumentLoaders.end())
303         m_associatedDocumentLoaders.remove(it);
304
305     m_pendingMasterResourceLoaders.remove(loader);
306
307     loader->setApplicationCache(0); // Will set candidate to 0, too.
308
309     if (!m_associatedDocumentLoaders.isEmpty() || !m_pendingMasterResourceLoaders.isEmpty())
310         return;
311
312     if (m_caches.isEmpty()) {
313         // There is an initial cache attempt in progress.
314         ASSERT(!m_newestCache);
315         // Delete ourselves, causing the cache attempt to be stopped.
316         delete this;
317         return;
318     }
319
320     ASSERT(m_caches.contains(m_newestCache.get()));
321
322     // Release our reference to the newest cache. This could cause us to be deleted.
323     // Any ongoing updates will be stopped from destructor.
324     m_newestCache.release();
325 }
326
327 void ApplicationCacheGroup::cacheDestroyed(ApplicationCache* cache)
328 {
329     if (!m_caches.contains(cache))
330         return;
331     
332     m_caches.remove(cache);
333
334     if (m_caches.isEmpty()) {
335         ASSERT(m_associatedDocumentLoaders.isEmpty());
336         ASSERT(m_pendingMasterResourceLoaders.isEmpty());
337         delete this;
338     }
339 }
340
341 void ApplicationCacheGroup::setNewestCache(PassRefPtr<ApplicationCache> newestCache)
342 {
343     ASSERT(!m_caches.contains(newestCache.get()));
344
345     m_newestCache = newestCache;
346
347     m_caches.add(m_newestCache.get());
348     m_newestCache->setGroup(this);
349 }
350
351 void ApplicationCacheGroup::makeObsolete()
352 {
353     if (isObsolete())
354         return;
355
356     m_isObsolete = true;
357     cacheStorage().cacheGroupMadeObsolete(this);
358     ASSERT(!m_storageID);
359 }
360
361 void ApplicationCacheGroup::update(Frame* frame, ApplicationCacheUpdateOption updateOption)
362 {
363     if (m_updateStatus == Checking || m_updateStatus == Downloading) {
364         if (updateOption == ApplicationCacheUpdateWithBrowsingContext) {
365             postListenerTask(&DOMApplicationCache::callCheckingListener, frame->loader()->documentLoader());
366             if (m_updateStatus == Downloading)
367                 postListenerTask(&DOMApplicationCache::callDownloadingListener, frame->loader()->documentLoader());
368         }
369         return;
370     }
371
372     ASSERT(!m_frame);
373     m_frame = frame;
374
375     m_updateStatus = Checking;
376
377     postListenerTask(&DOMApplicationCache::callCheckingListener, m_associatedDocumentLoaders);
378     if (!m_newestCache) {
379         ASSERT(updateOption == ApplicationCacheUpdateWithBrowsingContext);
380         postListenerTask(&DOMApplicationCache::callCheckingListener, frame->loader()->documentLoader());
381     }
382     
383     ASSERT(!m_manifestHandle);
384     ASSERT(!m_manifestResource);
385     ASSERT(m_completionType == None);
386
387     // FIXME: Handle defer loading
388     
389     ResourceRequest request(m_manifestURL);
390     m_frame->loader()->applyUserAgent(request);
391     // FIXME: Should ask to revalidate from origin.
392     
393     m_manifestHandle = ResourceHandle::create(request, this, m_frame, false, true, false);
394 }
395  
396 void ApplicationCacheGroup::didReceiveResponse(ResourceHandle* handle, const ResourceResponse& response)
397 {
398     if (handle == m_manifestHandle) {
399         didReceiveManifestResponse(response);
400         return;
401     }
402     
403     ASSERT(handle == m_currentHandle);
404
405     const KURL& url = handle->request().url();
406     
407     ASSERT(!m_currentResource);
408     ASSERT(m_pendingEntries.contains(url));
409     
410     unsigned type = m_pendingEntries.get(url);
411     
412     // If this is an initial cache attempt, we should not get master resources delivered here.
413     if (!m_newestCache)
414         ASSERT(!(type & ApplicationCacheResource::Master));
415
416     if (response.httpStatusCode() / 100 != 2) {
417         if ((type & ApplicationCacheResource::Explicit) || (type & ApplicationCacheResource::Fallback)) {
418             // Note that cacheUpdateFailed() can cause the cache group to be deleted.
419             cacheUpdateFailed();
420         } else if (response.httpStatusCode() == 404 || response.httpStatusCode() == 410) {
421             // Skip this resource. It is dropped from the cache.
422             m_currentHandle->cancel();
423             m_currentHandle = 0;
424             m_pendingEntries.remove(handle->request().url());
425             // Load the next resource, if any.
426             startLoadingEntry();
427         } else {
428             // Copy the resource and its metadata from the newest application cache in cache group whose completeness flag is complete, and act
429             // as if that was the fetched resource, ignoring the resource obtained from the network.
430             ASSERT(m_newestCache);
431             ApplicationCacheResource* resource = m_newestCache->resourceForURL(handle->request().url());
432             ASSERT(resource);
433             m_cacheBeingUpdated->addResource(ApplicationCacheResource::create(handle->request().url(), resource->response(), resource->type(), resource->data()));
434             // Load the next resource, if any.
435             m_currentHandle->cancel();
436             m_currentHandle = 0;
437             startLoadingEntry();
438         }
439         return;
440     }
441     
442     m_currentResource = ApplicationCacheResource::create(url, response, type);
443 }
444
445 void ApplicationCacheGroup::didReceiveData(ResourceHandle* handle, const char* data, int length, int)
446 {
447     if (handle == m_manifestHandle) {
448         didReceiveManifestData(data, length);
449         return;
450     }
451     
452     ASSERT(handle == m_currentHandle);
453     
454     ASSERT(m_currentResource);
455     m_currentResource->data()->append(data, length);
456 }
457
458 void ApplicationCacheGroup::didFinishLoading(ResourceHandle* handle)
459 {
460     if (handle == m_manifestHandle) {
461         didFinishLoadingManifest();
462         return;
463     }
464  
465     ASSERT(m_currentHandle == handle);
466     ASSERT(m_pendingEntries.contains(handle->request().url()));
467     
468     m_pendingEntries.remove(handle->request().url());
469     
470     ASSERT(m_cacheBeingUpdated);
471
472     m_cacheBeingUpdated->addResource(m_currentResource.release());
473     m_currentHandle = 0;
474     
475     // Load the next resource, if any.
476     startLoadingEntry();
477 }
478
479 void ApplicationCacheGroup::didFail(ResourceHandle* handle, const ResourceError&)
480 {
481     if (handle == m_manifestHandle) {
482         cacheUpdateFailed();
483         return;
484     }
485     
486     if ((m_currentResource->type() & ApplicationCacheResource::Explicit) || (m_currentResource->type() & ApplicationCacheResource::Fallback)) {
487         // Note that cacheUpdateFailed() can cause the cache group to be deleted.
488         cacheUpdateFailed();
489     } else {
490         // Copy the resource and its metadata from the newest application cache in cache group whose completeness flag is complete, and act
491         // as if that was the fetched resource, ignoring the resource obtained from the network.
492         ASSERT(m_newestCache);
493         ApplicationCacheResource* resource = m_newestCache->resourceForURL(handle->request().url());
494         ASSERT(resource);
495         m_cacheBeingUpdated->addResource(ApplicationCacheResource::create(handle->request().url(), resource->response(), resource->type(), resource->data()));
496         // Load the next resource, if any.
497         startLoadingEntry();
498     }
499 }
500
501 void ApplicationCacheGroup::didReceiveManifestResponse(const ResourceResponse& response)
502 {
503     if (response.httpStatusCode() == 404 || response.httpStatusCode() == 410) {
504         manifestNotFound();
505         return;
506     }
507
508     if (response.httpStatusCode() / 100 != 2 || !equalIgnoringCase(response.mimeType(), "text/cache-manifest")) {
509         cacheUpdateFailed();
510         return;
511     }
512     
513     ASSERT(!m_manifestResource);
514     ASSERT(m_manifestHandle);
515     m_manifestResource = ApplicationCacheResource::create(m_manifestHandle->request().url(), response, 
516                                                           ApplicationCacheResource::Manifest);
517 }
518
519 void ApplicationCacheGroup::didReceiveManifestData(const char* data, int length)
520 {
521     ASSERT(m_manifestResource);
522     m_manifestResource->data()->append(data, length);
523 }
524
525 void ApplicationCacheGroup::didFinishLoadingManifest()
526 {
527     if (!m_manifestResource) {
528         cacheUpdateFailed();
529         return;
530     }
531
532     bool isUpgradeAttempt = m_newestCache;
533     
534     m_manifestHandle = 0;
535
536     // Check if the manifest is byte-for-byte identical.
537     if (isUpgradeAttempt) {
538         ApplicationCacheResource* newestManifest = m_newestCache->manifestResource();
539         ASSERT(newestManifest);
540     
541         if (newestManifest->data()->size() == m_manifestResource->data()->size() &&
542             !memcmp(newestManifest->data()->data(), m_manifestResource->data()->data(), newestManifest->data()->size())) {
543
544             m_completionType = NoUpdate;
545             m_manifestResource = 0;
546             deliverDelayedMainResources();
547
548             return;
549         }
550     }
551     
552     Manifest manifest;
553     if (!parseManifest(m_manifestURL, m_manifestResource->data()->data(), m_manifestResource->data()->size(), manifest)) {
554         cacheUpdateFailed();
555         return;
556     }
557
558     ASSERT(!m_cacheBeingUpdated);
559     m_cacheBeingUpdated = ApplicationCache::create();
560     m_cacheBeingUpdated->setGroup(this);
561
562     HashSet<DocumentLoader*>::const_iterator masterEnd = m_pendingMasterResourceLoaders.end();
563     for (HashSet<DocumentLoader*>::const_iterator iter = m_pendingMasterResourceLoaders.begin(); iter != masterEnd; ++iter)
564         associateDocumentLoaderWithCache(*iter, m_cacheBeingUpdated.get());
565
566     // We have the manifest, now download the resources.
567     m_updateStatus = Downloading;
568     
569     postListenerTask(&DOMApplicationCache::callDownloadingListener, m_associatedDocumentLoaders);
570
571     ASSERT(m_pendingEntries.isEmpty());
572
573     if (isUpgradeAttempt) {
574         ApplicationCache::ResourceMap::const_iterator end = m_newestCache->end();
575         for (ApplicationCache::ResourceMap::const_iterator it = m_newestCache->begin(); it != end; ++it) {
576             unsigned type = it->second->type();
577             if (type & (ApplicationCacheResource::Master | ApplicationCacheResource::Dynamic))
578                 addEntry(it->first, type);
579         }
580     }
581     
582     HashSet<String>::const_iterator end = manifest.explicitURLs.end();
583     for (HashSet<String>::const_iterator it = manifest.explicitURLs.begin(); it != end; ++it)
584         addEntry(*it, ApplicationCacheResource::Explicit);
585
586     size_t fallbackCount = manifest.fallbackURLs.size();
587     for (size_t i = 0; i  < fallbackCount; ++i)
588         addEntry(manifest.fallbackURLs[i].second, ApplicationCacheResource::Fallback);
589     
590     m_cacheBeingUpdated->setOnlineWhitelist(manifest.onlineWhitelistedURLs);
591     m_cacheBeingUpdated->setFallbackURLs(manifest.fallbackURLs);
592     
593     startLoadingEntry();
594 }
595
596 void ApplicationCacheGroup::cacheUpdateFailed()
597 {
598     stopLoading();
599     m_manifestResource = 0;
600
601     // Wait for master resource loads to finish.
602     m_completionType = Failure;
603     deliverDelayedMainResources();
604 }
605     
606 void ApplicationCacheGroup::manifestNotFound()
607 {
608     makeObsolete();
609
610     postListenerTask(&DOMApplicationCache::callObsoleteListener, m_associatedDocumentLoaders);
611     postListenerTask(&DOMApplicationCache::callErrorListener, m_pendingMasterResourceLoaders);
612
613     stopLoading();
614
615     ASSERT(m_pendingEntries.isEmpty());
616     m_manifestResource = 0;
617
618     while (!m_pendingMasterResourceLoaders.isEmpty()) {
619         HashSet<DocumentLoader*>::iterator it = m_pendingMasterResourceLoaders.begin();
620         
621         ASSERT((*it)->candidateApplicationCacheGroup() == this);
622         ASSERT(!(*it)->applicationCache());
623         (*it)->setCandidateApplicationCacheGroup(0);
624         m_pendingMasterResourceLoaders.remove(it);
625     }
626
627     m_updateStatus = Idle;    
628     m_frame = 0;
629     
630     if (m_caches.isEmpty()) {
631         ASSERT(m_associatedDocumentLoaders.isEmpty());
632         ASSERT(!m_cacheBeingUpdated);
633         delete this;
634     }
635 }
636
637 void ApplicationCacheGroup::checkIfLoadIsComplete()
638 {
639     if (m_manifestHandle || !m_pendingEntries.isEmpty() || !m_pendingMasterResourceLoaders.isEmpty())
640         return;
641     
642     // We're done, all resources have finished downloading (successfully or not).
643
644     bool isUpgradeAttempt = m_newestCache;
645
646     switch (m_completionType) {
647     case None:
648         ASSERT_NOT_REACHED();
649         return;
650     case NoUpdate:
651         ASSERT(isUpgradeAttempt);
652         ASSERT(!m_cacheBeingUpdated);
653         ASSERT(m_storageID);
654         postListenerTask(&DOMApplicationCache::callNoUpdateListener, m_associatedDocumentLoaders);
655         break;
656     case Failure:
657         ASSERT(!m_cacheBeingUpdated);
658         postListenerTask(&DOMApplicationCache::callErrorListener, m_associatedDocumentLoaders);
659         if (m_caches.isEmpty()) {
660             ASSERT(m_associatedDocumentLoaders.isEmpty());
661             delete this;
662             return;
663         }
664         break;
665     case Completed: {
666         // 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>)
667
668         ASSERT(m_cacheBeingUpdated);
669         m_cacheBeingUpdated->setManifestResource(m_manifestResource.release());
670
671         RefPtr<ApplicationCache> oldNewestCache = (m_newestCache == m_cacheBeingUpdated) ? 0 : m_newestCache;
672
673         setNewestCache(m_cacheBeingUpdated.release());
674         cacheStorage().storeNewestCache(this);
675
676         if (oldNewestCache)
677             cacheStorage().remove(oldNewestCache.get());
678
679         postListenerTask(isUpgradeAttempt ? &DOMApplicationCache::callUpdateReadyListener : &DOMApplicationCache::callCachedListener, m_associatedDocumentLoaders);
680         break;
681     }
682     }
683
684     m_completionType = None;
685     m_updateStatus = Idle;
686     m_frame = 0;
687 }
688
689 void ApplicationCacheGroup::startLoadingEntry()
690 {
691     ASSERT(m_cacheBeingUpdated);
692
693     if (m_pendingEntries.isEmpty()) {
694         m_completionType = Completed;
695         deliverDelayedMainResources();
696         return;
697     }
698     
699     EntryMap::const_iterator it = m_pendingEntries.begin();
700
701     postListenerTask(&DOMApplicationCache::callProgressListener, m_associatedDocumentLoaders);
702
703     // FIXME: If this is an upgrade attempt, the newest cache should be used as an HTTP cache.
704     
705     ASSERT(!m_currentHandle);
706     
707     ResourceRequest request(it->first);
708     m_frame->loader()->applyUserAgent(request);
709     // FIXME: Should ask to revalidate from origin.
710
711     m_currentHandle = ResourceHandle::create(request, this, m_frame, false, true, false);
712 }
713
714 void ApplicationCacheGroup::deliverDelayedMainResources()
715 {
716     // Need to copy loaders, because the cache group may be destroyed at the end of iteration.
717     Vector<DocumentLoader*> loaders;
718     copyToVector(m_pendingMasterResourceLoaders, loaders);
719     size_t count = loaders.size();
720     for (size_t i = 0; i != count; ++i) {
721         DocumentLoader* loader = loaders[i];
722         if (loader->isLoadingMainResource())
723             continue;
724
725         const ResourceError& error = loader->mainDocumentError();
726         if (error.isNull())
727             finishedLoadingMainResource(loader);
728         else
729             failedLoadingMainResource(loader);
730     }
731     if (!count)
732         checkIfLoadIsComplete();
733 }
734
735 void ApplicationCacheGroup::addEntry(const String& url, unsigned type)
736 {
737     ASSERT(m_cacheBeingUpdated);
738     
739     // Don't add the URL if we already have an master resource in the cache
740     // (i.e., the main resource finished loading before the manifest).
741     if (ApplicationCacheResource* resource = m_cacheBeingUpdated->resourceForURL(url)) {
742         ASSERT(resource->type() & ApplicationCacheResource::Master);
743         ASSERT(!m_frame->loader()->documentLoader()->isLoadingMainResource());
744     
745         resource->addType(type);
746         return;
747     }
748
749     // Don't add the URL if it's the same as the manifest URL.
750     ASSERT(m_manifestResource);
751     if (m_manifestResource->url() == url) {
752         m_manifestResource->addType(type);
753         return;
754     }
755     
756     pair<EntryMap::iterator, bool> result = m_pendingEntries.add(url, type);
757     
758     if (!result.second)
759         result.first->second |= type;
760 }
761
762 void ApplicationCacheGroup::associateDocumentLoaderWithCache(DocumentLoader* loader, ApplicationCache* cache)
763 {
764     // If teardown started already, revive the group.
765     if (!m_newestCache && !m_cacheBeingUpdated)
766         m_newestCache = cache;
767
768     ASSERT(!m_isObsolete);
769
770     loader->setApplicationCache(cache);
771
772     ASSERT(!m_associatedDocumentLoaders.contains(loader));
773     m_associatedDocumentLoaders.add(loader);
774 }
775  
776 class CallCacheListenerTask : public ScriptExecutionContext::Task {
777     typedef void (DOMApplicationCache::*ListenerFunction)();
778 public:
779     static PassRefPtr<CallCacheListenerTask> create(ListenerFunction listenerFunction)
780     {
781         return adoptRef(new CallCacheListenerTask(listenerFunction));
782     }
783
784     virtual void performTask(ScriptExecutionContext* context)
785     {
786         ASSERT(context->isDocument());
787         if (DOMWindow* window = static_cast<Document*>(context)->domWindow()) {
788             if (DOMApplicationCache* domCache = window->optionalApplicationCache())
789                 (domCache->*m_listenerFunction)();
790         }
791     }
792
793 private:
794     CallCacheListenerTask(ListenerFunction listenerFunction)
795         : m_listenerFunction(listenerFunction)
796     {
797     }
798
799     ListenerFunction m_listenerFunction;
800 };
801
802 void ApplicationCacheGroup::postListenerTask(ListenerFunction listenerFunction, const HashSet<DocumentLoader*>& loaderSet)
803 {
804     HashSet<DocumentLoader*>::const_iterator loaderSetEnd = loaderSet.end();
805     for (HashSet<DocumentLoader*>::const_iterator iter = loaderSet.begin(); iter != loaderSetEnd; ++iter)
806         postListenerTask(listenerFunction, *iter);
807 }
808
809 void ApplicationCacheGroup::postListenerTask(ListenerFunction listenerFunction, DocumentLoader* loader)
810 {
811     Frame* frame = loader->frame();
812     if (!frame)
813         return;
814     
815     ASSERT(frame->loader()->documentLoader() == loader);
816
817     frame->document()->postTask(CallCacheListenerTask::create(listenerFunction));
818 }
819
820 void ApplicationCacheGroup::clearStorageID()
821 {
822     m_storageID = 0;
823     
824     HashSet<ApplicationCache*>::const_iterator end = m_caches.end();
825     for (HashSet<ApplicationCache*>::const_iterator it = m_caches.begin(); it != end; ++it)
826         (*it)->clearStorageID();
827 }
828     
829
830 }
831
832 #endif // ENABLE(OFFLINE_WEB_APPLICATIONS)