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