initial import
[vuplus_webkit] / Source / WebCore / loader / appcache / ApplicationCacheGroup.cpp
1 /*
2  * Copyright (C) 2008, 2009, 2010 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 "Console.h"
38 #include "DOMApplicationCache.h"
39 #include "DOMWindow.h"
40 #include "DocumentLoader.h"
41 #include "Frame.h"
42 #include "FrameLoader.h"
43 #include "FrameLoaderClient.h"
44 #include "InspectorInstrumentation.h"
45 #include "MainResourceLoader.h"
46 #include "ManifestParser.h"
47 #include "Page.h"
48 #include "ScriptProfile.h"
49 #include "SecurityOrigin.h"
50 #include "Settings.h"
51 #include <wtf/HashMap.h>
52 #include <wtf/MainThread.h>
53 #include <wtf/UnusedParam.h>
54
55 #if ENABLE(INSPECTOR)
56 #include "ProgressTracker.h"
57 #endif
58
59 namespace WebCore {
60
61 ApplicationCacheGroup::ApplicationCacheGroup(const KURL& manifestURL, bool isCopy)
62     : m_manifestURL(manifestURL)
63     , m_origin(SecurityOrigin::create(manifestURL))
64     , m_updateStatus(Idle)
65     , m_downloadingPendingMasterResourceLoadersCount(0)
66     , m_progressTotal(0)
67     , m_progressDone(0)
68     , m_frame(0)
69     , m_storageID(0)
70     , m_isObsolete(false)
71     , m_completionType(None)
72     , m_isCopy(isCopy)
73     , m_calledReachedMaxAppCacheSize(false)
74     , m_availableSpaceInQuota(ApplicationCacheStorage::unknownQuota())
75     , m_originQuotaExceededPreviously(false)
76 {
77 }
78
79 ApplicationCacheGroup::~ApplicationCacheGroup()
80 {
81     if (m_isCopy) {
82         ASSERT(m_newestCache);
83         ASSERT(m_caches.size() == 1);
84         ASSERT(m_caches.contains(m_newestCache.get()));
85         ASSERT(!m_cacheBeingUpdated);
86         ASSERT(m_associatedDocumentLoaders.isEmpty());
87         ASSERT(m_pendingMasterResourceLoaders.isEmpty());
88         ASSERT(m_newestCache->group() == this);
89         
90         return;
91     }
92                
93     ASSERT(!m_newestCache);
94     ASSERT(m_caches.isEmpty());
95     
96     stopLoading();
97     
98     cacheStorage().cacheGroupDestroyed(this);
99 }
100     
101 ApplicationCache* ApplicationCacheGroup::cacheForMainRequest(const ResourceRequest& request, DocumentLoader*)
102 {
103     if (!ApplicationCache::requestIsHTTPOrHTTPSGet(request))
104         return 0;
105
106     KURL url(request.url());
107     if (url.hasFragmentIdentifier())
108         url.removeFragmentIdentifier();
109
110     if (ApplicationCacheGroup* group = cacheStorage().cacheGroupForURL(url)) {
111         ASSERT(group->newestCache());
112         ASSERT(!group->isObsolete());
113         
114         return group->newestCache();
115     }
116     
117     return 0;
118 }
119     
120 ApplicationCache* ApplicationCacheGroup::fallbackCacheForMainRequest(const ResourceRequest& request, DocumentLoader*)
121 {
122     if (!ApplicationCache::requestIsHTTPOrHTTPSGet(request))
123         return 0;
124
125     KURL url(request.url());
126     if (url.hasFragmentIdentifier())
127         url.removeFragmentIdentifier();
128
129     if (ApplicationCacheGroup* group = cacheStorage().fallbackCacheGroupForURL(url)) {
130         ASSERT(group->newestCache());
131         ASSERT(!group->isObsolete());
132
133         return group->newestCache();
134     }
135     
136     return 0;
137 }
138
139 void ApplicationCacheGroup::selectCache(Frame* frame, const KURL& passedManifestURL)
140 {
141     ASSERT(frame && frame->page());
142     
143     if (!frame->settings()->offlineWebApplicationCacheEnabled())
144         return;
145     
146     DocumentLoader* documentLoader = frame->loader()->documentLoader();
147     ASSERT(!documentLoader->applicationCacheHost()->applicationCache());
148
149     if (passedManifestURL.isNull()) {
150         selectCacheWithoutManifestURL(frame);        
151         return;
152     }
153
154     KURL manifestURL(passedManifestURL);
155     if (manifestURL.hasFragmentIdentifier())
156         manifestURL.removeFragmentIdentifier();
157
158     ApplicationCache* mainResourceCache = documentLoader->applicationCacheHost()->mainResourceApplicationCache();
159     
160     if (mainResourceCache) {
161         if (manifestURL == mainResourceCache->group()->m_manifestURL) {
162             // The cache may have gotten obsoleted after we've loaded from it, but before we parsed the document and saw cache manifest.
163             if (mainResourceCache->group()->isObsolete())
164                 return;
165             mainResourceCache->group()->associateDocumentLoaderWithCache(documentLoader, mainResourceCache);
166             mainResourceCache->group()->update(frame, ApplicationCacheUpdateWithBrowsingContext);
167         } else {
168             // The main resource was loaded from cache, so the cache must have an entry for it. Mark it as foreign.
169             KURL resourceURL(documentLoader->responseURL());
170             if (resourceURL.hasFragmentIdentifier())
171                 resourceURL.removeFragmentIdentifier();
172             ApplicationCacheResource* resource = mainResourceCache->resourceForURL(resourceURL);
173             bool inStorage = resource->storageID();
174             resource->addType(ApplicationCacheResource::Foreign);
175             if (inStorage)
176                 cacheStorage().storeUpdatedType(resource, mainResourceCache);
177
178             // Restart the current navigation from the top of the navigation algorithm, undoing any changes that were made
179             // as part of the initial load.
180             // The navigation will not result in the same resource being loaded, because "foreign" entries are never picked during navigation.
181             frame->navigationScheduler()->scheduleLocationChange(frame->document()->securityOrigin(), documentLoader->url(), frame->loader()->referrer());
182         }
183         
184         return;
185     }
186     
187     // The resource was loaded from the network, check if it is a HTTP/HTTPS GET.    
188     const ResourceRequest& request = frame->loader()->activeDocumentLoader()->request();
189
190     if (!ApplicationCache::requestIsHTTPOrHTTPSGet(request))
191         return;
192
193     // Check that the resource URL has the same scheme/host/port as the manifest URL.
194     if (!protocolHostAndPortAreEqual(manifestURL, request.url()))
195         return;
196
197     // Don't change anything on disk if private browsing is enabled.
198     if (!frame->settings() || frame->settings()->privateBrowsingEnabled()) {
199         postListenerTask(ApplicationCacheHost::CHECKING_EVENT, documentLoader);
200         postListenerTask(ApplicationCacheHost::ERROR_EVENT, documentLoader);
201         return;
202     }
203
204     ApplicationCacheGroup* group = cacheStorage().findOrCreateCacheGroup(manifestURL);
205
206     documentLoader->applicationCacheHost()->setCandidateApplicationCacheGroup(group);
207     group->m_pendingMasterResourceLoaders.add(documentLoader);
208     group->m_downloadingPendingMasterResourceLoadersCount++;
209
210     ASSERT(!group->m_cacheBeingUpdated || group->m_updateStatus != Idle);
211     group->update(frame, ApplicationCacheUpdateWithBrowsingContext);
212 }
213
214 void ApplicationCacheGroup::selectCacheWithoutManifestURL(Frame* frame)
215 {
216     if (!frame->settings()->offlineWebApplicationCacheEnabled())
217         return;
218
219     DocumentLoader* documentLoader = frame->loader()->documentLoader();
220     ASSERT(!documentLoader->applicationCacheHost()->applicationCache());
221
222     ApplicationCache* mainResourceCache = documentLoader->applicationCacheHost()->mainResourceApplicationCache();
223
224     if (mainResourceCache) {
225         mainResourceCache->group()->associateDocumentLoaderWithCache(documentLoader, mainResourceCache);
226         mainResourceCache->group()->update(frame, ApplicationCacheUpdateWithBrowsingContext);
227     }
228 }
229
230 void ApplicationCacheGroup::finishedLoadingMainResource(DocumentLoader* loader)
231 {
232     ASSERT(m_pendingMasterResourceLoaders.contains(loader));
233     ASSERT(m_completionType == None || m_pendingEntries.isEmpty());
234     KURL url = loader->url();
235     if (url.hasFragmentIdentifier())
236         url.removeFragmentIdentifier();
237
238     switch (m_completionType) {
239     case None:
240         // The main resource finished loading before the manifest was ready. It will be handled via dispatchMainResources() later.
241         return;
242     case NoUpdate:
243         ASSERT(!m_cacheBeingUpdated);
244         associateDocumentLoaderWithCache(loader, m_newestCache.get());
245
246         if (ApplicationCacheResource* resource = m_newestCache->resourceForURL(url)) {
247             if (!(resource->type() & ApplicationCacheResource::Master)) {
248                 resource->addType(ApplicationCacheResource::Master);
249                 ASSERT(!resource->storageID());
250             }
251         } else
252             m_newestCache->addResource(ApplicationCacheResource::create(url, loader->response(), ApplicationCacheResource::Master, loader->mainResourceData()));
253
254         break;
255     case Failure:
256         // Cache update has been a failure, so there is no reason to keep the document associated with the incomplete cache
257         // (its main resource was not cached yet, so it is likely that the application changed significantly server-side).
258         ASSERT(!m_cacheBeingUpdated); // Already cleared out by stopLoading().
259         loader->applicationCacheHost()->setApplicationCache(0); // Will unset candidate, too.
260         m_associatedDocumentLoaders.remove(loader);
261         postListenerTask(ApplicationCacheHost::ERROR_EVENT, loader);
262         break;
263     case Completed:
264         ASSERT(m_associatedDocumentLoaders.contains(loader));
265
266         if (ApplicationCacheResource* resource = m_cacheBeingUpdated->resourceForURL(url)) {
267             if (!(resource->type() & ApplicationCacheResource::Master)) {
268                 resource->addType(ApplicationCacheResource::Master);
269                 ASSERT(!resource->storageID());
270             }
271         } else
272             m_cacheBeingUpdated->addResource(ApplicationCacheResource::create(url, loader->response(), ApplicationCacheResource::Master, loader->mainResourceData()));
273         // The "cached" event will be posted to all associated documents once update is complete.
274         break;
275     }
276
277     ASSERT(m_downloadingPendingMasterResourceLoadersCount > 0);
278     m_downloadingPendingMasterResourceLoadersCount--;
279     checkIfLoadIsComplete();
280 }
281
282 void ApplicationCacheGroup::failedLoadingMainResource(DocumentLoader* loader)
283 {
284     ASSERT(m_pendingMasterResourceLoaders.contains(loader));
285     ASSERT(m_completionType == None || m_pendingEntries.isEmpty());
286
287     switch (m_completionType) {
288     case None:
289         // The main resource finished loading before the manifest was ready. It will be handled via dispatchMainResources() later.
290         return;
291     case NoUpdate:
292         ASSERT(!m_cacheBeingUpdated);
293
294         // 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,
295         // 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.
296         postListenerTask(ApplicationCacheHost::ERROR_EVENT, loader);
297
298         break;
299     case Failure:
300         // Cache update failed, too.
301         ASSERT(!m_cacheBeingUpdated); // Already cleared out by stopLoading().
302         ASSERT(!loader->applicationCacheHost()->applicationCache() || loader->applicationCacheHost()->applicationCache() == m_cacheBeingUpdated);
303
304         loader->applicationCacheHost()->setApplicationCache(0); // Will unset candidate, too.
305         m_associatedDocumentLoaders.remove(loader);
306         postListenerTask(ApplicationCacheHost::ERROR_EVENT, loader);
307         break;
308     case Completed:
309         // The cache manifest didn't list this main resource, and all cache entries were already updated successfully - but the main resource failed to load,
310         // so it cannot be stored to the cache. If there are other main resources being downloaded for this cache group, they may still succeed.
311         ASSERT(m_associatedDocumentLoaders.contains(loader));
312         ASSERT(loader->applicationCacheHost()->applicationCache() == m_cacheBeingUpdated);
313         ASSERT(!loader->applicationCacheHost()->candidateApplicationCacheGroup());
314         m_associatedDocumentLoaders.remove(loader);
315         loader->applicationCacheHost()->setApplicationCache(0);
316
317         postListenerTask(ApplicationCacheHost::ERROR_EVENT, loader);
318
319         break;
320     }
321
322     ASSERT(m_downloadingPendingMasterResourceLoadersCount > 0);
323     m_downloadingPendingMasterResourceLoadersCount--;
324     checkIfLoadIsComplete();
325 }
326
327 void ApplicationCacheGroup::stopLoading()
328 {
329     if (m_manifestHandle) {
330         ASSERT(!m_currentHandle);
331
332         ASSERT(m_manifestHandle->client() == this);
333         m_manifestHandle->setClient(0);
334
335         m_manifestHandle->cancel();
336         m_manifestHandle = 0;
337     }
338     
339     if (m_currentHandle) {
340         ASSERT(!m_manifestHandle);
341         ASSERT(m_cacheBeingUpdated);
342
343         ASSERT(m_currentHandle->client() == this);
344         m_currentHandle->setClient(0);
345
346         m_currentHandle->cancel();
347         m_currentHandle = 0;
348     }    
349
350     // FIXME: Resetting just a tiny part of the state in this function is confusing. Callers have to take care of a lot more.
351     m_cacheBeingUpdated = 0;
352     m_pendingEntries.clear();
353 }    
354
355 void ApplicationCacheGroup::disassociateDocumentLoader(DocumentLoader* loader)
356 {
357     HashSet<DocumentLoader*>::iterator it = m_associatedDocumentLoaders.find(loader);
358     if (it != m_associatedDocumentLoaders.end())
359         m_associatedDocumentLoaders.remove(it);
360
361     m_pendingMasterResourceLoaders.remove(loader);
362
363     loader->applicationCacheHost()->setApplicationCache(0); // Will set candidate to 0, too.
364
365     if (!m_associatedDocumentLoaders.isEmpty() || !m_pendingMasterResourceLoaders.isEmpty())
366         return;
367
368     if (m_caches.isEmpty()) {
369         // There is an initial cache attempt in progress.
370         ASSERT(!m_newestCache);
371         // Delete ourselves, causing the cache attempt to be stopped.
372         delete this;
373         return;
374     }
375
376     ASSERT(m_caches.contains(m_newestCache.get()));
377
378     // Release our reference to the newest cache. This could cause us to be deleted.
379     // Any ongoing updates will be stopped from destructor.
380     m_newestCache.release();
381 }
382
383 void ApplicationCacheGroup::cacheDestroyed(ApplicationCache* cache)
384 {
385     if (!m_caches.contains(cache))
386         return;
387     
388     m_caches.remove(cache);
389
390     if (m_caches.isEmpty()) {
391         ASSERT(m_associatedDocumentLoaders.isEmpty());
392         ASSERT(m_pendingMasterResourceLoaders.isEmpty());
393         delete this;
394     }
395 }
396
397 void ApplicationCacheGroup::stopLoadingInFrame(Frame* frame)
398 {
399     if (frame != m_frame)
400         return;
401
402     cacheUpdateFailed();
403 }
404
405 void ApplicationCacheGroup::setNewestCache(PassRefPtr<ApplicationCache> newestCache)
406 {
407     m_newestCache = newestCache;
408
409     m_caches.add(m_newestCache.get());
410     m_newestCache->setGroup(this);
411     InspectorInstrumentation::updateApplicationCacheStatus(m_frame);
412 }
413
414 void ApplicationCacheGroup::makeObsolete()
415 {
416     if (isObsolete())
417         return;
418
419     m_isObsolete = true;
420     cacheStorage().cacheGroupMadeObsolete(this);
421     ASSERT(!m_storageID);
422     InspectorInstrumentation::updateApplicationCacheStatus(m_frame);
423 }
424
425 void ApplicationCacheGroup::update(Frame* frame, ApplicationCacheUpdateOption updateOption)
426 {
427     if (m_updateStatus == Checking || m_updateStatus == Downloading) {
428         if (updateOption == ApplicationCacheUpdateWithBrowsingContext) {
429             postListenerTask(ApplicationCacheHost::CHECKING_EVENT, frame->loader()->documentLoader());
430             if (m_updateStatus == Downloading)
431                 postListenerTask(ApplicationCacheHost::DOWNLOADING_EVENT, frame->loader()->documentLoader());
432         }
433         return;
434     }
435
436     // Don't change anything on disk if private browsing is enabled.
437     if (!frame->settings() || frame->settings()->privateBrowsingEnabled()) {
438         ASSERT(m_pendingMasterResourceLoaders.isEmpty());
439         ASSERT(m_pendingEntries.isEmpty());
440         ASSERT(!m_cacheBeingUpdated);
441         postListenerTask(ApplicationCacheHost::CHECKING_EVENT, frame->loader()->documentLoader());
442         postListenerTask(ApplicationCacheHost::NOUPDATE_EVENT, frame->loader()->documentLoader());
443         return;
444     }
445
446     ASSERT(!m_frame);
447     m_frame = frame;
448
449     setUpdateStatus(Checking);
450
451     postListenerTask(ApplicationCacheHost::CHECKING_EVENT, m_associatedDocumentLoaders);
452     if (!m_newestCache) {
453         ASSERT(updateOption == ApplicationCacheUpdateWithBrowsingContext);
454         postListenerTask(ApplicationCacheHost::CHECKING_EVENT, frame->loader()->documentLoader());
455     }
456     
457     ASSERT(!m_manifestHandle);
458     ASSERT(!m_manifestResource);
459     ASSERT(!m_currentHandle);
460     ASSERT(!m_currentResource);
461     ASSERT(m_completionType == None);
462
463     // FIXME: Handle defer loading
464     m_manifestHandle = createResourceHandle(m_manifestURL, m_newestCache ? m_newestCache->manifestResource() : 0);
465 }
466
467 PassRefPtr<ResourceHandle> ApplicationCacheGroup::createResourceHandle(const KURL& url, ApplicationCacheResource* newestCachedResource)
468 {
469     ResourceRequest request(url);
470     m_frame->loader()->applyUserAgent(request);
471     request.setHTTPHeaderField("Cache-Control", "max-age=0");
472
473     if (newestCachedResource) {
474         const String& lastModified = newestCachedResource->response().httpHeaderField("Last-Modified");
475         const String& eTag = newestCachedResource->response().httpHeaderField("ETag");
476         if (!lastModified.isEmpty() || !eTag.isEmpty()) {
477             if (!lastModified.isEmpty())
478                 request.setHTTPHeaderField("If-Modified-Since", lastModified);
479             if (!eTag.isEmpty())
480                 request.setHTTPHeaderField("If-None-Match", eTag);
481         }
482     }
483
484     RefPtr<ResourceHandle> handle = ResourceHandle::create(m_frame->loader()->networkingContext(), request, this, false, true);
485 #if ENABLE(INSPECTOR)
486     // Because willSendRequest only gets called during redirects, we initialize
487     // the identifier and the first willSendRequest here.
488     m_currentResourceIdentifier = m_frame->page()->progress()->createUniqueIdentifier();
489     ResourceResponse redirectResponse = ResourceResponse();
490     InspectorInstrumentation::willSendRequest(m_frame, m_currentResourceIdentifier, m_frame->loader()->documentLoader(), request, redirectResponse);
491 #endif
492     return handle;
493 }
494
495 void ApplicationCacheGroup::didReceiveResponse(ResourceHandle* handle, const ResourceResponse& response)
496 {
497 #if ENABLE(INSPECTOR)
498     DocumentLoader* loader = (handle == m_manifestHandle) ? 0 : m_frame->loader()->documentLoader();
499     InspectorInstrumentationCookie cookie = InspectorInstrumentation::willReceiveResourceResponse(m_frame, m_currentResourceIdentifier, response);
500     InspectorInstrumentation::didReceiveResourceResponse(cookie, m_currentResourceIdentifier, loader, response);
501 #endif
502
503     if (handle == m_manifestHandle) {
504         didReceiveManifestResponse(response);
505         return;
506     }
507     
508     ASSERT(handle == m_currentHandle);
509
510     KURL url(handle->firstRequest().url());
511     if (url.hasFragmentIdentifier())
512         url.removeFragmentIdentifier();
513     
514     ASSERT(!m_currentResource);
515     ASSERT(m_pendingEntries.contains(url));
516     
517     unsigned type = m_pendingEntries.get(url);
518     
519     // If this is an initial cache attempt, we should not get master resources delivered here.
520     if (!m_newestCache)
521         ASSERT(!(type & ApplicationCacheResource::Master));
522
523     if (m_newestCache && response.httpStatusCode() == 304) { // Not modified.
524         ApplicationCacheResource* newestCachedResource = m_newestCache->resourceForURL(url);
525         if (newestCachedResource) {
526             m_cacheBeingUpdated->addResource(ApplicationCacheResource::create(url, newestCachedResource->response(), type, newestCachedResource->data(), newestCachedResource->path()));
527             m_pendingEntries.remove(m_currentHandle->firstRequest().url());
528             m_currentHandle->cancel();
529             m_currentHandle = 0;
530             // Load the next resource, if any.
531             startLoadingEntry();
532             return;
533         }
534         // The server could return 304 for an unconditional request - in this case, we handle the response as a normal error.
535     }
536
537     if (response.httpStatusCode() / 100 != 2 || response.url() != m_currentHandle->firstRequest().url()) {
538         if ((type & ApplicationCacheResource::Explicit) || (type & ApplicationCacheResource::Fallback)) {
539             m_frame->domWindow()->console()->addMessage(OtherMessageSource, LogMessageType, ErrorMessageLevel, "Application Cache update failed, because " + m_currentHandle->firstRequest().url().string() + 
540                 ((response.httpStatusCode() / 100 != 2) ? " could not be fetched." : " was redirected."), 0, String());
541             // Note that cacheUpdateFailed() can cause the cache group to be deleted.
542             cacheUpdateFailed();
543         } else if (response.httpStatusCode() == 404 || response.httpStatusCode() == 410) {
544             // Skip this resource. It is dropped from the cache.
545             m_currentHandle->cancel();
546             m_currentHandle = 0;
547             m_pendingEntries.remove(url);
548             // Load the next resource, if any.
549             startLoadingEntry();
550         } else {
551             // Copy the resource and its metadata from the newest application cache in cache group whose completeness flag is complete, and act
552             // as if that was the fetched resource, ignoring the resource obtained from the network.
553             ASSERT(m_newestCache);
554             ApplicationCacheResource* newestCachedResource = m_newestCache->resourceForURL(handle->firstRequest().url());
555             ASSERT(newestCachedResource);
556             m_cacheBeingUpdated->addResource(ApplicationCacheResource::create(url, newestCachedResource->response(), type, newestCachedResource->data(), newestCachedResource->path()));
557             m_pendingEntries.remove(m_currentHandle->firstRequest().url());
558             m_currentHandle->cancel();
559             m_currentHandle = 0;
560             // Load the next resource, if any.
561             startLoadingEntry();
562         }
563         return;
564     }
565     
566     m_currentResource = ApplicationCacheResource::create(url, response, type);
567 }
568
569 void ApplicationCacheGroup::didReceiveData(ResourceHandle* handle, const char* data, int length, int encodedDataLength)
570 {
571     UNUSED_PARAM(encodedDataLength);
572
573 #if ENABLE(INSPECTOR)
574     InspectorInstrumentation::didReceiveData(m_frame, m_currentResourceIdentifier, 0, length, 0);
575 #endif
576
577     if (handle == m_manifestHandle) {
578         didReceiveManifestData(data, length);
579         return;
580     }
581     
582     ASSERT(handle == m_currentHandle);
583     
584     ASSERT(m_currentResource);
585     m_currentResource->data()->append(data, length);
586 }
587
588 void ApplicationCacheGroup::didFinishLoading(ResourceHandle* handle, double finishTime)
589 {
590 #if ENABLE(INSPECTOR)
591     InspectorInstrumentation::didFinishLoading(m_frame, m_frame->loader()->documentLoader(), m_currentResourceIdentifier, finishTime);
592 #endif
593
594     if (handle == m_manifestHandle) {
595         didFinishLoadingManifest();
596         return;
597     }
598
599     ASSERT(m_currentHandle == handle);
600     ASSERT(m_pendingEntries.contains(handle->firstRequest().url()));
601     
602     m_pendingEntries.remove(handle->firstRequest().url());
603     
604     ASSERT(m_cacheBeingUpdated);
605
606     m_cacheBeingUpdated->addResource(m_currentResource.release());
607     m_currentHandle = 0;
608
609     // While downloading check to see if we have exceeded the available quota.
610     // We can stop immediately if we have already previously failed
611     // due to an earlier quota restriction. The client was already notified
612     // of the quota being reached and decided not to increase it then.
613     // FIXME: Should we break earlier and prevent redownloading on later page loads?
614     if (m_originQuotaExceededPreviously && m_availableSpaceInQuota < m_cacheBeingUpdated->estimatedSizeInStorage()) {
615         m_currentResource = 0;
616         m_frame->domWindow()->console()->addMessage(OtherMessageSource, LogMessageType, ErrorMessageLevel, "Application Cache update failed, because size quota was exceeded.", 0, String());
617         cacheUpdateFailed();
618         return;
619     }
620     
621     // Load the next resource, if any.
622     startLoadingEntry();
623 }
624
625 void ApplicationCacheGroup::didFail(ResourceHandle* handle, const ResourceError& error)
626 {
627 #if ENABLE(INSPECTOR)
628     InspectorInstrumentation::didFailLoading(m_frame, m_frame->loader()->documentLoader(), m_currentResourceIdentifier, error);
629 #endif
630
631     if (handle == m_manifestHandle) {
632         // A network error is logged elsewhere, no need to log again. Also, it's normal for manifest fetching to fail when working offline.
633         cacheUpdateFailed();
634         return;
635     }
636
637     ASSERT(handle == m_currentHandle);
638
639     unsigned type = m_currentResource ? m_currentResource->type() : m_pendingEntries.get(handle->firstRequest().url());
640     KURL url(handle->firstRequest().url());
641     if (url.hasFragmentIdentifier())
642         url.removeFragmentIdentifier();
643
644     ASSERT(!m_currentResource || !m_pendingEntries.contains(url));
645     m_currentResource = 0;
646     m_pendingEntries.remove(url);
647
648     if ((type & ApplicationCacheResource::Explicit) || (type & ApplicationCacheResource::Fallback)) {
649         m_frame->domWindow()->console()->addMessage(OtherMessageSource, LogMessageType, ErrorMessageLevel, "Application Cache update failed, because " + url.string() + " could not be fetched.", 0, String());
650         // Note that cacheUpdateFailed() can cause the cache group to be deleted.
651         cacheUpdateFailed();
652     } else {
653         // Copy the resource and its metadata from the newest application cache in cache group whose completeness flag is complete, and act
654         // as if that was the fetched resource, ignoring the resource obtained from the network.
655         ASSERT(m_newestCache);
656         ApplicationCacheResource* newestCachedResource = m_newestCache->resourceForURL(url);
657         ASSERT(newestCachedResource);
658         m_cacheBeingUpdated->addResource(ApplicationCacheResource::create(url, newestCachedResource->response(), type, newestCachedResource->data(), newestCachedResource->path()));
659         // Load the next resource, if any.
660         startLoadingEntry();
661     }
662 }
663
664 void ApplicationCacheGroup::didReceiveManifestResponse(const ResourceResponse& response)
665 {
666     ASSERT(!m_manifestResource);
667     ASSERT(m_manifestHandle);
668
669     if (response.httpStatusCode() == 404 || response.httpStatusCode() == 410) {
670         manifestNotFound();
671         return;
672     }
673
674     if (response.httpStatusCode() == 304)
675         return;
676
677     if (response.httpStatusCode() / 100 != 2) {
678         m_frame->domWindow()->console()->addMessage(OtherMessageSource, LogMessageType, ErrorMessageLevel, "Application Cache manifest could not be fetched.", 0, String());
679         cacheUpdateFailed();
680         return;
681     }
682
683     if (response.url() != m_manifestHandle->firstRequest().url()) {
684         m_frame->domWindow()->console()->addMessage(OtherMessageSource, LogMessageType, ErrorMessageLevel, "Application Cache manifest could not be fetched, because a redirection was attempted.", 0, String());
685         cacheUpdateFailed();
686         return;
687     }
688
689     if (!equalIgnoringCase(response.mimeType(), "text/cache-manifest")) {
690         m_frame->domWindow()->console()->addMessage(OtherMessageSource, LogMessageType, ErrorMessageLevel, "Application Cache manifest had an incorrect MIME type: " + response.mimeType() + ".", 0, String());
691         cacheUpdateFailed();
692         return;
693     }
694
695     m_manifestResource = ApplicationCacheResource::create(m_manifestHandle->firstRequest().url(), response, ApplicationCacheResource::Manifest);
696 }
697
698 void ApplicationCacheGroup::didReceiveManifestData(const char* data, int length)
699 {
700     if (m_manifestResource)
701         m_manifestResource->data()->append(data, length);
702 }
703
704 void ApplicationCacheGroup::didFinishLoadingManifest()
705 {
706     bool isUpgradeAttempt = m_newestCache;
707
708     if (!isUpgradeAttempt && !m_manifestResource) {
709         // The server returned 304 Not Modified even though we didn't send a conditional request.
710         m_frame->domWindow()->console()->addMessage(OtherMessageSource, LogMessageType, ErrorMessageLevel, "Application Cache manifest could not be fetched because of an unexpected 304 Not Modified server response.", 0, String());
711         cacheUpdateFailed();
712         return;
713     }
714
715     m_manifestHandle = 0;
716
717     // Check if the manifest was not modified.
718     if (isUpgradeAttempt) {
719         ApplicationCacheResource* newestManifest = m_newestCache->manifestResource();
720         ASSERT(newestManifest);
721     
722         if (!m_manifestResource || // The resource will be null if HTTP response was 304 Not Modified.
723             (newestManifest->data()->size() == m_manifestResource->data()->size() && !memcmp(newestManifest->data()->data(), m_manifestResource->data()->data(), newestManifest->data()->size()))) {
724
725             m_completionType = NoUpdate;
726             m_manifestResource = 0;
727             deliverDelayedMainResources();
728
729             return;
730         }
731     }
732     
733     Manifest manifest;
734     if (!parseManifest(m_manifestURL, m_manifestResource->data()->data(), m_manifestResource->data()->size(), manifest)) {
735         // At the time of this writing, lack of "CACHE MANIFEST" signature is the only reason for parseManifest to fail.
736         m_frame->domWindow()->console()->addMessage(OtherMessageSource, LogMessageType, ErrorMessageLevel, "Application Cache manifest could not be parsed. Does it start with CACHE MANIFEST?", 0, String());
737         cacheUpdateFailed();
738         return;
739     }
740
741     ASSERT(!m_cacheBeingUpdated);
742     m_cacheBeingUpdated = ApplicationCache::create();
743     m_cacheBeingUpdated->setGroup(this);
744
745     HashSet<DocumentLoader*>::const_iterator masterEnd = m_pendingMasterResourceLoaders.end();
746     for (HashSet<DocumentLoader*>::const_iterator iter = m_pendingMasterResourceLoaders.begin(); iter != masterEnd; ++iter)
747         associateDocumentLoaderWithCache(*iter, m_cacheBeingUpdated.get());
748
749     // We have the manifest, now download the resources.
750     setUpdateStatus(Downloading);
751     
752     postListenerTask(ApplicationCacheHost::DOWNLOADING_EVENT, m_associatedDocumentLoaders);
753
754     ASSERT(m_pendingEntries.isEmpty());
755
756     if (isUpgradeAttempt) {
757         ApplicationCache::ResourceMap::const_iterator end = m_newestCache->end();
758         for (ApplicationCache::ResourceMap::const_iterator it = m_newestCache->begin(); it != end; ++it) {
759             unsigned type = it->second->type();
760             if (type & ApplicationCacheResource::Master)
761                 addEntry(it->first, type);
762         }
763     }
764     
765     HashSet<String>::const_iterator end = manifest.explicitURLs.end();
766     for (HashSet<String>::const_iterator it = manifest.explicitURLs.begin(); it != end; ++it)
767         addEntry(*it, ApplicationCacheResource::Explicit);
768
769     size_t fallbackCount = manifest.fallbackURLs.size();
770     for (size_t i = 0; i  < fallbackCount; ++i)
771         addEntry(manifest.fallbackURLs[i].second, ApplicationCacheResource::Fallback);
772     
773     m_cacheBeingUpdated->setOnlineWhitelist(manifest.onlineWhitelistedURLs);
774     m_cacheBeingUpdated->setFallbackURLs(manifest.fallbackURLs);
775     m_cacheBeingUpdated->setAllowsAllNetworkRequests(manifest.allowAllNetworkRequests);
776
777     m_progressTotal = m_pendingEntries.size();
778     m_progressDone = 0;
779
780     recalculateAvailableSpaceInQuota();
781
782     startLoadingEntry();
783 }
784
785 void ApplicationCacheGroup::didReachMaxAppCacheSize()
786 {
787     ASSERT(m_frame);
788     ASSERT(m_cacheBeingUpdated);
789     m_frame->page()->chrome()->client()->reachedMaxAppCacheSize(cacheStorage().spaceNeeded(m_cacheBeingUpdated->estimatedSizeInStorage()));
790     m_calledReachedMaxAppCacheSize = true;
791     checkIfLoadIsComplete();
792 }
793
794 void ApplicationCacheGroup::didReachOriginQuota(int64_t totalSpaceNeeded)
795 {
796     // Inform the client the origin quota has been reached, they may decide to increase the quota.
797     // We expect quota to be increased synchronously while waiting for the call to return.
798     m_frame->page()->chrome()->client()->reachedApplicationCacheOriginQuota(m_origin.get(), totalSpaceNeeded);
799 }
800
801 void ApplicationCacheGroup::cacheUpdateFailed()
802 {
803     stopLoading();
804     m_manifestResource = 0;
805
806     // Wait for master resource loads to finish.
807     m_completionType = Failure;
808     deliverDelayedMainResources();
809 }
810
811 void ApplicationCacheGroup::recalculateAvailableSpaceInQuota()
812 {
813     if (!cacheStorage().calculateRemainingSizeForOriginExcludingCache(m_origin.get(), m_newestCache.get(), m_availableSpaceInQuota)) {
814         // Failed to determine what is left in the quota. Fallback to allowing anything.
815         m_availableSpaceInQuota = ApplicationCacheStorage::noQuota();
816     }
817 }
818     
819 void ApplicationCacheGroup::manifestNotFound()
820 {
821     makeObsolete();
822
823     postListenerTask(ApplicationCacheHost::OBSOLETE_EVENT, m_associatedDocumentLoaders);
824     postListenerTask(ApplicationCacheHost::ERROR_EVENT, m_pendingMasterResourceLoaders);
825
826     stopLoading();
827
828     ASSERT(m_pendingEntries.isEmpty());
829     m_manifestResource = 0;
830
831     while (!m_pendingMasterResourceLoaders.isEmpty()) {
832         HashSet<DocumentLoader*>::iterator it = m_pendingMasterResourceLoaders.begin();
833         
834         ASSERT((*it)->applicationCacheHost()->candidateApplicationCacheGroup() == this);
835         ASSERT(!(*it)->applicationCacheHost()->applicationCache());
836         (*it)->applicationCacheHost()->setCandidateApplicationCacheGroup(0);
837         m_pendingMasterResourceLoaders.remove(it);
838     }
839
840     m_downloadingPendingMasterResourceLoadersCount = 0;
841     setUpdateStatus(Idle);    
842     m_frame = 0;
843     
844     if (m_caches.isEmpty()) {
845         ASSERT(m_associatedDocumentLoaders.isEmpty());
846         ASSERT(!m_cacheBeingUpdated);
847         delete this;
848     }
849 }
850
851 void ApplicationCacheGroup::checkIfLoadIsComplete()
852 {
853     if (m_manifestHandle || !m_pendingEntries.isEmpty() || m_downloadingPendingMasterResourceLoadersCount)
854         return;
855     
856     // We're done, all resources have finished downloading (successfully or not).
857
858     bool isUpgradeAttempt = m_newestCache;
859
860     switch (m_completionType) {
861     case None:
862         ASSERT_NOT_REACHED();
863         return;
864     case NoUpdate:
865         ASSERT(isUpgradeAttempt);
866         ASSERT(!m_cacheBeingUpdated);
867
868         // The storage could have been manually emptied by the user.
869         if (!m_storageID)
870             cacheStorage().storeNewestCache(this);
871
872         postListenerTask(ApplicationCacheHost::NOUPDATE_EVENT, m_associatedDocumentLoaders);
873         break;
874     case Failure:
875         ASSERT(!m_cacheBeingUpdated);
876         postListenerTask(ApplicationCacheHost::ERROR_EVENT, m_associatedDocumentLoaders);
877         if (m_caches.isEmpty()) {
878             ASSERT(m_associatedDocumentLoaders.isEmpty());
879             delete this;
880             return;
881         }
882         break;
883     case Completed: {
884         // 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>)
885
886         ASSERT(m_cacheBeingUpdated);
887         if (m_manifestResource)
888             m_cacheBeingUpdated->setManifestResource(m_manifestResource.release());
889         else {
890             // We can get here as a result of retrying the Complete step, following
891             // a failure of the cache storage to save the newest cache due to hitting
892             // the maximum size. In such a case, m_manifestResource may be 0, as
893             // the manifest was already set on the newest cache object.
894             ASSERT(cacheStorage().isMaximumSizeReached() && m_calledReachedMaxAppCacheSize);
895         }
896
897         RefPtr<ApplicationCache> oldNewestCache = (m_newestCache == m_cacheBeingUpdated) ? RefPtr<ApplicationCache>() : m_newestCache;
898
899         // If we exceeded the origin quota while downloading we can request a quota
900         // increase now, before we attempt to store the cache.
901         int64_t totalSpaceNeeded;
902         if (!cacheStorage().checkOriginQuota(this, oldNewestCache.get(), m_cacheBeingUpdated.get(), totalSpaceNeeded))
903             didReachOriginQuota(totalSpaceNeeded);
904
905         ApplicationCacheStorage::FailureReason failureReason;
906         setNewestCache(m_cacheBeingUpdated.release());
907         if (cacheStorage().storeNewestCache(this, oldNewestCache.get(), failureReason)) {
908             // New cache stored, now remove the old cache.
909             if (oldNewestCache)
910                 cacheStorage().remove(oldNewestCache.get());
911
912             // Fire the final progress event.
913             ASSERT(m_progressDone == m_progressTotal);
914             postListenerTask(ApplicationCacheHost::PROGRESS_EVENT, m_progressTotal, m_progressDone, m_associatedDocumentLoaders);
915
916             // Fire the success event.
917             postListenerTask(isUpgradeAttempt ? ApplicationCacheHost::UPDATEREADY_EVENT : ApplicationCacheHost::CACHED_EVENT, m_associatedDocumentLoaders);
918             // It is clear that the origin quota was not reached, so clear the flag if it was set.
919             m_originQuotaExceededPreviously = false;
920         } else {
921             if (failureReason == ApplicationCacheStorage::OriginQuotaReached) {
922                 // We ran out of space for this origin. Fall down to the normal error handling
923                 // after recording this state.
924                 m_originQuotaExceededPreviously = true;
925                 m_frame->domWindow()->console()->addMessage(OtherMessageSource, LogMessageType, ErrorMessageLevel, "Application Cache update failed, because size quota was exceeded.", 0, String());
926             }
927
928             if (failureReason == ApplicationCacheStorage::TotalQuotaReached && !m_calledReachedMaxAppCacheSize) {
929                 // FIXME: Should this be handled more like Origin Quotas? Does this fail properly?
930
931                 // We ran out of space. All the changes in the cache storage have
932                 // been rolled back. We roll back to the previous state in here,
933                 // as well, call the chrome client asynchronously and retry to
934                 // save the new cache.
935
936                 // Save a reference to the new cache.
937                 m_cacheBeingUpdated = m_newestCache.release();
938                 if (oldNewestCache) {
939                     // Reinstate the oldNewestCache.
940                     setNewestCache(oldNewestCache.release());
941                 }
942                 scheduleReachedMaxAppCacheSizeCallback();
943                 return;
944             }
945
946             // Run the "cache failure steps"
947             // Fire the error events to all pending master entries, as well any other cache hosts
948             // currently associated with a cache in this group.
949             postListenerTask(ApplicationCacheHost::ERROR_EVENT, m_associatedDocumentLoaders);
950             // Disassociate the pending master entries from the failed new cache. Note that
951             // all other loaders in the m_associatedDocumentLoaders are still associated with
952             // some other cache in this group. They are not associated with the failed new cache.
953
954             // Need to copy loaders, because the cache group may be destroyed at the end of iteration.
955             Vector<DocumentLoader*> loaders;
956             copyToVector(m_pendingMasterResourceLoaders, loaders);
957             size_t count = loaders.size();
958             for (size_t i = 0; i != count; ++i)
959                 disassociateDocumentLoader(loaders[i]); // This can delete this group.
960
961             // Reinstate the oldNewestCache, if there was one.
962             if (oldNewestCache) {
963                 // This will discard the failed new cache.
964                 setNewestCache(oldNewestCache.release());
965             } else {
966                 // We must have been deleted by the last call to disassociateDocumentLoader().
967                 return;
968             }
969         }
970         break;
971     }
972     }
973
974     // Empty cache group's list of pending master entries.
975     m_pendingMasterResourceLoaders.clear();
976     m_completionType = None;
977     setUpdateStatus(Idle);
978     m_frame = 0;
979     m_availableSpaceInQuota = ApplicationCacheStorage::unknownQuota();
980     m_calledReachedMaxAppCacheSize = false;
981 }
982
983 void ApplicationCacheGroup::startLoadingEntry()
984 {
985     ASSERT(m_cacheBeingUpdated);
986
987     if (m_pendingEntries.isEmpty()) {
988         m_completionType = Completed;
989         deliverDelayedMainResources();
990         return;
991     }
992     
993     EntryMap::const_iterator it = m_pendingEntries.begin();
994
995     postListenerTask(ApplicationCacheHost::PROGRESS_EVENT, m_progressTotal, m_progressDone, m_associatedDocumentLoaders);
996     m_progressDone++;
997
998     ASSERT(!m_currentHandle);
999     
1000     m_currentHandle = createResourceHandle(KURL(ParsedURLString, it->first), m_newestCache ? m_newestCache->resourceForURL(it->first) : 0);
1001 }
1002
1003 void ApplicationCacheGroup::deliverDelayedMainResources()
1004 {
1005     // Need to copy loaders, because the cache group may be destroyed at the end of iteration.
1006     Vector<DocumentLoader*> loaders;
1007     copyToVector(m_pendingMasterResourceLoaders, loaders);
1008     size_t count = loaders.size();
1009     for (size_t i = 0; i != count; ++i) {
1010         DocumentLoader* loader = loaders[i];
1011         if (loader->isLoadingMainResource())
1012             continue;
1013
1014         const ResourceError& error = loader->mainDocumentError();
1015         if (error.isNull())
1016             finishedLoadingMainResource(loader);
1017         else
1018             failedLoadingMainResource(loader);
1019     }
1020     if (!count)
1021         checkIfLoadIsComplete();
1022 }
1023
1024 void ApplicationCacheGroup::addEntry(const String& url, unsigned type)
1025 {
1026     ASSERT(m_cacheBeingUpdated);
1027     ASSERT(!KURL(ParsedURLString, url).hasFragmentIdentifier());
1028     
1029     // Don't add the URL if we already have an master resource in the cache
1030     // (i.e., the main resource finished loading before the manifest).
1031     if (ApplicationCacheResource* resource = m_cacheBeingUpdated->resourceForURL(url)) {
1032         ASSERT(resource->type() & ApplicationCacheResource::Master);
1033         ASSERT(!m_frame->loader()->documentLoader()->isLoadingMainResource());
1034     
1035         resource->addType(type);
1036         return;
1037     }
1038
1039     // Don't add the URL if it's the same as the manifest URL.
1040     ASSERT(m_manifestResource);
1041     if (m_manifestResource->url() == url) {
1042         m_manifestResource->addType(type);
1043         return;
1044     }
1045     
1046     pair<EntryMap::iterator, bool> result = m_pendingEntries.add(url, type);
1047     
1048     if (!result.second)
1049         result.first->second |= type;
1050 }
1051
1052 void ApplicationCacheGroup::associateDocumentLoaderWithCache(DocumentLoader* loader, ApplicationCache* cache)
1053 {
1054     // If teardown started already, revive the group.
1055     if (!m_newestCache && !m_cacheBeingUpdated)
1056         m_newestCache = cache;
1057
1058     ASSERT(!m_isObsolete);
1059
1060     loader->applicationCacheHost()->setApplicationCache(cache);
1061
1062     ASSERT(!m_associatedDocumentLoaders.contains(loader));
1063     m_associatedDocumentLoaders.add(loader);
1064 }
1065
1066 class ChromeClientCallbackTimer: public TimerBase {
1067 public:
1068     ChromeClientCallbackTimer(ApplicationCacheGroup* cacheGroup)
1069         : m_cacheGroup(cacheGroup)
1070     {
1071     }
1072
1073 private:
1074     virtual void fired()
1075     {
1076         m_cacheGroup->didReachMaxAppCacheSize();
1077         delete this;
1078     }
1079     // Note that there is no need to use a RefPtr here. The ApplicationCacheGroup instance is guaranteed
1080     // to be alive when the timer fires since invoking the ChromeClient callback is part of its normal
1081     // update machinery and nothing can yet cause it to get deleted.
1082     ApplicationCacheGroup* m_cacheGroup;
1083 };
1084
1085 void ApplicationCacheGroup::scheduleReachedMaxAppCacheSizeCallback()
1086 {
1087     ASSERT(isMainThread());
1088     ChromeClientCallbackTimer* timer = new ChromeClientCallbackTimer(this);
1089     timer->startOneShot(0);
1090     // The timer will delete itself once it fires.
1091 }
1092
1093 class CallCacheListenerTask : public ScriptExecutionContext::Task {
1094 public:
1095     static PassOwnPtr<CallCacheListenerTask> create(PassRefPtr<DocumentLoader> loader, ApplicationCacheHost::EventID eventID, int progressTotal, int progressDone)
1096     {
1097         return adoptPtr(new CallCacheListenerTask(loader, eventID, progressTotal, progressDone));
1098     }
1099
1100     virtual void performTask(ScriptExecutionContext* context)
1101     {
1102         
1103         ASSERT_UNUSED(context, context->isDocument());
1104         Frame* frame = m_documentLoader->frame();
1105         if (!frame)
1106             return;
1107     
1108         ASSERT(frame->loader()->documentLoader() == m_documentLoader.get());
1109
1110         m_documentLoader->applicationCacheHost()->notifyDOMApplicationCache(m_eventID, m_progressTotal, m_progressDone);
1111     }
1112
1113 private:
1114     CallCacheListenerTask(PassRefPtr<DocumentLoader> loader, ApplicationCacheHost::EventID eventID, int progressTotal, int progressDone)
1115         : m_documentLoader(loader)
1116         , m_eventID(eventID)
1117         , m_progressTotal(progressTotal)
1118         , m_progressDone(progressDone)
1119     {
1120     }
1121
1122     RefPtr<DocumentLoader> m_documentLoader;
1123     ApplicationCacheHost::EventID m_eventID;
1124     int m_progressTotal;
1125     int m_progressDone;
1126 };
1127
1128 void ApplicationCacheGroup::postListenerTask(ApplicationCacheHost::EventID eventID, int progressTotal, int progressDone, const HashSet<DocumentLoader*>& loaderSet)
1129 {
1130     HashSet<DocumentLoader*>::const_iterator loaderSetEnd = loaderSet.end();
1131     for (HashSet<DocumentLoader*>::const_iterator iter = loaderSet.begin(); iter != loaderSetEnd; ++iter)
1132         postListenerTask(eventID, progressTotal, progressDone, *iter);
1133 }
1134
1135 void ApplicationCacheGroup::postListenerTask(ApplicationCacheHost::EventID eventID, int progressTotal, int progressDone, DocumentLoader* loader)
1136 {
1137     Frame* frame = loader->frame();
1138     if (!frame)
1139         return;
1140     
1141     ASSERT(frame->loader()->documentLoader() == loader);
1142
1143     frame->document()->postTask(CallCacheListenerTask::create(loader, eventID, progressTotal, progressDone));
1144 }
1145
1146 void ApplicationCacheGroup::setUpdateStatus(UpdateStatus status)
1147 {
1148     m_updateStatus = status;
1149     InspectorInstrumentation::updateApplicationCacheStatus(m_frame);
1150 }
1151
1152 void ApplicationCacheGroup::clearStorageID()
1153 {
1154     m_storageID = 0;
1155     
1156     HashSet<ApplicationCache*>::const_iterator end = m_caches.end();
1157     for (HashSet<ApplicationCache*>::const_iterator it = m_caches.begin(); it != end; ++it)
1158         (*it)->clearStorageID();
1159 }
1160
1161
1162 }
1163
1164 #endif // ENABLE(OFFLINE_WEB_APPLICATIONS)