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