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