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