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