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