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