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