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