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 "ApplicationCacheHost.h" 28 29 #if ENABLE(OFFLINE_WEB_APPLICATIONS) 30 31 #include "ApplicationCache.h" 32 #include "ApplicationCacheGroup.h" 33 #include "ApplicationCacheResource.h" 34 #include "DocumentLoader.h" 35 #include "DOMApplicationCache.h" 36 #include "Frame.h" 37 #include "FrameLoader.h" 38 #include "FrameLoaderClient.h" 39 #include "MainResourceLoader.h" 40 #include "ProgressEvent.h" 41 #include "ResourceLoader.h" 42 #include "ResourceRequest.h" 43 #include "Settings.h" 44 45 namespace WebCore { 46 47 ApplicationCacheHost::ApplicationCacheHost(DocumentLoader* documentLoader) 48 : m_domApplicationCache(0) 49 , m_documentLoader(documentLoader) 50 , m_defersEvents(true) 51 , m_candidateApplicationCacheGroup(0) 52 { 53 ASSERT(m_documentLoader); 54 } 55 56 ApplicationCacheHost::~ApplicationCacheHost() 57 { 58 ASSERT(!m_applicationCache || !m_candidateApplicationCacheGroup || m_applicationCache->group() == m_candidateApplicationCacheGroup); 59 60 if (m_applicationCache) 61 m_applicationCache->group()->disassociateDocumentLoader(m_documentLoader); 62 else if (m_candidateApplicationCacheGroup) 63 m_candidateApplicationCacheGroup->disassociateDocumentLoader(m_documentLoader); 64 } 65 66 void ApplicationCacheHost::selectCacheWithoutManifest() 67 { 68 ApplicationCacheGroup::selectCacheWithoutManifestURL(m_documentLoader->frame()); 69 } 70 71 void ApplicationCacheHost::selectCacheWithManifest(const KURL& manifestURL) 72 { 73 ApplicationCacheGroup::selectCache(m_documentLoader->frame(), manifestURL); 74 } 75 76 void ApplicationCacheHost::maybeLoadMainResource(ResourceRequest& request, SubstituteData& substituteData) 77 { 78 // Check if this request should be loaded from the application cache 79 if (!substituteData.isValid() && isApplicationCacheEnabled()) { 80 ASSERT(!m_mainResourceApplicationCache); 81 82 m_mainResourceApplicationCache = ApplicationCacheGroup::cacheForMainRequest(request, m_documentLoader); 83 84 if (m_mainResourceApplicationCache) { 85 // Get the resource from the application cache. By definition, cacheForMainRequest() returns a cache that contains the resource. 86 ApplicationCacheResource* resource = m_mainResourceApplicationCache->resourceForRequest(request); 87 substituteData = SubstituteData(resource->data(), 88 resource->response().mimeType(), 89 resource->response().textEncodingName(), KURL()); 90 } 91 } 92 } 93 94 void ApplicationCacheHost::maybeLoadMainResourceForRedirect(ResourceRequest& request, SubstituteData& substituteData) 95 { 96 ASSERT(status() == UNCACHED); 97 maybeLoadMainResource(request, substituteData); 98 } 99 100 bool ApplicationCacheHost::maybeLoadFallbackForMainResponse(const ResourceRequest& request, const ResourceResponse& r) 101 { 102 if (r.httpStatusCode() / 100 == 4 || r.httpStatusCode() / 100 == 5) { 103 ASSERT(!m_mainResourceApplicationCache); 104 if (isApplicationCacheEnabled()) { 105 m_mainResourceApplicationCache = ApplicationCacheGroup::fallbackCacheForMainRequest(request, documentLoader()); 106 107 if (scheduleLoadFallbackResourceFromApplicationCache(documentLoader()->mainResourceLoader(), m_mainResourceApplicationCache.get())) 108 return true; 109 } 110 } 111 return false; 112 } 113 114 bool ApplicationCacheHost::maybeLoadFallbackForMainError(const ResourceRequest& request, const ResourceError& error) 115 { 116 if (!error.isCancellation()) { 117 ASSERT(!m_mainResourceApplicationCache); 118 if (isApplicationCacheEnabled()) { 119 m_mainResourceApplicationCache = ApplicationCacheGroup::fallbackCacheForMainRequest(request, m_documentLoader); 120 121 if (scheduleLoadFallbackResourceFromApplicationCache(documentLoader()->mainResourceLoader(), m_mainResourceApplicationCache.get())) 122 return true; 123 } 124 } 125 return false; 126 } 127 128 void ApplicationCacheHost::mainResourceDataReceived(const char*, int, long long, bool) 129 { 130 // This method is here to facilitate alternate implemetations of this interface by the host browser. 131 } 132 133 void ApplicationCacheHost::failedLoadingMainResource() 134 { 135 ApplicationCacheGroup* group = m_candidateApplicationCacheGroup; 136 if (!group && m_applicationCache) { 137 if (mainResourceApplicationCache()) { 138 // Even when the main resource is being loaded from an application cache, loading can fail if aborted. 139 return; 140 } 141 group = m_applicationCache->group(); 142 } 143 144 if (group) 145 group->failedLoadingMainResource(m_documentLoader); 146 } 147 148 void ApplicationCacheHost::finishedLoadingMainResource() 149 { 150 ApplicationCacheGroup* group = candidateApplicationCacheGroup(); 151 if (!group && applicationCache() && !mainResourceApplicationCache()) 152 group = applicationCache()->group(); 153 154 if (group) 155 group->finishedLoadingMainResource(m_documentLoader); 156 } 157 158 bool ApplicationCacheHost::maybeLoadResource(ResourceLoader* loader, ResourceRequest& request, const KURL& originalURL) 159 { 160 if (!isApplicationCacheEnabled()) 161 return false; 162 163 if (request.url() != originalURL) 164 return false; 165 166 ApplicationCacheResource* resource; 167 if (!shouldLoadResourceFromApplicationCache(request, resource)) 168 return false; 169 170 m_documentLoader->m_pendingSubstituteResources.set(loader, resource); 171 m_documentLoader->deliverSubstituteResourcesAfterDelay(); 172 173 return true; 174 } 175 176 bool ApplicationCacheHost::maybeLoadFallbackForRedirect(ResourceLoader* resourceLoader, ResourceRequest& request, const ResourceResponse& redirectResponse) 177 { 178 if (!redirectResponse.isNull() && !protocolHostAndPortAreEqual(request.url(), redirectResponse.url())) 179 if (scheduleLoadFallbackResourceFromApplicationCache(resourceLoader)) 180 return true; 181 return false; 182 } 183 184 bool ApplicationCacheHost::maybeLoadFallbackForResponse(ResourceLoader* resourceLoader, const ResourceResponse& response) 185 { 186 if (response.httpStatusCode() / 100 == 4 || response.httpStatusCode() / 100 == 5) 187 if (scheduleLoadFallbackResourceFromApplicationCache(resourceLoader)) 188 return true; 189 return false; 190 } 191 192 bool ApplicationCacheHost::maybeLoadFallbackForError(ResourceLoader* resourceLoader, const ResourceError& error) 193 { 194 if (!error.isCancellation()) 195 if (scheduleLoadFallbackResourceFromApplicationCache(resourceLoader)) 196 return true; 197 return false; 198 } 199 200 bool ApplicationCacheHost::maybeLoadSynchronously(ResourceRequest& request, ResourceError& error, ResourceResponse& response, Vector<char>& data) 201 { 202 ApplicationCacheResource* resource; 203 if (shouldLoadResourceFromApplicationCache(request, resource)) { 204 if (resource) { 205 response = resource->response(); 206 data.append(resource->data()->data(), resource->data()->size()); 207 } else { 208 error = documentLoader()->frameLoader()->client()->cannotShowURLError(request); 209 } 210 return true; 211 } 212 return false; 213 } 214 215 void ApplicationCacheHost::maybeLoadFallbackSynchronously(const ResourceRequest& request, ResourceError& error, ResourceResponse& response, Vector<char>& data) 216 { 217 // If normal loading results in a redirect to a resource with another origin (indicative of a captive portal), or a 4xx or 5xx status code or equivalent, 218 // or if there were network errors (but not if the user canceled the download), then instead get, from the cache, the resource of the fallback entry 219 // corresponding to the matched namespace. 220 if ((!error.isNull() && !error.isCancellation()) 221 || response.httpStatusCode() / 100 == 4 || response.httpStatusCode() / 100 == 5 222 || !protocolHostAndPortAreEqual(request.url(), response.url())) { 223 ApplicationCacheResource* resource; 224 if (getApplicationCacheFallbackResource(request, resource)) { 225 response = resource->response(); 226 data.clear(); 227 data.append(resource->data()->data(), resource->data()->size()); 228 } 229 } 230 } 231 232 bool ApplicationCacheHost::canCacheInPageCache() const 233 { 234 return !applicationCache() && !candidateApplicationCacheGroup(); 235 } 236 237 void ApplicationCacheHost::setDOMApplicationCache(DOMApplicationCache* domApplicationCache) 238 { 239 ASSERT(!m_domApplicationCache || !domApplicationCache); 240 m_domApplicationCache = domApplicationCache; 241 } 242 243 void ApplicationCacheHost::notifyDOMApplicationCache(EventID id, int total, int done) 244 { 245 if (m_defersEvents) { 246 // Event dispatching is deferred until document.onload has fired. 247 m_deferredEvents.append(DeferredEvent(id, total, done)); 248 return; 249 } 250 dispatchDOMEvent(id, total, done); 251 } 252 253 void ApplicationCacheHost::stopLoadingInFrame(Frame* frame) 254 { 255 ASSERT(!m_applicationCache || !m_candidateApplicationCacheGroup || m_applicationCache->group() == m_candidateApplicationCacheGroup); 256 257 if (m_candidateApplicationCacheGroup) 258 m_candidateApplicationCacheGroup->stopLoadingInFrame(frame); 259 else if (m_applicationCache) 260 m_applicationCache->group()->stopLoadingInFrame(frame); 261 } 262 263 void ApplicationCacheHost::stopDeferringEvents() 264 { 265 RefPtr<DocumentLoader> protect(documentLoader()); 266 for (unsigned i = 0; i < m_deferredEvents.size(); ++i) { 267 const DeferredEvent& deferred = m_deferredEvents[i]; 268 dispatchDOMEvent(deferred.eventID, deferred.progressTotal, deferred.progressDone); 269 } 270 m_deferredEvents.clear(); 271 m_defersEvents = false; 272 } 273 274 #if ENABLE(INSPECTOR) 275 void ApplicationCacheHost::fillResourceList(ResourceInfoList* resources) 276 { 277 ApplicationCache* cache = applicationCache(); 278 if (!cache || !cache->isComplete()) 279 return; 280 281 ApplicationCache::ResourceMap::const_iterator end = cache->end(); 282 for (ApplicationCache::ResourceMap::const_iterator it = cache->begin(); it != end; ++it) { 283 RefPtr<ApplicationCacheResource> resource = it->second; 284 unsigned type = resource->type(); 285 bool isMaster = type & ApplicationCacheResource::Master; 286 bool isManifest = type & ApplicationCacheResource::Manifest; 287 bool isExplicit = type & ApplicationCacheResource::Explicit; 288 bool isForeign = type & ApplicationCacheResource::Foreign; 289 bool isFallback = type & ApplicationCacheResource::Fallback; 290 resources->append(ResourceInfo(resource->url(), isMaster, isManifest, isFallback, isForeign, isExplicit, resource->estimatedSizeInStorage())); 291 } 292 } 293 294 ApplicationCacheHost::CacheInfo ApplicationCacheHost::applicationCacheInfo() 295 { 296 ApplicationCache* cache = applicationCache(); 297 if (!cache || !cache->isComplete()) 298 return CacheInfo(KURL(), 0, 0, 0); 299 300 // FIXME: Add "Creation Time" and "Update Time" to Application Caches. 301 return CacheInfo(cache->manifestResource()->url(), 0, 0, cache->estimatedSizeInStorage()); 302 } 303 #endif 304 305 void ApplicationCacheHost::dispatchDOMEvent(EventID id, int total, int done) 306 { 307 if (m_domApplicationCache) { 308 const AtomicString& eventType = DOMApplicationCache::toEventType(id); 309 ExceptionCode ec = 0; 310 RefPtr<Event> event; 311 if (id == PROGRESS_EVENT) 312 event = ProgressEvent::create(eventType, true, done, total); 313 else 314 event = Event::create(eventType, false, false); 315 m_domApplicationCache->dispatchEvent(event, ec); 316 ASSERT(!ec); 317 } 318 } 319 320 void ApplicationCacheHost::setCandidateApplicationCacheGroup(ApplicationCacheGroup* group) 321 { 322 ASSERT(!m_applicationCache); 323 m_candidateApplicationCacheGroup = group; 324 } 325 326 void ApplicationCacheHost::setApplicationCache(PassRefPtr<ApplicationCache> applicationCache) 327 { 328 if (m_candidateApplicationCacheGroup) { 329 ASSERT(!m_applicationCache); 330 m_candidateApplicationCacheGroup = 0; 331 } 332 333 m_applicationCache = applicationCache; 334 } 335 336 bool ApplicationCacheHost::shouldLoadResourceFromApplicationCache(const ResourceRequest& request, ApplicationCacheResource*& resource) 337 { 338 ApplicationCache* cache = applicationCache(); 339 if (!cache || !cache->isComplete()) 340 return false; 341 342 // If the resource is not to be fetched using the HTTP GET mechanism or equivalent, or if its URL has a different 343 // <scheme> component than the application cache's manifest, then fetch the resource normally. 344 if (!ApplicationCache::requestIsHTTPOrHTTPSGet(request) || !equalIgnoringCase(request.url().protocol(), cache->manifestResource()->url().protocol())) 345 return false; 346 347 // If the resource's URL is an master entry, the manifest, an explicit entry, or a fallback entry 348 // in the application cache, then get the resource from the cache (instead of fetching it). 349 resource = cache->resourceForURL(request.url()); 350 351 // Resources that match fallback namespaces or online whitelist entries are fetched from the network, 352 // unless they are also cached. 353 if (!resource && (cache->allowsAllNetworkRequests() || cache->urlMatchesFallbackNamespace(request.url()) || cache->isURLInOnlineWhitelist(request.url()))) 354 return false; 355 356 // Resources that are not present in the manifest will always fail to load (at least, after the 357 // cache has been primed the first time), making the testing of offline applications simpler. 358 return true; 359 } 360 361 bool ApplicationCacheHost::getApplicationCacheFallbackResource(const ResourceRequest& request, ApplicationCacheResource*& resource, ApplicationCache* cache) 362 { 363 if (!cache) { 364 cache = applicationCache(); 365 if (!cache) 366 return false; 367 } 368 if (!cache->isComplete()) 369 return false; 370 371 // If the resource is not a HTTP/HTTPS GET, then abort 372 if (!ApplicationCache::requestIsHTTPOrHTTPSGet(request)) 373 return false; 374 375 KURL fallbackURL; 376 if (cache->isURLInOnlineWhitelist(request.url())) 377 return false; 378 if (!cache->urlMatchesFallbackNamespace(request.url(), &fallbackURL)) 379 return false; 380 381 resource = cache->resourceForURL(fallbackURL); 382 ASSERT(resource); 383 384 return true; 385 } 386 387 bool ApplicationCacheHost::scheduleLoadFallbackResourceFromApplicationCache(ResourceLoader* loader, ApplicationCache* cache) 388 { 389 if (!isApplicationCacheEnabled()) 390 return false; 391 392 ApplicationCacheResource* resource; 393 if (!getApplicationCacheFallbackResource(loader->request(), resource, cache)) 394 return false; 395 396 m_documentLoader->m_pendingSubstituteResources.set(loader, resource); 397 m_documentLoader->deliverSubstituteResourcesAfterDelay(); 398 399 loader->handle()->cancel(); 400 401 return true; 402 } 403 404 ApplicationCacheHost::Status ApplicationCacheHost::status() const 405 { 406 ApplicationCache* cache = applicationCache(); 407 if (!cache) 408 return UNCACHED; 409 410 switch (cache->group()->updateStatus()) { 411 case ApplicationCacheGroup::Checking: 412 return CHECKING; 413 case ApplicationCacheGroup::Downloading: 414 return DOWNLOADING; 415 case ApplicationCacheGroup::Idle: { 416 if (cache->group()->isObsolete()) 417 return OBSOLETE; 418 if (cache != cache->group()->newestCache()) 419 return UPDATEREADY; 420 return IDLE; 421 } 422 } 423 424 ASSERT_NOT_REACHED(); 425 return UNCACHED; 426 } 427 428 bool ApplicationCacheHost::update() 429 { 430 ApplicationCache* cache = applicationCache(); 431 if (!cache) 432 return false; 433 cache->group()->update(m_documentLoader->frame(), ApplicationCacheUpdateWithoutBrowsingContext); 434 return true; 435 } 436 437 bool ApplicationCacheHost::swapCache() 438 { 439 ApplicationCache* cache = applicationCache(); 440 if (!cache) 441 return false; 442 443 // If the group of application caches to which cache belongs has the lifecycle status obsolete, unassociate document from cache. 444 if (cache->group()->isObsolete()) { 445 cache->group()->disassociateDocumentLoader(m_documentLoader); 446 return true; 447 } 448 449 // If there is no newer cache, raise an INVALID_STATE_ERR exception. 450 ApplicationCache* newestCache = cache->group()->newestCache(); 451 if (cache == newestCache) 452 return false; 453 454 ASSERT(cache->group() == newestCache->group()); 455 setApplicationCache(newestCache); 456 457 return true; 458 } 459 460 bool ApplicationCacheHost::isApplicationCacheEnabled() 461 { 462 return m_documentLoader->frame()->settings() 463 && m_documentLoader->frame()->settings()->offlineWebApplicationCacheEnabled(); 464 } 465 466 } // namespace WebCore 467 468 #endif // ENABLE(OFFLINE_WEB_APPLICATIONS) 469