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