2 * Copyright (C) 2008 Apple Inc. All Rights Reserved.
4 * Redistribution and use in source and binary forms, with or without
5 * modification, are permitted provided that the following conditions
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.
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.
27 #include "ApplicationCacheGroup.h"
29 #if ENABLE(OFFLINE_WEB_APPLICATIONS)
31 #include "ApplicationCache.h"
32 #include "ApplicationCacheResource.h"
33 #include "ApplicationCacheStorage.h"
34 #include "DocumentLoader.h"
35 #include "DOMApplicationCache.h"
36 #include "DOMWindow.h"
38 #include "FrameLoader.h"
39 #include "MainResourceLoader.h"
40 #include "ManifestParser.h"
43 #include <wtf/HashMap.h>
47 ApplicationCacheGroup::ApplicationCacheGroup(const KURL& manifestURL)
48 : m_manifestURL(manifestURL)
50 , m_savedNewestCachePointer(0)
56 ApplicationCacheGroup::~ApplicationCacheGroup()
58 ASSERT(!m_newestCache);
59 ASSERT(m_caches.isEmpty());
61 if (m_cacheBeingUpdated)
64 cacheStorage().cacheGroupDestroyed(this);
67 ApplicationCache* ApplicationCacheGroup::cacheForMainRequest(const ResourceRequest& request, DocumentLoader* loader)
69 if (!ApplicationCache::requestIsHTTPOrHTTPSGet(request))
72 ASSERT(loader->frame());
73 ASSERT(loader->frame()->page());
74 if (loader->frame() != loader->frame()->page()->mainFrame())
77 if (ApplicationCacheGroup* group = cacheStorage().cacheGroupForURL(request.url())) {
78 ASSERT(group->newestCache());
80 return group->newestCache();
86 void ApplicationCacheGroup::selectCache(Frame* frame, const KURL& manifestURL)
88 ASSERT(frame && frame->page());
90 if (!frame->settings()->offlineWebApplicationCacheEnabled())
93 DocumentLoader* documentLoader = frame->loader()->documentLoader();
94 ASSERT(!documentLoader->applicationCache());
96 if (manifestURL.isNull()) {
97 selectCacheWithoutManifestURL(frame);
101 ApplicationCache* mainResourceCache = documentLoader->mainResourceApplicationCache();
103 // Check if the main resource is being loaded as part of navigation of the main frame
104 bool isMainFrame = frame->page()->mainFrame() == frame;
107 if (mainResourceCache && manifestURL != mainResourceCache->group()->manifestURL()) {
108 ApplicationCacheResource* resource = mainResourceCache->resourceForURL(documentLoader->originalURL());
111 resource->addType(ApplicationCacheResource::Foreign);
117 if (mainResourceCache) {
118 if (manifestURL == mainResourceCache->group()->m_manifestURL) {
119 mainResourceCache->group()->associateDocumentLoaderWithCache(documentLoader, mainResourceCache);
120 mainResourceCache->group()->update(frame);
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.
130 // The resource was loaded from the network, check if it is a HTTP/HTTPS GET.
131 const ResourceRequest& request = frame->loader()->activeDocumentLoader()->request();
133 if (!ApplicationCache::requestIsHTTPOrHTTPSGet(request)) {
134 selectCacheWithoutManifestURL(frame);
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);
144 ApplicationCacheGroup* group = cacheStorage().findOrCreateCacheGroup(manifestURL);
146 if (ApplicationCache* cache = group->newestCache()) {
147 ASSERT(cache->manifestResource());
149 group->associateDocumentLoaderWithCache(frame->loader()->documentLoader(), cache);
151 if (!frame->loader()->documentLoader()->isLoadingMainResource())
152 group->finishedLoadingMainResource(frame->loader()->documentLoader());
154 group->update(frame);
156 bool isUpdating = group->m_cacheBeingUpdated;
159 group->m_cacheBeingUpdated = ApplicationCache::create();
160 documentLoader->setCandidateApplicationCacheGroup(group);
161 group->m_cacheCandidates.add(documentLoader);
163 const KURL& url = frame->loader()->documentLoader()->originalURL();
167 // If the resource has already been downloaded, remove it so that it will be replaced with the implicit resource
169 type = group->m_cacheBeingUpdated->removeResource(url);
171 // Add the main resource URL as an implicit entry.
172 group->addEntry(url, type | ApplicationCacheResource::Implicit);
174 if (!frame->loader()->documentLoader()->isLoadingMainResource())
175 group->finishedLoadingMainResource(frame->loader()->documentLoader());
178 group->update(frame);
182 void ApplicationCacheGroup::selectCacheWithoutManifestURL(Frame* frame)
184 if (!frame->settings()->offlineWebApplicationCacheEnabled())
187 DocumentLoader* documentLoader = frame->loader()->documentLoader();
188 ASSERT(!documentLoader->applicationCache());
190 ApplicationCache* mainResourceCache = documentLoader->mainResourceApplicationCache();
191 bool isMainFrame = frame->page()->mainFrame() == frame;
193 if (isMainFrame && mainResourceCache) {
194 mainResourceCache->group()->associateDocumentLoaderWithCache(documentLoader, mainResourceCache);
195 mainResourceCache->group()->update(frame);
199 void ApplicationCacheGroup::finishedLoadingMainResource(DocumentLoader* loader)
201 const KURL& url = loader->originalURL();
203 if (ApplicationCache* cache = loader->applicationCache()) {
204 RefPtr<ApplicationCacheResource> resource = ApplicationCacheResource::create(url, loader->response(), ApplicationCacheResource::Implicit, loader->mainResourceData());
205 cache->addResource(resource.release());
207 if (!m_cacheBeingUpdated)
211 ASSERT(m_pendingEntries.contains(url));
213 EntryMap::iterator it = m_pendingEntries.find(url);
214 ASSERT(it->second & ApplicationCacheResource::Implicit);
216 RefPtr<ApplicationCacheResource> resource = ApplicationCacheResource::create(url, loader->response(), it->second, loader->mainResourceData());
218 ASSERT(m_cacheBeingUpdated);
219 m_cacheBeingUpdated->addResource(resource.release());
221 m_pendingEntries.remove(it);
223 checkIfLoadIsComplete();
226 void ApplicationCacheGroup::stopLoading()
228 ASSERT(m_cacheBeingUpdated);
230 if (m_manifestHandle) {
231 ASSERT(!m_currentHandle);
233 m_manifestHandle->setClient(0);
234 m_manifestHandle->cancel();
235 m_manifestHandle = 0;
238 if (m_currentHandle) {
239 ASSERT(!m_manifestHandle);
241 m_currentHandle->setClient(0);
242 m_currentHandle->cancel();
246 m_cacheBeingUpdated = 0;
249 void ApplicationCacheGroup::documentLoaderDestroyed(DocumentLoader* loader)
251 HashSet<DocumentLoader*>::iterator it = m_associatedDocumentLoaders.find(loader);
253 if (it != m_associatedDocumentLoaders.end()) {
254 ASSERT(!m_cacheCandidates.contains(loader));
256 m_associatedDocumentLoaders.remove(it);
258 ASSERT(m_cacheCandidates.contains(loader));
259 m_cacheCandidates.remove(loader);
262 if (!m_associatedDocumentLoaders.isEmpty() || !m_cacheCandidates.isEmpty())
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);
268 // If a cache update is in progress, stop it.
269 if (m_caches.size() == 1) {
270 ASSERT(m_caches.contains(m_newestCache.get()));
272 // Release our reference to the newest cache.
273 m_savedNewestCachePointer = m_newestCache.get();
275 // This could cause us to be deleted.
281 // There is an initial cache attempt in progress
282 ASSERT(m_cacheBeingUpdated);
283 ASSERT(m_caches.size() == 0);
285 // Delete ourselves, causing the cache attempt to be stopped.
289 void ApplicationCacheGroup::cacheDestroyed(ApplicationCache* cache)
291 ASSERT(m_caches.contains(cache));
293 m_caches.remove(cache);
295 if (cache != m_savedNewestCachePointer)
296 cacheStorage().remove(cache);
298 if (m_caches.isEmpty())
302 void ApplicationCacheGroup::setNewestCache(PassRefPtr<ApplicationCache> newestCache)
304 ASSERT(!m_newestCache);
305 ASSERT(!m_caches.contains(newestCache.get()));
306 ASSERT(!newestCache->group());
308 m_newestCache = newestCache;
309 m_caches.add(m_newestCache.get());
310 m_newestCache->setGroup(this);
313 void ApplicationCacheGroup::update(Frame* frame)
315 if (m_status == Checking || m_status == Downloading)
323 callListenersOnAssociatedDocuments(&DOMApplicationCache::callCheckingListener);
325 ASSERT(!m_manifestHandle);
326 ASSERT(!m_manifestResource);
328 // FIXME: Handle defer loading
330 ResourceRequest request(m_manifestURL);
331 m_frame->loader()->applyUserAgent(request);
333 m_manifestHandle = ResourceHandle::create(request, this, m_frame, false, true, false);
336 void ApplicationCacheGroup::didReceiveResponse(ResourceHandle* handle, const ResourceResponse& response)
338 if (handle == m_manifestHandle) {
339 didReceiveManifestResponse(response);
343 ASSERT(handle == m_currentHandle);
345 int statusCode = response.httpStatusCode() / 100;
346 if (statusCode == 4 || statusCode == 5) {
352 const KURL& url = handle->request().url();
354 ASSERT(!m_currentResource);
355 ASSERT(m_pendingEntries.contains(url));
357 unsigned type = m_pendingEntries.get(url);
359 // If this is an initial cache attempt, we should not get implicit resources delivered here.
361 ASSERT(!(type & ApplicationCacheResource::Implicit));
363 m_currentResource = ApplicationCacheResource::create(url, response, type);
366 void ApplicationCacheGroup::didReceiveData(ResourceHandle* handle, const char* data, int length, int lengthReceived)
368 if (handle == m_manifestHandle) {
369 didReceiveManifestData(data, length);
373 ASSERT(handle == m_currentHandle);
375 ASSERT(m_currentResource);
376 m_currentResource->data()->append(data, length);
379 void ApplicationCacheGroup::didFinishLoading(ResourceHandle* handle)
381 if (handle == m_manifestHandle) {
382 didFinishLoadingManifest();
386 ASSERT(m_currentHandle == handle);
387 ASSERT(m_pendingEntries.contains(handle->request().url()));
389 m_pendingEntries.remove(handle->request().url());
391 ASSERT(m_cacheBeingUpdated);
393 m_cacheBeingUpdated->addResource(m_currentResource.release());
396 // Load the next file.
397 if (!m_pendingEntries.isEmpty()) {
402 checkIfLoadIsComplete();
405 void ApplicationCacheGroup::didFail(ResourceHandle* handle, const ResourceError&)
407 if (handle == m_manifestHandle) {
408 didFailToLoadManifest();
416 void ApplicationCacheGroup::didReceiveManifestResponse(const ResourceResponse& response)
418 int statusCode = response.httpStatusCode() / 100;
420 if (statusCode == 4 || statusCode == 5 ||
421 !equalIgnoringCase(response.mimeType(), "text/cache-manifest")) {
422 didFailToLoadManifest();
426 ASSERT(!m_manifestResource);
427 ASSERT(m_manifestHandle);
428 m_manifestResource = ApplicationCacheResource::create(m_manifestHandle->request().url(), response,
429 ApplicationCacheResource::Manifest);
432 void ApplicationCacheGroup::didReceiveManifestData(const char* data, int length)
434 ASSERT(m_manifestResource);
435 m_manifestResource->data()->append(data, length);
438 void ApplicationCacheGroup::didFinishLoadingManifest()
440 if (!m_manifestResource) {
441 didFailToLoadManifest();
445 bool isUpgradeAttempt = m_newestCache;
447 m_manifestHandle = 0;
449 // Check if the manifest is byte-for-byte identical.
450 if (isUpgradeAttempt) {
451 ApplicationCacheResource* newestManifest = m_newestCache->manifestResource();
452 ASSERT(newestManifest);
454 if (newestManifest->data()->size() == m_manifestResource->data()->size() &&
455 !memcmp(newestManifest->data()->data(), m_manifestResource->data()->data(), newestManifest->data()->size())) {
457 callListenersOnAssociatedDocuments(&DOMApplicationCache::callNoUpdateListener);
461 m_manifestResource = 0;
467 if (!parseManifest(m_manifestURL, m_manifestResource->data()->data(), m_manifestResource->data()->size(), manifest)) {
468 didFailToLoadManifest();
472 // FIXME: Add the opportunistic caching namespaces and their fallbacks.
474 // We have the manifest, now download the resources.
475 m_status = Downloading;
477 callListenersOnAssociatedDocuments(&DOMApplicationCache::callDownloadingListener);
480 // We should only have implicit entries.
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);
488 if (isUpgradeAttempt) {
489 ASSERT(!m_cacheBeingUpdated);
491 m_cacheBeingUpdated = ApplicationCache::create();
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);
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);
505 m_cacheBeingUpdated->setOnlineWhitelist(manifest.onlineWhitelistedURLs);
510 void ApplicationCacheGroup::cacheUpdateFailed()
512 callListenersOnAssociatedDocuments(&DOMApplicationCache::callErrorListener);
514 m_pendingEntries.clear();
515 m_cacheBeingUpdated = 0;
516 m_manifestResource = 0;
518 while (!m_cacheCandidates.isEmpty()) {
519 HashSet<DocumentLoader*>::iterator it = m_cacheCandidates.begin();
521 ASSERT((*it)->candidateApplicationCacheGroup() == this);
522 (*it)->setCandidateApplicationCacheGroup(0);
523 m_cacheCandidates.remove(it);
531 void ApplicationCacheGroup::didFailToLoadManifest()
534 m_manifestHandle = 0;
537 void ApplicationCacheGroup::checkIfLoadIsComplete()
539 ASSERT(m_cacheBeingUpdated);
541 if (m_manifestHandle)
544 if (!m_pendingEntries.isEmpty())
548 bool isUpgradeAttempt = m_newestCache;
550 m_cacheBeingUpdated->setManifestResource(m_manifestResource.release());
555 Vector<RefPtr<DocumentLoader> > documentLoaders;
557 if (isUpgradeAttempt) {
558 ASSERT(m_cacheCandidates.isEmpty());
560 copyToVector(m_associatedDocumentLoaders, documentLoaders);
562 while (!m_cacheCandidates.isEmpty()) {
563 HashSet<DocumentLoader*>::iterator it = m_cacheCandidates.begin();
565 DocumentLoader* loader = *it;
566 ASSERT(!loader->applicationCache());
567 ASSERT(loader->candidateApplicationCacheGroup() == this);
569 associateDocumentLoaderWithCache(loader, m_cacheBeingUpdated.get());
571 documentLoaders.append(loader);
573 m_cacheCandidates.remove(it);
577 setNewestCache(m_cacheBeingUpdated.release());
580 cacheStorage().storeNewestCache(this);
582 callListeners(isUpgradeAttempt ? &DOMApplicationCache::callUpdateReadyListener : &DOMApplicationCache::callCachedListener,
586 void ApplicationCacheGroup::startLoadingEntry()
588 ASSERT(m_cacheBeingUpdated);
590 if (m_pendingEntries.isEmpty()) {
591 checkIfLoadIsComplete();
595 EntryMap::const_iterator it = m_pendingEntries.begin();
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();
603 while (it->second & ApplicationCacheResource::Implicit) {
611 // FIXME: Fire progress event.
612 // FIXME: If this is an upgrade attempt, the newest cache should be used as an HTTP cache.
614 ASSERT(!m_currentHandle);
616 ResourceRequest request(it->first);
617 m_frame->loader()->applyUserAgent(request);
619 m_currentHandle = ResourceHandle::create(request, this, m_frame, false, true, false);
622 void ApplicationCacheGroup::addEntry(const String& url, unsigned type)
624 ASSERT(m_cacheBeingUpdated);
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);
630 resource->addType(type);
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);
640 pair<EntryMap::iterator, bool> result = m_pendingEntries.add(url, type);
643 result.first->second |= type;
646 void ApplicationCacheGroup::associateDocumentLoaderWithCache(DocumentLoader* loader, ApplicationCache* cache)
648 loader->setApplicationCache(cache);
650 ASSERT(!m_associatedDocumentLoaders.contains(loader));
651 m_associatedDocumentLoaders.add(loader);
654 void ApplicationCacheGroup::callListenersOnAssociatedDocuments(ListenerFunction listenerFunction)
656 Vector<RefPtr<DocumentLoader> > loaders;
657 copyToVector(m_associatedDocumentLoaders, loaders);
659 callListeners(listenerFunction, loaders);
662 void ApplicationCacheGroup::callListeners(ListenerFunction listenerFunction, const Vector<RefPtr<DocumentLoader> >& loaders)
664 for (unsigned i = 0; i < loaders.size(); i++) {
665 Frame* frame = loaders[i]->frame();
669 ASSERT(frame->loader()->documentLoader() == loaders[i]);
670 DOMWindow* window = frame->domWindow();
672 if (DOMApplicationCache* domCache = window->optionalApplicationCache())
673 (domCache->*listenerFunction)();
677 void ApplicationCacheGroup::clearStorageID()
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();
689 #endif // ENABLE(OFFLINE_WEB_APPLICATIONS)