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