14bb969fa78a7b85960add0a3d8cc02281386cf7
[WebKit-https.git] / WebCore / loader / appcache / ApplicationCacheGroup.cpp
1 /*
2  * Copyright (C) 2008 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 "ApplicationCacheResource.h"
33 #include "ApplicationCacheStorage.h"
34 #include "DocumentLoader.h"
35 #include "DOMApplicationCache.h"
36 #include "DOMWindow.h"
37 #include "Frame.h"
38 #include "FrameLoader.h"
39 #include "MainResourceLoader.h"
40 #include "ManifestParser.h"
41 #include "Page.h"
42 #include "Settings.h"
43 #include <wtf/HashMap.h>
44
45 namespace WebCore {
46
47 ApplicationCacheGroup::ApplicationCacheGroup(const KURL& manifestURL)
48     : m_manifestURL(manifestURL)
49     , m_status(Idle)
50     , m_savedNewestCachePointer(0)
51     , m_frame(0)
52     , m_storageID(0)
53 {
54 }
55
56 ApplicationCacheGroup::~ApplicationCacheGroup()
57 {
58     ASSERT(!m_newestCache);
59     ASSERT(m_caches.isEmpty());
60     
61     cacheStorage().cacheGroupDestroyed(this);
62 }
63     
64 ApplicationCache* ApplicationCacheGroup::cacheForMainRequest(const ResourceRequest& request, DocumentLoader* loader)
65 {
66     if (!ApplicationCache::requestIsHTTPOrHTTPSGet(request))
67         return 0;
68  
69     ASSERT(loader->frame());
70     ASSERT(loader->frame()->page());
71     if (loader->frame() != loader->frame()->page()->mainFrame())
72         return 0;
73
74     if (ApplicationCacheGroup* group = cacheStorage().cacheGroupForURL(request.url())) {
75         ASSERT(group->newestCache());
76         
77         return group->newestCache();
78     }
79     
80     return 0;
81 }
82     
83 void ApplicationCacheGroup::selectCache(Frame* frame, const KURL& manifestURL)
84 {
85     ASSERT(frame && frame->page());
86     
87     if (!frame->settings()->offlineWebApplicationCacheEnabled())
88         return;
89     
90     DocumentLoader* documentLoader = frame->loader()->documentLoader();
91     ASSERT(!documentLoader->applicationCache());
92
93     if (manifestURL.isNull()) {
94         selectCacheWithoutManifestURL(frame);        
95         return;
96     }
97     
98     ApplicationCache* mainResourceCache = documentLoader->mainResourceApplicationCache();
99     
100     // Check if the main resource is being loaded as part of navigation of the main frame
101     bool isMainFrame = frame->page()->mainFrame() == frame;
102     
103     if (!isMainFrame) {
104         if (mainResourceCache && manifestURL != mainResourceCache->group()->manifestURL()) {
105             ApplicationCacheResource* resource = mainResourceCache->resourceForURL(documentLoader->originalURL());
106             ASSERT(resource);
107             
108             resource->addType(ApplicationCacheResource::Foreign);
109         }
110
111         return;
112     }
113     
114     if (mainResourceCache) {
115         if (manifestURL == mainResourceCache->group()->m_manifestURL) {
116             mainResourceCache->group()->associateDocumentLoaderWithCache(documentLoader, mainResourceCache);
117             mainResourceCache->group()->update(frame);
118         } else {
119             // FIXME: If the resource being loaded was loaded from an application cache and the URI of 
120             // that application cache's manifest is not the same as the manifest URI with which the algorithm was invoked
121             // then we should "undo" the navigation.
122         }
123         
124         return;
125     }
126     
127     // The resource was loaded from the network, check if it is a HTTP/HTTPS GET.    
128     const ResourceRequest& request = frame->loader()->activeDocumentLoader()->request();
129
130     if (!ApplicationCache::requestIsHTTPOrHTTPSGet(request)) {
131         selectCacheWithoutManifestURL(frame);
132         return;
133     }
134
135     // Check that the resource URL has the same scheme/host/port as the manifest URL.
136     if (!protocolHostAndPortAreEqual(manifestURL, request.url())) {
137         selectCacheWithoutManifestURL(frame);
138         return;
139     }
140             
141     ApplicationCacheGroup* group = cacheStorage().findOrCreateCacheGroup(manifestURL);
142             
143     if (ApplicationCache* cache = group->newestCache()) {
144         ASSERT(cache->manifestResource());
145                 
146         group->associateDocumentLoaderWithCache(frame->loader()->documentLoader(), cache);
147               
148         if (!frame->loader()->documentLoader()->isLoadingMainResource())
149             group->finishedLoadingMainResource(frame->loader()->documentLoader());
150
151         group->update(frame);
152     } else {
153         bool isUpdating = group->m_cacheBeingUpdated;
154                 
155         if (!isUpdating)
156             group->m_cacheBeingUpdated = ApplicationCache::create();
157         documentLoader->setCandidateApplicationCacheGroup(group);
158         group->m_cacheCandidates.add(documentLoader);
159
160         const KURL& url = frame->loader()->documentLoader()->originalURL();
161                 
162         unsigned type = 0;
163
164         // If the resource has already been downloaded, remove it so that it will be replaced with the implicit resource
165         if (isUpdating)
166             type = group->m_cacheBeingUpdated->removeResource(url);
167                
168         // Add the main resource URL as an implicit entry.
169         group->addEntry(url, type | ApplicationCacheResource::Implicit);
170
171         if (!frame->loader()->documentLoader()->isLoadingMainResource())
172             group->finishedLoadingMainResource(frame->loader()->documentLoader());
173                 
174         if (!isUpdating)
175             group->update(frame);                
176     }               
177 }
178
179 void ApplicationCacheGroup::selectCacheWithoutManifestURL(Frame* frame)
180 {
181     if (!frame->settings()->offlineWebApplicationCacheEnabled())
182         return;
183
184     DocumentLoader* documentLoader = frame->loader()->documentLoader();
185     ASSERT(!documentLoader->applicationCache());
186
187     ApplicationCache* mainResourceCache = documentLoader->mainResourceApplicationCache();
188     bool isMainFrame = frame->page()->mainFrame() == frame;
189
190     if (isMainFrame && mainResourceCache) {
191         mainResourceCache->group()->associateDocumentLoaderWithCache(documentLoader, mainResourceCache);
192         mainResourceCache->group()->update(frame);
193     }
194 }
195
196 void ApplicationCacheGroup::finishedLoadingMainResource(DocumentLoader* loader)
197 {
198     const KURL& url = loader->originalURL();
199     
200     if (ApplicationCache* cache = loader->applicationCache()) {
201         RefPtr<ApplicationCacheResource> resource = ApplicationCacheResource::create(url, loader->response(), ApplicationCacheResource::Implicit, loader->mainResourceData());
202         cache->addResource(resource.release());
203         
204         if (!m_cacheBeingUpdated)
205             return;
206     }
207     
208     ASSERT(m_pendingEntries.contains(url));
209  
210     EntryMap::iterator it = m_pendingEntries.find(url);
211     ASSERT(it->second & ApplicationCacheResource::Implicit);
212
213     RefPtr<ApplicationCacheResource> resource = ApplicationCacheResource::create(url, loader->response(), it->second, loader->mainResourceData());
214
215     ASSERT(m_cacheBeingUpdated);
216     m_cacheBeingUpdated->addResource(resource.release());
217     
218     m_pendingEntries.remove(it);
219     
220     checkIfLoadIsComplete();
221 }
222
223 void ApplicationCacheGroup::documentLoaderDestroyed(DocumentLoader* loader)
224 {
225     HashSet<DocumentLoader*>::iterator it = m_associatedDocumentLoaders.find(loader);
226     
227     if (it != m_associatedDocumentLoaders.end()) {
228         ASSERT(!m_cacheCandidates.contains(loader));
229     
230         m_associatedDocumentLoaders.remove(it);
231     } else {
232         ASSERT(m_cacheCandidates.contains(loader));
233         m_cacheCandidates.remove(loader);
234     }
235     
236     if (m_associatedDocumentLoaders.isEmpty() && m_cacheCandidates.isEmpty()) {
237         // We should only have the newest cache remaining.
238         ASSERT(m_caches.size() == 1);
239         ASSERT(m_caches.contains(m_newestCache.get()));
240         
241         // Release our reference to the newest cache.
242         m_savedNewestCachePointer = m_newestCache.get();
243         
244         // This could cause us to be deleted.
245         m_newestCache = 0;
246     }    
247 }    
248
249 void ApplicationCacheGroup::cacheDestroyed(ApplicationCache* cache)
250 {
251     ASSERT(m_caches.contains(cache));
252     
253     m_caches.remove(cache);
254     
255     if (cache != m_savedNewestCachePointer)
256         cacheStorage().remove(cache);
257
258     if (m_caches.isEmpty())
259         delete this;
260 }
261
262 void ApplicationCacheGroup::setNewestCache(PassRefPtr<ApplicationCache> newestCache)
263
264     ASSERT(!m_newestCache);
265     ASSERT(!m_caches.contains(newestCache.get()));
266     ASSERT(!newestCache->group());
267            
268     m_newestCache = newestCache; 
269     m_caches.add(m_newestCache.get());
270     m_newestCache->setGroup(this);
271 }
272
273 void ApplicationCacheGroup::update(Frame* frame)
274 {
275     if (m_status == Checking || m_status == Downloading) 
276         return;
277
278     ASSERT(!m_frame);
279     m_frame = frame;
280
281     m_status = Checking;
282
283     callListenersOnAssociatedDocuments(&DOMApplicationCache::callCheckingListener);
284     
285     ASSERT(!m_manifestHandle);
286     ASSERT(!m_manifestResource);
287     
288     // FIXME: Handle defer loading
289     
290     ResourceRequest request(m_manifestURL);
291     m_frame->loader()->applyUserAgent(request);
292     
293     m_manifestHandle = ResourceHandle::create(request, this, m_frame, false, true, false);
294 }
295  
296 void ApplicationCacheGroup::didReceiveResponse(ResourceHandle* handle, const ResourceResponse& response)
297 {
298     if (handle == m_manifestHandle) {
299         didReceiveManifestResponse(response);
300         return;
301     }
302     
303     ASSERT(handle == m_currentHandle);
304     
305     int statusCode = response.httpStatusCode() / 100;
306     if (statusCode == 4 || statusCode == 5) {
307         cacheUpdateFailed();
308         m_currentHandle = 0;
309         return;
310     }
311     
312     const KURL& url = handle->request().url();
313     
314     ASSERT(!m_currentResource);
315     ASSERT(m_pendingEntries.contains(url));
316     
317     unsigned type = m_pendingEntries.get(url);
318     
319     // If this is an initial cache attempt, we should not get implicit resources delivered here.
320     if (!m_newestCache)
321         ASSERT(!(type & ApplicationCacheResource::Implicit));
322     
323     m_currentResource = ApplicationCacheResource::create(url, response, type);
324 }
325
326 void ApplicationCacheGroup::didReceiveData(ResourceHandle* handle, const char* data, int length, int lengthReceived)
327 {
328     if (handle == m_manifestHandle) {
329         didReceiveManifestData(data, length);
330         return;
331     }
332     
333     ASSERT(handle == m_currentHandle);
334     
335     ASSERT(m_currentResource);
336     m_currentResource->data()->append(data, length);
337 }
338
339 void ApplicationCacheGroup::didFinishLoading(ResourceHandle* handle)
340 {
341     if (handle == m_manifestHandle) {
342         didFinishLoadingManifest();
343         return;
344     }
345  
346     ASSERT(m_currentHandle == handle);
347     ASSERT(m_pendingEntries.contains(handle->request().url()));
348     
349     m_pendingEntries.remove(handle->request().url());
350     
351     ASSERT(m_cacheBeingUpdated);
352
353     m_cacheBeingUpdated->addResource(m_currentResource.release());
354     m_currentHandle = 0;
355     
356     // Load the next file.
357     if (!m_pendingEntries.isEmpty()) {
358         startLoadingEntry();
359         return;
360     }
361     
362     checkIfLoadIsComplete();
363 }
364
365 void ApplicationCacheGroup::didFail(ResourceHandle* handle, const ResourceError&)
366 {
367     if (handle == m_manifestHandle) {
368         didFailToLoadManifest();
369         return;
370     }
371     
372     cacheUpdateFailed();
373     m_currentHandle = 0;
374 }
375
376 void ApplicationCacheGroup::didReceiveManifestResponse(const ResourceResponse& response)
377 {
378     int statusCode = response.httpStatusCode() / 100;
379
380     if (statusCode == 4 || statusCode == 5 || 
381         !equalIgnoringCase(response.mimeType(), "text/cache-manifest")) {
382         didFailToLoadManifest();
383         return;
384     }
385     
386     ASSERT(!m_manifestResource);
387     ASSERT(m_manifestHandle);
388     m_manifestResource = ApplicationCacheResource::create(m_manifestHandle->request().url(), response, 
389                                                           ApplicationCacheResource::Manifest);
390 }
391
392 void ApplicationCacheGroup::didReceiveManifestData(const char* data, int length)
393 {
394     ASSERT(m_manifestResource);
395     m_manifestResource->data()->append(data, length);
396 }
397
398 void ApplicationCacheGroup::didFinishLoadingManifest()
399 {
400     if (!m_manifestResource) {
401         didFailToLoadManifest();
402         return;
403     }
404
405     bool isUpgradeAttempt = m_newestCache;
406     
407     m_manifestHandle = 0;
408
409     // Check if the manifest is byte-for-byte identical.
410     if (isUpgradeAttempt) {
411         ApplicationCacheResource* newestManifest = m_newestCache->manifestResource();
412         ASSERT(newestManifest);
413     
414         if (newestManifest->data()->size() == m_manifestResource->data()->size() &&
415             !memcmp(newestManifest->data()->data(), m_manifestResource->data()->data(), newestManifest->data()->size())) {
416             
417             callListenersOnAssociatedDocuments(&DOMApplicationCache::callNoUpdateListener);
418          
419             m_status = Idle;
420             m_frame = 0;
421             m_manifestResource = 0;
422             return;
423         }
424     }
425     
426     Manifest manifest;
427     if (!parseManifest(m_manifestURL, m_manifestResource->data()->data(), m_manifestResource->data()->size(), manifest)) {
428         didFailToLoadManifest();
429         return;
430     }
431         
432     // FIXME: Add the opportunistic caching namespaces and their fallbacks.
433     
434     // We have the manifest, now download the resources.
435     m_status = Downloading;
436     
437     callListenersOnAssociatedDocuments(&DOMApplicationCache::callDownloadingListener);
438
439 #ifndef NDEBUG
440     // We should only have implicit entries.
441     {
442         EntryMap::const_iterator end = m_pendingEntries.end();
443         for (EntryMap::const_iterator it = m_pendingEntries.begin(); it != end; ++it)
444             ASSERT(it->second & ApplicationCacheResource::Implicit);
445     }
446 #endif
447     
448     if (isUpgradeAttempt) {
449         ASSERT(!m_cacheBeingUpdated);
450         
451         m_cacheBeingUpdated = ApplicationCache::create();
452         
453         ApplicationCache::ResourceMap::const_iterator end = m_newestCache->end();
454         for (ApplicationCache::ResourceMap::const_iterator it = m_newestCache->begin(); it != end; ++it) {
455             unsigned type = it->second->type();
456             if (type & (ApplicationCacheResource::Opportunistic | ApplicationCacheResource::Implicit | ApplicationCacheResource::Dynamic))
457                 addEntry(it->first, type);
458         }
459     }
460     
461     HashSet<String>::const_iterator end = manifest.explicitURLs.end();
462     for (HashSet<String>::const_iterator it = manifest.explicitURLs.begin(); it != end; ++it)
463         addEntry(*it, ApplicationCacheResource::Explicit);
464     
465     m_cacheBeingUpdated->setOnlineWhitelist(manifest.onlineWhitelistedURLs);
466     
467     startLoadingEntry();
468 }
469
470 void ApplicationCacheGroup::cacheUpdateFailed()
471 {
472     callListenersOnAssociatedDocuments(&DOMApplicationCache::callErrorListener);
473
474     m_pendingEntries.clear();
475     m_cacheBeingUpdated = 0;
476     m_manifestResource = 0;
477
478     while (!m_cacheCandidates.isEmpty()) {
479         HashSet<DocumentLoader*>::iterator it = m_cacheCandidates.begin();
480         
481         ASSERT((*it)->candidateApplicationCacheGroup() == this);
482         (*it)->setCandidateApplicationCacheGroup(0);
483         m_cacheCandidates.remove(it);
484     }
485     
486     m_status = Idle;    
487     m_frame = 0;
488 }
489     
490     
491 void ApplicationCacheGroup::didFailToLoadManifest()
492 {
493     cacheUpdateFailed();
494     m_manifestHandle = 0;
495 }
496
497 void ApplicationCacheGroup::checkIfLoadIsComplete()
498 {
499     ASSERT(m_cacheBeingUpdated);
500     
501     if (m_manifestHandle)
502         return;
503     
504     if (!m_pendingEntries.isEmpty())
505         return;
506     
507     // We're done    
508     bool isUpgradeAttempt = m_newestCache;
509     
510     m_cacheBeingUpdated->setManifestResource(m_manifestResource.release());
511     
512     m_status = Idle;
513     m_frame = 0;
514     
515     Vector<RefPtr<DocumentLoader> > documentLoaders;
516     
517     if (isUpgradeAttempt) {
518         ASSERT(m_cacheCandidates.isEmpty());
519         
520         copyToVector(m_associatedDocumentLoaders, documentLoaders);
521     } else {
522         while (!m_cacheCandidates.isEmpty()) {
523             HashSet<DocumentLoader*>::iterator it = m_cacheCandidates.begin();
524             
525             DocumentLoader* loader = *it;
526             ASSERT(!loader->applicationCache());
527             ASSERT(loader->candidateApplicationCacheGroup() == this);
528             
529             associateDocumentLoaderWithCache(loader, m_cacheBeingUpdated.get());
530     
531             documentLoaders.append(loader);
532
533             m_cacheCandidates.remove(it);
534         }
535     }
536     
537     setNewestCache(m_cacheBeingUpdated.release());
538         
539     // Store the cache 
540     cacheStorage().storeNewestCache(this);
541     
542     callListeners(isUpgradeAttempt ? &DOMApplicationCache::callUpdateReadyListener : &DOMApplicationCache::callCachedListener, 
543                   documentLoaders);
544 }
545
546 void ApplicationCacheGroup::startLoadingEntry()
547 {
548     ASSERT(m_cacheBeingUpdated);
549
550     if (m_pendingEntries.isEmpty()) {
551         checkIfLoadIsComplete();
552         return;
553     }
554     
555     EntryMap::const_iterator it = m_pendingEntries.begin();
556
557     // If this is an initial cache attempt, we do not want to fetch any implicit entries,
558     // since those are fed to us by the normal loader machinery.
559     if (!m_newestCache) {
560         // Get the first URL in the entry table that is not implicit
561         EntryMap::const_iterator end = m_pendingEntries.end();
562     
563         while (it->second & ApplicationCacheResource::Implicit) {
564             ++it;
565
566             if (it == end)
567                 return;
568         }
569     }
570     
571     // FIXME: Fire progress event.
572     // FIXME: If this is an upgrade attempt, the newest cache should be used as an HTTP cache.
573     
574     ASSERT(!m_currentHandle);
575     
576     ResourceRequest request(it->first);
577     m_frame->loader()->applyUserAgent(request);
578
579     m_currentHandle = ResourceHandle::create(request, this, m_frame, false, true, false);
580 }
581
582 void ApplicationCacheGroup::addEntry(const String& url, unsigned type)
583 {
584     ASSERT(m_cacheBeingUpdated);
585     
586     // Don't add the URL if we already have an implicit resource in the cache
587     if (ApplicationCacheResource* resource = m_cacheBeingUpdated->resourceForURL(url)) {
588         ASSERT(resource->type() & ApplicationCacheResource::Implicit);
589     
590         resource->addType(type);
591         return;
592     }
593
594     // Don't add the URL if it's the same as the manifest URL.
595     if (m_manifestResource && m_manifestResource->url() == url) {
596         m_manifestResource->addType(type);
597         return;
598     }
599     
600     pair<EntryMap::iterator, bool> result = m_pendingEntries.add(url, type);
601     
602     if (!result.second)
603         result.first->second |= type;
604 }
605
606 void ApplicationCacheGroup::associateDocumentLoaderWithCache(DocumentLoader* loader, ApplicationCache* cache)
607 {
608     loader->setApplicationCache(cache);
609     
610     ASSERT(!m_associatedDocumentLoaders.contains(loader));
611     m_associatedDocumentLoaders.add(loader);
612 }
613  
614 void ApplicationCacheGroup::callListenersOnAssociatedDocuments(ListenerFunction listenerFunction)
615 {
616     Vector<RefPtr<DocumentLoader> > loaders;
617     copyToVector(m_associatedDocumentLoaders, loaders);
618
619     callListeners(listenerFunction, loaders);
620 }
621     
622 void ApplicationCacheGroup::callListeners(ListenerFunction listenerFunction, const Vector<RefPtr<DocumentLoader> >& loaders)
623 {
624     for (unsigned i = 0; i < loaders.size(); i++) {
625         Frame* frame = loaders[i]->frame();
626         if (!frame)
627             continue;
628         
629         ASSERT(frame->loader()->documentLoader() == loaders[i]);
630         DOMWindow* window = frame->domWindow();
631         
632         if (DOMApplicationCache* domCache = window->optionalApplicationCache())
633             (domCache->*listenerFunction)();
634     }    
635 }
636
637 void ApplicationCacheGroup::clearStorageID()
638 {
639     m_storageID = 0;
640     
641     HashSet<ApplicationCache*>::const_iterator end = m_caches.end();
642     for (HashSet<ApplicationCache*>::const_iterator it = m_caches.begin(); it != end; ++it)
643         (*it)->clearStorageID();
644 }
645     
646
647 }
648
649 #endif // ENABLE(OFFLINE_WEB_APPLICATIONS)