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