2 * Copyright (C) 2008, 2009, 2010 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 "ApplicationCacheHost.h"
33 #include "ApplicationCacheResource.h"
34 #include "ApplicationCacheStorage.h"
36 #include "ChromeClient.h"
38 #include "DOMApplicationCache.h"
39 #include "DOMWindow.h"
40 #include "DocumentLoader.h"
42 #include "FrameLoader.h"
43 #include "FrameLoaderClient.h"
44 #include "InspectorInstrumentation.h"
45 #include "MainResourceLoader.h"
46 #include "ManifestParser.h"
48 #include "ScriptProfile.h"
49 #include "SecurityOrigin.h"
51 #include <wtf/HashMap.h>
52 #include <wtf/MainThread.h>
53 #include <wtf/UnusedParam.h>
56 #include "ProgressTracker.h"
61 ApplicationCacheGroup::ApplicationCacheGroup(const KURL& manifestURL, bool isCopy)
62 : m_manifestURL(manifestURL)
63 , m_origin(SecurityOrigin::create(manifestURL))
64 , m_updateStatus(Idle)
65 , m_downloadingPendingMasterResourceLoadersCount(0)
71 , m_completionType(None)
73 , m_calledReachedMaxAppCacheSize(false)
74 , m_availableSpaceInQuota(ApplicationCacheStorage::unknownQuota())
75 , m_originQuotaExceededPreviously(false)
79 ApplicationCacheGroup::~ApplicationCacheGroup()
82 ASSERT(m_newestCache);
83 ASSERT(m_caches.size() == 1);
84 ASSERT(m_caches.contains(m_newestCache.get()));
85 ASSERT(!m_cacheBeingUpdated);
86 ASSERT(m_associatedDocumentLoaders.isEmpty());
87 ASSERT(m_pendingMasterResourceLoaders.isEmpty());
88 ASSERT(m_newestCache->group() == this);
93 ASSERT(!m_newestCache);
94 ASSERT(m_caches.isEmpty());
98 cacheStorage().cacheGroupDestroyed(this);
101 ApplicationCache* ApplicationCacheGroup::cacheForMainRequest(const ResourceRequest& request, DocumentLoader*)
103 if (!ApplicationCache::requestIsHTTPOrHTTPSGet(request))
106 KURL url(request.url());
107 if (url.hasFragmentIdentifier())
108 url.removeFragmentIdentifier();
110 if (ApplicationCacheGroup* group = cacheStorage().cacheGroupForURL(url)) {
111 ASSERT(group->newestCache());
112 ASSERT(!group->isObsolete());
114 return group->newestCache();
120 ApplicationCache* ApplicationCacheGroup::fallbackCacheForMainRequest(const ResourceRequest& request, DocumentLoader*)
122 if (!ApplicationCache::requestIsHTTPOrHTTPSGet(request))
125 KURL url(request.url());
126 if (url.hasFragmentIdentifier())
127 url.removeFragmentIdentifier();
129 if (ApplicationCacheGroup* group = cacheStorage().fallbackCacheGroupForURL(url)) {
130 ASSERT(group->newestCache());
131 ASSERT(!group->isObsolete());
133 return group->newestCache();
139 void ApplicationCacheGroup::selectCache(Frame* frame, const KURL& passedManifestURL)
141 ASSERT(frame && frame->page());
143 if (!frame->settings()->offlineWebApplicationCacheEnabled())
146 DocumentLoader* documentLoader = frame->loader()->documentLoader();
147 ASSERT(!documentLoader->applicationCacheHost()->applicationCache());
149 if (passedManifestURL.isNull()) {
150 selectCacheWithoutManifestURL(frame);
154 KURL manifestURL(passedManifestURL);
155 if (manifestURL.hasFragmentIdentifier())
156 manifestURL.removeFragmentIdentifier();
158 ApplicationCache* mainResourceCache = documentLoader->applicationCacheHost()->mainResourceApplicationCache();
160 if (mainResourceCache) {
161 if (manifestURL == mainResourceCache->group()->m_manifestURL) {
162 // The cache may have gotten obsoleted after we've loaded from it, but before we parsed the document and saw cache manifest.
163 if (mainResourceCache->group()->isObsolete())
165 mainResourceCache->group()->associateDocumentLoaderWithCache(documentLoader, mainResourceCache);
166 mainResourceCache->group()->update(frame, ApplicationCacheUpdateWithBrowsingContext);
168 // The main resource was loaded from cache, so the cache must have an entry for it. Mark it as foreign.
169 KURL resourceURL(documentLoader->responseURL());
170 if (resourceURL.hasFragmentIdentifier())
171 resourceURL.removeFragmentIdentifier();
172 ApplicationCacheResource* resource = mainResourceCache->resourceForURL(resourceURL);
173 bool inStorage = resource->storageID();
174 resource->addType(ApplicationCacheResource::Foreign);
176 cacheStorage().storeUpdatedType(resource, mainResourceCache);
178 // Restart the current navigation from the top of the navigation algorithm, undoing any changes that were made
179 // as part of the initial load.
180 // The navigation will not result in the same resource being loaded, because "foreign" entries are never picked during navigation.
181 frame->navigationScheduler()->scheduleLocationChange(frame->document()->securityOrigin(), documentLoader->url(), frame->loader()->referrer());
187 // The resource was loaded from the network, check if it is a HTTP/HTTPS GET.
188 const ResourceRequest& request = frame->loader()->activeDocumentLoader()->request();
190 if (!ApplicationCache::requestIsHTTPOrHTTPSGet(request))
193 // Check that the resource URL has the same scheme/host/port as the manifest URL.
194 if (!protocolHostAndPortAreEqual(manifestURL, request.url()))
197 // Don't change anything on disk if private browsing is enabled.
198 if (!frame->settings() || frame->settings()->privateBrowsingEnabled()) {
199 postListenerTask(ApplicationCacheHost::CHECKING_EVENT, documentLoader);
200 postListenerTask(ApplicationCacheHost::ERROR_EVENT, documentLoader);
204 ApplicationCacheGroup* group = cacheStorage().findOrCreateCacheGroup(manifestURL);
206 documentLoader->applicationCacheHost()->setCandidateApplicationCacheGroup(group);
207 group->m_pendingMasterResourceLoaders.add(documentLoader);
208 group->m_downloadingPendingMasterResourceLoadersCount++;
210 ASSERT(!group->m_cacheBeingUpdated || group->m_updateStatus != Idle);
211 group->update(frame, ApplicationCacheUpdateWithBrowsingContext);
214 void ApplicationCacheGroup::selectCacheWithoutManifestURL(Frame* frame)
216 if (!frame->settings()->offlineWebApplicationCacheEnabled())
219 DocumentLoader* documentLoader = frame->loader()->documentLoader();
220 ASSERT(!documentLoader->applicationCacheHost()->applicationCache());
222 ApplicationCache* mainResourceCache = documentLoader->applicationCacheHost()->mainResourceApplicationCache();
224 if (mainResourceCache) {
225 mainResourceCache->group()->associateDocumentLoaderWithCache(documentLoader, mainResourceCache);
226 mainResourceCache->group()->update(frame, ApplicationCacheUpdateWithBrowsingContext);
230 void ApplicationCacheGroup::finishedLoadingMainResource(DocumentLoader* loader)
232 ASSERT(m_pendingMasterResourceLoaders.contains(loader));
233 ASSERT(m_completionType == None || m_pendingEntries.isEmpty());
234 KURL url = loader->url();
235 if (url.hasFragmentIdentifier())
236 url.removeFragmentIdentifier();
238 switch (m_completionType) {
240 // The main resource finished loading before the manifest was ready. It will be handled via dispatchMainResources() later.
243 ASSERT(!m_cacheBeingUpdated);
244 associateDocumentLoaderWithCache(loader, m_newestCache.get());
246 if (ApplicationCacheResource* resource = m_newestCache->resourceForURL(url)) {
247 if (!(resource->type() & ApplicationCacheResource::Master)) {
248 resource->addType(ApplicationCacheResource::Master);
249 ASSERT(!resource->storageID());
252 m_newestCache->addResource(ApplicationCacheResource::create(url, loader->response(), ApplicationCacheResource::Master, loader->mainResourceData()));
256 // Cache update has been a failure, so there is no reason to keep the document associated with the incomplete cache
257 // (its main resource was not cached yet, so it is likely that the application changed significantly server-side).
258 ASSERT(!m_cacheBeingUpdated); // Already cleared out by stopLoading().
259 loader->applicationCacheHost()->setApplicationCache(0); // Will unset candidate, too.
260 m_associatedDocumentLoaders.remove(loader);
261 postListenerTask(ApplicationCacheHost::ERROR_EVENT, loader);
264 ASSERT(m_associatedDocumentLoaders.contains(loader));
266 if (ApplicationCacheResource* resource = m_cacheBeingUpdated->resourceForURL(url)) {
267 if (!(resource->type() & ApplicationCacheResource::Master)) {
268 resource->addType(ApplicationCacheResource::Master);
269 ASSERT(!resource->storageID());
272 m_cacheBeingUpdated->addResource(ApplicationCacheResource::create(url, loader->response(), ApplicationCacheResource::Master, loader->mainResourceData()));
273 // The "cached" event will be posted to all associated documents once update is complete.
277 ASSERT(m_downloadingPendingMasterResourceLoadersCount > 0);
278 m_downloadingPendingMasterResourceLoadersCount--;
279 checkIfLoadIsComplete();
282 void ApplicationCacheGroup::failedLoadingMainResource(DocumentLoader* loader)
284 ASSERT(m_pendingMasterResourceLoaders.contains(loader));
285 ASSERT(m_completionType == None || m_pendingEntries.isEmpty());
287 switch (m_completionType) {
289 // The main resource finished loading before the manifest was ready. It will be handled via dispatchMainResources() later.
292 ASSERT(!m_cacheBeingUpdated);
294 // 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,
295 // 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.
296 postListenerTask(ApplicationCacheHost::ERROR_EVENT, loader);
300 // Cache update failed, too.
301 ASSERT(!m_cacheBeingUpdated); // Already cleared out by stopLoading().
302 ASSERT(!loader->applicationCacheHost()->applicationCache() || loader->applicationCacheHost()->applicationCache() == m_cacheBeingUpdated);
304 loader->applicationCacheHost()->setApplicationCache(0); // Will unset candidate, too.
305 m_associatedDocumentLoaders.remove(loader);
306 postListenerTask(ApplicationCacheHost::ERROR_EVENT, loader);
309 // The cache manifest didn't list this main resource, and all cache entries were already updated successfully - but the main resource failed to load,
310 // so it cannot be stored to the cache. If there are other main resources being downloaded for this cache group, they may still succeed.
311 ASSERT(m_associatedDocumentLoaders.contains(loader));
312 ASSERT(loader->applicationCacheHost()->applicationCache() == m_cacheBeingUpdated);
313 ASSERT(!loader->applicationCacheHost()->candidateApplicationCacheGroup());
314 m_associatedDocumentLoaders.remove(loader);
315 loader->applicationCacheHost()->setApplicationCache(0);
317 postListenerTask(ApplicationCacheHost::ERROR_EVENT, loader);
322 ASSERT(m_downloadingPendingMasterResourceLoadersCount > 0);
323 m_downloadingPendingMasterResourceLoadersCount--;
324 checkIfLoadIsComplete();
327 void ApplicationCacheGroup::stopLoading()
329 if (m_manifestHandle) {
330 ASSERT(!m_currentHandle);
332 ASSERT(m_manifestHandle->client() == this);
333 m_manifestHandle->setClient(0);
335 m_manifestHandle->cancel();
336 m_manifestHandle = 0;
339 if (m_currentHandle) {
340 ASSERT(!m_manifestHandle);
341 ASSERT(m_cacheBeingUpdated);
343 ASSERT(m_currentHandle->client() == this);
344 m_currentHandle->setClient(0);
346 m_currentHandle->cancel();
350 // FIXME: Resetting just a tiny part of the state in this function is confusing. Callers have to take care of a lot more.
351 m_cacheBeingUpdated = 0;
352 m_pendingEntries.clear();
355 void ApplicationCacheGroup::disassociateDocumentLoader(DocumentLoader* loader)
357 HashSet<DocumentLoader*>::iterator it = m_associatedDocumentLoaders.find(loader);
358 if (it != m_associatedDocumentLoaders.end())
359 m_associatedDocumentLoaders.remove(it);
361 m_pendingMasterResourceLoaders.remove(loader);
363 loader->applicationCacheHost()->setApplicationCache(0); // Will set candidate to 0, too.
365 if (!m_associatedDocumentLoaders.isEmpty() || !m_pendingMasterResourceLoaders.isEmpty())
368 if (m_caches.isEmpty()) {
369 // There is an initial cache attempt in progress.
370 ASSERT(!m_newestCache);
371 // Delete ourselves, causing the cache attempt to be stopped.
376 ASSERT(m_caches.contains(m_newestCache.get()));
378 // Release our reference to the newest cache. This could cause us to be deleted.
379 // Any ongoing updates will be stopped from destructor.
380 m_newestCache.release();
383 void ApplicationCacheGroup::cacheDestroyed(ApplicationCache* cache)
385 if (!m_caches.contains(cache))
388 m_caches.remove(cache);
390 if (m_caches.isEmpty()) {
391 ASSERT(m_associatedDocumentLoaders.isEmpty());
392 ASSERT(m_pendingMasterResourceLoaders.isEmpty());
397 void ApplicationCacheGroup::stopLoadingInFrame(Frame* frame)
399 if (frame != m_frame)
405 void ApplicationCacheGroup::setNewestCache(PassRefPtr<ApplicationCache> newestCache)
407 m_newestCache = newestCache;
409 m_caches.add(m_newestCache.get());
410 m_newestCache->setGroup(this);
411 InspectorInstrumentation::updateApplicationCacheStatus(m_frame);
414 void ApplicationCacheGroup::makeObsolete()
420 cacheStorage().cacheGroupMadeObsolete(this);
421 ASSERT(!m_storageID);
422 InspectorInstrumentation::updateApplicationCacheStatus(m_frame);
425 void ApplicationCacheGroup::update(Frame* frame, ApplicationCacheUpdateOption updateOption)
427 if (m_updateStatus == Checking || m_updateStatus == Downloading) {
428 if (updateOption == ApplicationCacheUpdateWithBrowsingContext) {
429 postListenerTask(ApplicationCacheHost::CHECKING_EVENT, frame->loader()->documentLoader());
430 if (m_updateStatus == Downloading)
431 postListenerTask(ApplicationCacheHost::DOWNLOADING_EVENT, frame->loader()->documentLoader());
436 // Don't change anything on disk if private browsing is enabled.
437 if (!frame->settings() || frame->settings()->privateBrowsingEnabled()) {
438 ASSERT(m_pendingMasterResourceLoaders.isEmpty());
439 ASSERT(m_pendingEntries.isEmpty());
440 ASSERT(!m_cacheBeingUpdated);
441 postListenerTask(ApplicationCacheHost::CHECKING_EVENT, frame->loader()->documentLoader());
442 postListenerTask(ApplicationCacheHost::NOUPDATE_EVENT, frame->loader()->documentLoader());
449 setUpdateStatus(Checking);
451 postListenerTask(ApplicationCacheHost::CHECKING_EVENT, m_associatedDocumentLoaders);
452 if (!m_newestCache) {
453 ASSERT(updateOption == ApplicationCacheUpdateWithBrowsingContext);
454 postListenerTask(ApplicationCacheHost::CHECKING_EVENT, frame->loader()->documentLoader());
457 ASSERT(!m_manifestHandle);
458 ASSERT(!m_manifestResource);
459 ASSERT(!m_currentHandle);
460 ASSERT(!m_currentResource);
461 ASSERT(m_completionType == None);
463 // FIXME: Handle defer loading
464 m_manifestHandle = createResourceHandle(m_manifestURL, m_newestCache ? m_newestCache->manifestResource() : 0);
467 PassRefPtr<ResourceHandle> ApplicationCacheGroup::createResourceHandle(const KURL& url, ApplicationCacheResource* newestCachedResource)
469 ResourceRequest request(url);
470 m_frame->loader()->applyUserAgent(request);
471 request.setHTTPHeaderField("Cache-Control", "max-age=0");
473 if (newestCachedResource) {
474 const String& lastModified = newestCachedResource->response().httpHeaderField("Last-Modified");
475 const String& eTag = newestCachedResource->response().httpHeaderField("ETag");
476 if (!lastModified.isEmpty() || !eTag.isEmpty()) {
477 if (!lastModified.isEmpty())
478 request.setHTTPHeaderField("If-Modified-Since", lastModified);
480 request.setHTTPHeaderField("If-None-Match", eTag);
484 RefPtr<ResourceHandle> handle = ResourceHandle::create(m_frame->loader()->networkingContext(), request, this, false, true);
485 #if ENABLE(INSPECTOR)
486 // Because willSendRequest only gets called during redirects, we initialize
487 // the identifier and the first willSendRequest here.
488 m_currentResourceIdentifier = m_frame->page()->progress()->createUniqueIdentifier();
489 ResourceResponse redirectResponse = ResourceResponse();
490 InspectorInstrumentation::willSendRequest(m_frame, m_currentResourceIdentifier, m_frame->loader()->documentLoader(), request, redirectResponse);
495 void ApplicationCacheGroup::didReceiveResponse(ResourceHandle* handle, const ResourceResponse& response)
497 #if ENABLE(INSPECTOR)
498 DocumentLoader* loader = (handle == m_manifestHandle) ? 0 : m_frame->loader()->documentLoader();
499 InspectorInstrumentationCookie cookie = InspectorInstrumentation::willReceiveResourceResponse(m_frame, m_currentResourceIdentifier, response);
500 InspectorInstrumentation::didReceiveResourceResponse(cookie, m_currentResourceIdentifier, loader, response);
503 if (handle == m_manifestHandle) {
504 didReceiveManifestResponse(response);
508 ASSERT(handle == m_currentHandle);
510 KURL url(handle->firstRequest().url());
511 if (url.hasFragmentIdentifier())
512 url.removeFragmentIdentifier();
514 ASSERT(!m_currentResource);
515 ASSERT(m_pendingEntries.contains(url));
517 unsigned type = m_pendingEntries.get(url);
519 // If this is an initial cache attempt, we should not get master resources delivered here.
521 ASSERT(!(type & ApplicationCacheResource::Master));
523 if (m_newestCache && response.httpStatusCode() == 304) { // Not modified.
524 ApplicationCacheResource* newestCachedResource = m_newestCache->resourceForURL(url);
525 if (newestCachedResource) {
526 m_cacheBeingUpdated->addResource(ApplicationCacheResource::create(url, newestCachedResource->response(), type, newestCachedResource->data(), newestCachedResource->path()));
527 m_pendingEntries.remove(m_currentHandle->firstRequest().url());
528 m_currentHandle->cancel();
530 // Load the next resource, if any.
534 // The server could return 304 for an unconditional request - in this case, we handle the response as a normal error.
537 if (response.httpStatusCode() / 100 != 2 || response.url() != m_currentHandle->firstRequest().url()) {
538 if ((type & ApplicationCacheResource::Explicit) || (type & ApplicationCacheResource::Fallback)) {
539 m_frame->domWindow()->console()->addMessage(OtherMessageSource, LogMessageType, ErrorMessageLevel, "Application Cache update failed, because " + m_currentHandle->firstRequest().url().string() +
540 ((response.httpStatusCode() / 100 != 2) ? " could not be fetched." : " was redirected."), 0, String());
541 // Note that cacheUpdateFailed() can cause the cache group to be deleted.
543 } else if (response.httpStatusCode() == 404 || response.httpStatusCode() == 410) {
544 // Skip this resource. It is dropped from the cache.
545 m_currentHandle->cancel();
547 m_pendingEntries.remove(url);
548 // Load the next resource, if any.
551 // Copy the resource and its metadata from the newest application cache in cache group whose completeness flag is complete, and act
552 // as if that was the fetched resource, ignoring the resource obtained from the network.
553 ASSERT(m_newestCache);
554 ApplicationCacheResource* newestCachedResource = m_newestCache->resourceForURL(handle->firstRequest().url());
555 ASSERT(newestCachedResource);
556 m_cacheBeingUpdated->addResource(ApplicationCacheResource::create(url, newestCachedResource->response(), type, newestCachedResource->data(), newestCachedResource->path()));
557 m_pendingEntries.remove(m_currentHandle->firstRequest().url());
558 m_currentHandle->cancel();
560 // Load the next resource, if any.
566 m_currentResource = ApplicationCacheResource::create(url, response, type);
569 void ApplicationCacheGroup::didReceiveData(ResourceHandle* handle, const char* data, int length, int encodedDataLength)
571 UNUSED_PARAM(encodedDataLength);
573 #if ENABLE(INSPECTOR)
574 InspectorInstrumentation::didReceiveData(m_frame, m_currentResourceIdentifier, 0, length, 0);
577 if (handle == m_manifestHandle) {
578 didReceiveManifestData(data, length);
582 ASSERT(handle == m_currentHandle);
584 ASSERT(m_currentResource);
585 m_currentResource->data()->append(data, length);
588 void ApplicationCacheGroup::didFinishLoading(ResourceHandle* handle, double finishTime)
590 #if ENABLE(INSPECTOR)
591 InspectorInstrumentation::didFinishLoading(m_frame, m_frame->loader()->documentLoader(), m_currentResourceIdentifier, finishTime);
594 if (handle == m_manifestHandle) {
595 didFinishLoadingManifest();
599 ASSERT(m_currentHandle == handle);
600 ASSERT(m_pendingEntries.contains(handle->firstRequest().url()));
602 m_pendingEntries.remove(handle->firstRequest().url());
604 ASSERT(m_cacheBeingUpdated);
606 m_cacheBeingUpdated->addResource(m_currentResource.release());
609 // While downloading check to see if we have exceeded the available quota.
610 // We can stop immediately if we have already previously failed
611 // due to an earlier quota restriction. The client was already notified
612 // of the quota being reached and decided not to increase it then.
613 // FIXME: Should we break earlier and prevent redownloading on later page loads?
614 if (m_originQuotaExceededPreviously && m_availableSpaceInQuota < m_cacheBeingUpdated->estimatedSizeInStorage()) {
615 m_currentResource = 0;
616 m_frame->domWindow()->console()->addMessage(OtherMessageSource, LogMessageType, ErrorMessageLevel, "Application Cache update failed, because size quota was exceeded.", 0, String());
621 // Load the next resource, if any.
625 void ApplicationCacheGroup::didFail(ResourceHandle* handle, const ResourceError& error)
627 #if ENABLE(INSPECTOR)
628 InspectorInstrumentation::didFailLoading(m_frame, m_frame->loader()->documentLoader(), m_currentResourceIdentifier, error);
631 if (handle == m_manifestHandle) {
632 // A network error is logged elsewhere, no need to log again. Also, it's normal for manifest fetching to fail when working offline.
637 ASSERT(handle == m_currentHandle);
639 unsigned type = m_currentResource ? m_currentResource->type() : m_pendingEntries.get(handle->firstRequest().url());
640 KURL url(handle->firstRequest().url());
641 if (url.hasFragmentIdentifier())
642 url.removeFragmentIdentifier();
644 ASSERT(!m_currentResource || !m_pendingEntries.contains(url));
645 m_currentResource = 0;
646 m_pendingEntries.remove(url);
648 if ((type & ApplicationCacheResource::Explicit) || (type & ApplicationCacheResource::Fallback)) {
649 m_frame->domWindow()->console()->addMessage(OtherMessageSource, LogMessageType, ErrorMessageLevel, "Application Cache update failed, because " + url.string() + " could not be fetched.", 0, String());
650 // Note that cacheUpdateFailed() can cause the cache group to be deleted.
653 // Copy the resource and its metadata from the newest application cache in cache group whose completeness flag is complete, and act
654 // as if that was the fetched resource, ignoring the resource obtained from the network.
655 ASSERT(m_newestCache);
656 ApplicationCacheResource* newestCachedResource = m_newestCache->resourceForURL(url);
657 ASSERT(newestCachedResource);
658 m_cacheBeingUpdated->addResource(ApplicationCacheResource::create(url, newestCachedResource->response(), type, newestCachedResource->data(), newestCachedResource->path()));
659 // Load the next resource, if any.
664 void ApplicationCacheGroup::didReceiveManifestResponse(const ResourceResponse& response)
666 ASSERT(!m_manifestResource);
667 ASSERT(m_manifestHandle);
669 if (response.httpStatusCode() == 404 || response.httpStatusCode() == 410) {
674 if (response.httpStatusCode() == 304)
677 if (response.httpStatusCode() / 100 != 2) {
678 m_frame->domWindow()->console()->addMessage(OtherMessageSource, LogMessageType, ErrorMessageLevel, "Application Cache manifest could not be fetched.", 0, String());
683 if (response.url() != m_manifestHandle->firstRequest().url()) {
684 m_frame->domWindow()->console()->addMessage(OtherMessageSource, LogMessageType, ErrorMessageLevel, "Application Cache manifest could not be fetched, because a redirection was attempted.", 0, String());
689 if (!equalIgnoringCase(response.mimeType(), "text/cache-manifest")) {
690 m_frame->domWindow()->console()->addMessage(OtherMessageSource, LogMessageType, ErrorMessageLevel, "Application Cache manifest had an incorrect MIME type: " + response.mimeType() + ".", 0, String());
695 m_manifestResource = ApplicationCacheResource::create(m_manifestHandle->firstRequest().url(), response, ApplicationCacheResource::Manifest);
698 void ApplicationCacheGroup::didReceiveManifestData(const char* data, int length)
700 if (m_manifestResource)
701 m_manifestResource->data()->append(data, length);
704 void ApplicationCacheGroup::didFinishLoadingManifest()
706 bool isUpgradeAttempt = m_newestCache;
708 if (!isUpgradeAttempt && !m_manifestResource) {
709 // The server returned 304 Not Modified even though we didn't send a conditional request.
710 m_frame->domWindow()->console()->addMessage(OtherMessageSource, LogMessageType, ErrorMessageLevel, "Application Cache manifest could not be fetched because of an unexpected 304 Not Modified server response.", 0, String());
715 m_manifestHandle = 0;
717 // Check if the manifest was not modified.
718 if (isUpgradeAttempt) {
719 ApplicationCacheResource* newestManifest = m_newestCache->manifestResource();
720 ASSERT(newestManifest);
722 if (!m_manifestResource || // The resource will be null if HTTP response was 304 Not Modified.
723 (newestManifest->data()->size() == m_manifestResource->data()->size() && !memcmp(newestManifest->data()->data(), m_manifestResource->data()->data(), newestManifest->data()->size()))) {
725 m_completionType = NoUpdate;
726 m_manifestResource = 0;
727 deliverDelayedMainResources();
734 if (!parseManifest(m_manifestURL, m_manifestResource->data()->data(), m_manifestResource->data()->size(), manifest)) {
735 // At the time of this writing, lack of "CACHE MANIFEST" signature is the only reason for parseManifest to fail.
736 m_frame->domWindow()->console()->addMessage(OtherMessageSource, LogMessageType, ErrorMessageLevel, "Application Cache manifest could not be parsed. Does it start with CACHE MANIFEST?", 0, String());
741 ASSERT(!m_cacheBeingUpdated);
742 m_cacheBeingUpdated = ApplicationCache::create();
743 m_cacheBeingUpdated->setGroup(this);
745 HashSet<DocumentLoader*>::const_iterator masterEnd = m_pendingMasterResourceLoaders.end();
746 for (HashSet<DocumentLoader*>::const_iterator iter = m_pendingMasterResourceLoaders.begin(); iter != masterEnd; ++iter)
747 associateDocumentLoaderWithCache(*iter, m_cacheBeingUpdated.get());
749 // We have the manifest, now download the resources.
750 setUpdateStatus(Downloading);
752 postListenerTask(ApplicationCacheHost::DOWNLOADING_EVENT, m_associatedDocumentLoaders);
754 ASSERT(m_pendingEntries.isEmpty());
756 if (isUpgradeAttempt) {
757 ApplicationCache::ResourceMap::const_iterator end = m_newestCache->end();
758 for (ApplicationCache::ResourceMap::const_iterator it = m_newestCache->begin(); it != end; ++it) {
759 unsigned type = it->second->type();
760 if (type & ApplicationCacheResource::Master)
761 addEntry(it->first, type);
765 HashSet<String>::const_iterator end = manifest.explicitURLs.end();
766 for (HashSet<String>::const_iterator it = manifest.explicitURLs.begin(); it != end; ++it)
767 addEntry(*it, ApplicationCacheResource::Explicit);
769 size_t fallbackCount = manifest.fallbackURLs.size();
770 for (size_t i = 0; i < fallbackCount; ++i)
771 addEntry(manifest.fallbackURLs[i].second, ApplicationCacheResource::Fallback);
773 m_cacheBeingUpdated->setOnlineWhitelist(manifest.onlineWhitelistedURLs);
774 m_cacheBeingUpdated->setFallbackURLs(manifest.fallbackURLs);
775 m_cacheBeingUpdated->setAllowsAllNetworkRequests(manifest.allowAllNetworkRequests);
777 m_progressTotal = m_pendingEntries.size();
780 recalculateAvailableSpaceInQuota();
785 void ApplicationCacheGroup::didReachMaxAppCacheSize()
788 ASSERT(m_cacheBeingUpdated);
789 m_frame->page()->chrome()->client()->reachedMaxAppCacheSize(cacheStorage().spaceNeeded(m_cacheBeingUpdated->estimatedSizeInStorage()));
790 m_calledReachedMaxAppCacheSize = true;
791 checkIfLoadIsComplete();
794 void ApplicationCacheGroup::didReachOriginQuota(int64_t totalSpaceNeeded)
796 // Inform the client the origin quota has been reached, they may decide to increase the quota.
797 // We expect quota to be increased synchronously while waiting for the call to return.
798 m_frame->page()->chrome()->client()->reachedApplicationCacheOriginQuota(m_origin.get(), totalSpaceNeeded);
801 void ApplicationCacheGroup::cacheUpdateFailed()
804 m_manifestResource = 0;
806 // Wait for master resource loads to finish.
807 m_completionType = Failure;
808 deliverDelayedMainResources();
811 void ApplicationCacheGroup::recalculateAvailableSpaceInQuota()
813 if (!cacheStorage().calculateRemainingSizeForOriginExcludingCache(m_origin.get(), m_newestCache.get(), m_availableSpaceInQuota)) {
814 // Failed to determine what is left in the quota. Fallback to allowing anything.
815 m_availableSpaceInQuota = ApplicationCacheStorage::noQuota();
819 void ApplicationCacheGroup::manifestNotFound()
823 postListenerTask(ApplicationCacheHost::OBSOLETE_EVENT, m_associatedDocumentLoaders);
824 postListenerTask(ApplicationCacheHost::ERROR_EVENT, m_pendingMasterResourceLoaders);
828 ASSERT(m_pendingEntries.isEmpty());
829 m_manifestResource = 0;
831 while (!m_pendingMasterResourceLoaders.isEmpty()) {
832 HashSet<DocumentLoader*>::iterator it = m_pendingMasterResourceLoaders.begin();
834 ASSERT((*it)->applicationCacheHost()->candidateApplicationCacheGroup() == this);
835 ASSERT(!(*it)->applicationCacheHost()->applicationCache());
836 (*it)->applicationCacheHost()->setCandidateApplicationCacheGroup(0);
837 m_pendingMasterResourceLoaders.remove(it);
840 m_downloadingPendingMasterResourceLoadersCount = 0;
841 setUpdateStatus(Idle);
844 if (m_caches.isEmpty()) {
845 ASSERT(m_associatedDocumentLoaders.isEmpty());
846 ASSERT(!m_cacheBeingUpdated);
851 void ApplicationCacheGroup::checkIfLoadIsComplete()
853 if (m_manifestHandle || !m_pendingEntries.isEmpty() || m_downloadingPendingMasterResourceLoadersCount)
856 // We're done, all resources have finished downloading (successfully or not).
858 bool isUpgradeAttempt = m_newestCache;
860 switch (m_completionType) {
862 ASSERT_NOT_REACHED();
865 ASSERT(isUpgradeAttempt);
866 ASSERT(!m_cacheBeingUpdated);
868 // The storage could have been manually emptied by the user.
870 cacheStorage().storeNewestCache(this);
872 postListenerTask(ApplicationCacheHost::NOUPDATE_EVENT, m_associatedDocumentLoaders);
875 ASSERT(!m_cacheBeingUpdated);
876 postListenerTask(ApplicationCacheHost::ERROR_EVENT, m_associatedDocumentLoaders);
877 if (m_caches.isEmpty()) {
878 ASSERT(m_associatedDocumentLoaders.isEmpty());
884 // 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>)
886 ASSERT(m_cacheBeingUpdated);
887 if (m_manifestResource)
888 m_cacheBeingUpdated->setManifestResource(m_manifestResource.release());
890 // We can get here as a result of retrying the Complete step, following
891 // a failure of the cache storage to save the newest cache due to hitting
892 // the maximum size. In such a case, m_manifestResource may be 0, as
893 // the manifest was already set on the newest cache object.
894 ASSERT(cacheStorage().isMaximumSizeReached() && m_calledReachedMaxAppCacheSize);
897 RefPtr<ApplicationCache> oldNewestCache = (m_newestCache == m_cacheBeingUpdated) ? RefPtr<ApplicationCache>() : m_newestCache;
899 // If we exceeded the origin quota while downloading we can request a quota
900 // increase now, before we attempt to store the cache.
901 int64_t totalSpaceNeeded;
902 if (!cacheStorage().checkOriginQuota(this, oldNewestCache.get(), m_cacheBeingUpdated.get(), totalSpaceNeeded))
903 didReachOriginQuota(totalSpaceNeeded);
905 ApplicationCacheStorage::FailureReason failureReason;
906 setNewestCache(m_cacheBeingUpdated.release());
907 if (cacheStorage().storeNewestCache(this, oldNewestCache.get(), failureReason)) {
908 // New cache stored, now remove the old cache.
910 cacheStorage().remove(oldNewestCache.get());
912 // Fire the final progress event.
913 ASSERT(m_progressDone == m_progressTotal);
914 postListenerTask(ApplicationCacheHost::PROGRESS_EVENT, m_progressTotal, m_progressDone, m_associatedDocumentLoaders);
916 // Fire the success event.
917 postListenerTask(isUpgradeAttempt ? ApplicationCacheHost::UPDATEREADY_EVENT : ApplicationCacheHost::CACHED_EVENT, m_associatedDocumentLoaders);
918 // It is clear that the origin quota was not reached, so clear the flag if it was set.
919 m_originQuotaExceededPreviously = false;
921 if (failureReason == ApplicationCacheStorage::OriginQuotaReached) {
922 // We ran out of space for this origin. Fall down to the normal error handling
923 // after recording this state.
924 m_originQuotaExceededPreviously = true;
925 m_frame->domWindow()->console()->addMessage(OtherMessageSource, LogMessageType, ErrorMessageLevel, "Application Cache update failed, because size quota was exceeded.", 0, String());
928 if (failureReason == ApplicationCacheStorage::TotalQuotaReached && !m_calledReachedMaxAppCacheSize) {
929 // FIXME: Should this be handled more like Origin Quotas? Does this fail properly?
931 // We ran out of space. All the changes in the cache storage have
932 // been rolled back. We roll back to the previous state in here,
933 // as well, call the chrome client asynchronously and retry to
934 // save the new cache.
936 // Save a reference to the new cache.
937 m_cacheBeingUpdated = m_newestCache.release();
938 if (oldNewestCache) {
939 // Reinstate the oldNewestCache.
940 setNewestCache(oldNewestCache.release());
942 scheduleReachedMaxAppCacheSizeCallback();
946 // Run the "cache failure steps"
947 // Fire the error events to all pending master entries, as well any other cache hosts
948 // currently associated with a cache in this group.
949 postListenerTask(ApplicationCacheHost::ERROR_EVENT, m_associatedDocumentLoaders);
950 // Disassociate the pending master entries from the failed new cache. Note that
951 // all other loaders in the m_associatedDocumentLoaders are still associated with
952 // some other cache in this group. They are not associated with the failed new cache.
954 // Need to copy loaders, because the cache group may be destroyed at the end of iteration.
955 Vector<DocumentLoader*> loaders;
956 copyToVector(m_pendingMasterResourceLoaders, loaders);
957 size_t count = loaders.size();
958 for (size_t i = 0; i != count; ++i)
959 disassociateDocumentLoader(loaders[i]); // This can delete this group.
961 // Reinstate the oldNewestCache, if there was one.
962 if (oldNewestCache) {
963 // This will discard the failed new cache.
964 setNewestCache(oldNewestCache.release());
966 // We must have been deleted by the last call to disassociateDocumentLoader().
974 // Empty cache group's list of pending master entries.
975 m_pendingMasterResourceLoaders.clear();
976 m_completionType = None;
977 setUpdateStatus(Idle);
979 m_availableSpaceInQuota = ApplicationCacheStorage::unknownQuota();
980 m_calledReachedMaxAppCacheSize = false;
983 void ApplicationCacheGroup::startLoadingEntry()
985 ASSERT(m_cacheBeingUpdated);
987 if (m_pendingEntries.isEmpty()) {
988 m_completionType = Completed;
989 deliverDelayedMainResources();
993 EntryMap::const_iterator it = m_pendingEntries.begin();
995 postListenerTask(ApplicationCacheHost::PROGRESS_EVENT, m_progressTotal, m_progressDone, m_associatedDocumentLoaders);
998 ASSERT(!m_currentHandle);
1000 m_currentHandle = createResourceHandle(KURL(ParsedURLString, it->first), m_newestCache ? m_newestCache->resourceForURL(it->first) : 0);
1003 void ApplicationCacheGroup::deliverDelayedMainResources()
1005 // Need to copy loaders, because the cache group may be destroyed at the end of iteration.
1006 Vector<DocumentLoader*> loaders;
1007 copyToVector(m_pendingMasterResourceLoaders, loaders);
1008 size_t count = loaders.size();
1009 for (size_t i = 0; i != count; ++i) {
1010 DocumentLoader* loader = loaders[i];
1011 if (loader->isLoadingMainResource())
1014 const ResourceError& error = loader->mainDocumentError();
1016 finishedLoadingMainResource(loader);
1018 failedLoadingMainResource(loader);
1021 checkIfLoadIsComplete();
1024 void ApplicationCacheGroup::addEntry(const String& url, unsigned type)
1026 ASSERT(m_cacheBeingUpdated);
1027 ASSERT(!KURL(ParsedURLString, url).hasFragmentIdentifier());
1029 // Don't add the URL if we already have an master resource in the cache
1030 // (i.e., the main resource finished loading before the manifest).
1031 if (ApplicationCacheResource* resource = m_cacheBeingUpdated->resourceForURL(url)) {
1032 ASSERT(resource->type() & ApplicationCacheResource::Master);
1033 ASSERT(!m_frame->loader()->documentLoader()->isLoadingMainResource());
1035 resource->addType(type);
1039 // Don't add the URL if it's the same as the manifest URL.
1040 ASSERT(m_manifestResource);
1041 if (m_manifestResource->url() == url) {
1042 m_manifestResource->addType(type);
1046 pair<EntryMap::iterator, bool> result = m_pendingEntries.add(url, type);
1049 result.first->second |= type;
1052 void ApplicationCacheGroup::associateDocumentLoaderWithCache(DocumentLoader* loader, ApplicationCache* cache)
1054 // If teardown started already, revive the group.
1055 if (!m_newestCache && !m_cacheBeingUpdated)
1056 m_newestCache = cache;
1058 ASSERT(!m_isObsolete);
1060 loader->applicationCacheHost()->setApplicationCache(cache);
1062 ASSERT(!m_associatedDocumentLoaders.contains(loader));
1063 m_associatedDocumentLoaders.add(loader);
1066 class ChromeClientCallbackTimer: public TimerBase {
1068 ChromeClientCallbackTimer(ApplicationCacheGroup* cacheGroup)
1069 : m_cacheGroup(cacheGroup)
1074 virtual void fired()
1076 m_cacheGroup->didReachMaxAppCacheSize();
1079 // Note that there is no need to use a RefPtr here. The ApplicationCacheGroup instance is guaranteed
1080 // to be alive when the timer fires since invoking the ChromeClient callback is part of its normal
1081 // update machinery and nothing can yet cause it to get deleted.
1082 ApplicationCacheGroup* m_cacheGroup;
1085 void ApplicationCacheGroup::scheduleReachedMaxAppCacheSizeCallback()
1087 ASSERT(isMainThread());
1088 ChromeClientCallbackTimer* timer = new ChromeClientCallbackTimer(this);
1089 timer->startOneShot(0);
1090 // The timer will delete itself once it fires.
1093 class CallCacheListenerTask : public ScriptExecutionContext::Task {
1095 static PassOwnPtr<CallCacheListenerTask> create(PassRefPtr<DocumentLoader> loader, ApplicationCacheHost::EventID eventID, int progressTotal, int progressDone)
1097 return adoptPtr(new CallCacheListenerTask(loader, eventID, progressTotal, progressDone));
1100 virtual void performTask(ScriptExecutionContext* context)
1103 ASSERT_UNUSED(context, context->isDocument());
1104 Frame* frame = m_documentLoader->frame();
1108 ASSERT(frame->loader()->documentLoader() == m_documentLoader.get());
1110 m_documentLoader->applicationCacheHost()->notifyDOMApplicationCache(m_eventID, m_progressTotal, m_progressDone);
1114 CallCacheListenerTask(PassRefPtr<DocumentLoader> loader, ApplicationCacheHost::EventID eventID, int progressTotal, int progressDone)
1115 : m_documentLoader(loader)
1116 , m_eventID(eventID)
1117 , m_progressTotal(progressTotal)
1118 , m_progressDone(progressDone)
1122 RefPtr<DocumentLoader> m_documentLoader;
1123 ApplicationCacheHost::EventID m_eventID;
1124 int m_progressTotal;
1128 void ApplicationCacheGroup::postListenerTask(ApplicationCacheHost::EventID eventID, int progressTotal, int progressDone, const HashSet<DocumentLoader*>& loaderSet)
1130 HashSet<DocumentLoader*>::const_iterator loaderSetEnd = loaderSet.end();
1131 for (HashSet<DocumentLoader*>::const_iterator iter = loaderSet.begin(); iter != loaderSetEnd; ++iter)
1132 postListenerTask(eventID, progressTotal, progressDone, *iter);
1135 void ApplicationCacheGroup::postListenerTask(ApplicationCacheHost::EventID eventID, int progressTotal, int progressDone, DocumentLoader* loader)
1137 Frame* frame = loader->frame();
1141 ASSERT(frame->loader()->documentLoader() == loader);
1143 frame->document()->postTask(CallCacheListenerTask::create(loader, eventID, progressTotal, progressDone));
1146 void ApplicationCacheGroup::setUpdateStatus(UpdateStatus status)
1148 m_updateStatus = status;
1149 InspectorInstrumentation::updateApplicationCacheStatus(m_frame);
1152 void ApplicationCacheGroup::clearStorageID()
1156 HashSet<ApplicationCache*>::const_iterator end = m_caches.end();
1157 for (HashSet<ApplicationCache*>::const_iterator it = m_caches.begin(); it != end; ++it)
1158 (*it)->clearStorageID();
1164 #endif // ENABLE(OFFLINE_WEB_APPLICATIONS)