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