1/* 2 * Copyright (C) 2007 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 COMPUTER, 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 COMPUTER, 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 "PageCache.h" 28 29#include "ApplicationCacheHost.h" 30#include "BackForwardController.h" 31#include "MemoryCache.h" 32#include "CachedPage.h" 33#include "DOMWindow.h" 34#include "DatabaseManager.h" 35#include "DeviceMotionController.h" 36#include "DeviceOrientationController.h" 37#include "Document.h" 38#include "DocumentLoader.h" 39#include "Frame.h" 40#include "FrameLoader.h" 41#include "FrameLoaderClient.h" 42#include "FrameLoaderStateMachine.h" 43#include "FrameView.h" 44#include "HistogramSupport.h" 45#include "HistoryController.h" 46#include "HistoryItem.h" 47#include "Logging.h" 48#include "Page.h" 49#include "Settings.h" 50#include "SharedWorkerRepository.h" 51#include <wtf/CurrentTime.h> 52#include <wtf/text/CString.h> 53#include <wtf/text/StringConcatenate.h> 54 55#if ENABLE(PROXIMITY_EVENTS) 56#include "DeviceProximityController.h" 57#endif 58 59using namespace std; 60 61namespace WebCore { 62 63#if !defined(NDEBUG) 64 65#define PCLOG(...) LOG(PageCache, "%*s%s", indentLevel*4, "", makeString(__VA_ARGS__).utf8().data()) 66 67// Used in histograms, please only add at the end, and do not remove elements (renaming e.g. to "FooEnumUnused1" is fine). 68// This is because statistics may be gathered from histograms between versions over time, and re-using values causes collisions. 69enum ReasonFrameCannotBeInPageCache { 70 NoDocumentLoader = 0, 71 MainDocumentError, 72 IsErrorPage, 73 HasPlugins, 74 IsHttpsAndCacheControlled, 75 HasUnloadListener, 76 HasDatabaseHandles, 77 HasSharedWorkers, 78 NoHistoryItem, 79 QuickRedirectComing, 80 IsLoadingInAPISense, 81 IsStopping, 82 CannotSuspendActiveDOMObjects, 83 DocumentLoaderUsesApplicationCache, 84 ClientDeniesCaching, 85 NumberOfReasonsFramesCannotBeInPageCache, 86}; 87COMPILE_ASSERT(NumberOfReasonsFramesCannotBeInPageCache <= sizeof(unsigned)*8, ReasonFrameCannotBeInPageCacheDoesNotFitInBitmap); 88 89static unsigned logCanCacheFrameDecision(Frame* frame, int indentLevel) 90{ 91 PCLOG("+---"); 92 if (!frame->loader()->documentLoader()) { 93 PCLOG(" -There is no DocumentLoader object"); 94 return 1 << NoDocumentLoader; 95 } 96 97 KURL currentURL = frame->loader()->documentLoader()->url(); 98 KURL newURL = frame->loader()->provisionalDocumentLoader() ? frame->loader()->provisionalDocumentLoader()->url() : KURL(); 99 if (!newURL.isEmpty()) 100 PCLOG(" Determining if frame can be cached navigating from (", currentURL.string(), ") to (", newURL.string(), "):"); 101 else 102 PCLOG(" Determining if subframe with URL (", currentURL.string(), ") can be cached:"); 103 104 unsigned rejectReasons = 0; 105 if (!frame->loader()->documentLoader()->mainDocumentError().isNull()) { 106 PCLOG(" -Main document has an error"); 107 rejectReasons |= 1 << MainDocumentError; 108 } 109 if (frame->loader()->documentLoader()->substituteData().isValid() && frame->loader()->documentLoader()->substituteData().failingURL().isEmpty()) { 110 PCLOG(" -Frame is an error page"); 111 rejectReasons |= 1 << IsErrorPage; 112 } 113 if (frame->loader()->subframeLoader()->containsPlugins() && !frame->page()->settings()->pageCacheSupportsPlugins()) { 114 PCLOG(" -Frame contains plugins"); 115 rejectReasons |= 1 << HasPlugins; 116 } 117 if (frame->document()->url().protocolIs("https") 118 && (frame->loader()->documentLoader()->response().cacheControlContainsNoCache() 119 || frame->loader()->documentLoader()->response().cacheControlContainsNoStore())) { 120 PCLOG(" -Frame is HTTPS, and cache control prohibits caching or storing"); 121 rejectReasons |= 1 << IsHttpsAndCacheControlled; 122 } 123 if (frame->document()->domWindow() && frame->document()->domWindow()->hasEventListeners(eventNames().unloadEvent)) { 124 PCLOG(" -Frame has an unload event listener"); 125 rejectReasons |= 1 << HasUnloadListener; 126 } 127#if ENABLE(SQL_DATABASE) 128 if (DatabaseManager::manager().hasOpenDatabases(frame->document())) { 129 PCLOG(" -Frame has open database handles"); 130 rejectReasons |= 1 << HasDatabaseHandles; 131 } 132#endif 133#if ENABLE(SHARED_WORKERS) 134 if (SharedWorkerRepository::hasSharedWorkers(frame->document())) { 135 PCLOG(" -Frame has associated SharedWorkers"); 136 rejectReasons |= 1 << HasSharedWorkers; 137 } 138#endif 139 if (!frame->loader()->history()->currentItem()) { 140 PCLOG(" -No current history item"); 141 rejectReasons |= 1 << NoHistoryItem; 142 } 143 if (frame->loader()->quickRedirectComing()) { 144 PCLOG(" -Quick redirect is coming"); 145 rejectReasons |= 1 << QuickRedirectComing; 146 } 147 if (frame->loader()->documentLoader()->isLoadingInAPISense()) { 148 PCLOG(" -DocumentLoader is still loading in API sense"); 149 rejectReasons |= 1 << IsLoadingInAPISense; 150 } 151 if (frame->loader()->documentLoader()->isStopping()) { 152 PCLOG(" -DocumentLoader is in the middle of stopping"); 153 rejectReasons |= 1 << IsStopping; 154 } 155 if (!frame->document()->canSuspendActiveDOMObjects()) { 156 PCLOG(" -The document cannot suspect its active DOM Objects"); 157 rejectReasons |= 1 << CannotSuspendActiveDOMObjects; 158 } 159 if (!frame->loader()->documentLoader()->applicationCacheHost()->canCacheInPageCache()) { 160 PCLOG(" -The DocumentLoader uses an application cache"); 161 rejectReasons |= 1 << DocumentLoaderUsesApplicationCache; 162 } 163 if (!frame->loader()->client()->canCachePage()) { 164 PCLOG(" -The client says this frame cannot be cached"); 165 rejectReasons |= 1 << ClientDeniesCaching; 166 } 167 168 HistogramSupport::histogramEnumeration("PageCache.FrameCacheable", !rejectReasons, 2); 169 int reasonCount = 0; 170 for (int i = 0; i < NumberOfReasonsFramesCannotBeInPageCache; ++i) { 171 if (rejectReasons & (1 << i)) { 172 ++reasonCount; 173 HistogramSupport::histogramEnumeration("PageCache.FrameRejectReason", i, NumberOfReasonsFramesCannotBeInPageCache); 174 } 175 } 176 HistogramSupport::histogramEnumeration("PageCache.FrameRejectReasonCount", reasonCount, 1 + NumberOfReasonsFramesCannotBeInPageCache); 177 178 for (Frame* child = frame->tree()->firstChild(); child; child = child->tree()->nextSibling()) 179 rejectReasons |= logCanCacheFrameDecision(child, indentLevel + 1); 180 181 PCLOG(rejectReasons ? " Frame CANNOT be cached" : " Frame CAN be cached"); 182 PCLOG("+---"); 183 184 return rejectReasons; 185} 186 187// Used in histograms, please only add at the end, and do not remove elements (renaming e.g. to "FooEnumUnused1" is fine). 188// This is because statistics may be gathered from histograms between versions over time, and re-using values causes collisions. 189enum ReasonPageCannotBeInPageCache { 190 FrameCannotBeInPageCache = 0, 191 DisabledBackForwardList, 192 DisabledPageCache, 193 UsesDeviceMotion, 194 UsesDeviceOrientation, 195 IsReload, 196 IsReloadFromOrigin, 197 IsSameLoad, 198 NumberOfReasonsPagesCannotBeInPageCache, 199}; 200COMPILE_ASSERT(NumberOfReasonsPagesCannotBeInPageCache <= sizeof(unsigned)*8, ReasonPageCannotBeInPageCacheDoesNotFitInBitmap); 201 202static void logCanCachePageDecision(Page* page) 203{ 204 // Only bother logging for main frames that have actually loaded and have content. 205 if (page->mainFrame()->loader()->stateMachine()->creatingInitialEmptyDocument()) 206 return; 207 KURL currentURL = page->mainFrame()->loader()->documentLoader() ? page->mainFrame()->loader()->documentLoader()->url() : KURL(); 208 if (currentURL.isEmpty()) 209 return; 210 211 int indentLevel = 0; 212 PCLOG("--------\n Determining if page can be cached:"); 213 214 unsigned rejectReasons = 0; 215 unsigned frameRejectReasons = logCanCacheFrameDecision(page->mainFrame(), indentLevel+1); 216 if (frameRejectReasons) 217 rejectReasons |= 1 << FrameCannotBeInPageCache; 218 219 if (!page->backForward()->isActive()) { 220 PCLOG(" -The back/forward list is disabled or has 0 capacity"); 221 rejectReasons |= 1 << DisabledBackForwardList; 222 } 223 if (!page->settings()->usesPageCache()) { 224 PCLOG(" -Page settings says b/f cache disabled"); 225 rejectReasons |= 1 << DisabledPageCache; 226 } 227#if ENABLE(DEVICE_ORIENTATION) 228 if (DeviceMotionController::isActiveAt(page)) { 229 PCLOG(" -Page is using DeviceMotion"); 230 rejectReasons |= 1 << UsesDeviceMotion; 231 } 232 if (DeviceOrientationController::isActiveAt(page)) { 233 PCLOG(" -Page is using DeviceOrientation"); 234 rejectReasons |= 1 << UsesDeviceOrientation; 235 } 236#endif 237#if ENABLE(PROXIMITY_EVENTS) 238 if (DeviceProximityController::isActiveAt(page)) { 239 PCLOG(" -Page is using DeviceProximity"); 240 rejectReasons |= 1 << UsesDeviceMotion; 241 } 242#endif 243 FrameLoadType loadType = page->mainFrame()->loader()->loadType(); 244 if (loadType == FrameLoadTypeReload) { 245 PCLOG(" -Load type is: Reload"); 246 rejectReasons |= 1 << IsReload; 247 } 248 if (loadType == FrameLoadTypeReloadFromOrigin) { 249 PCLOG(" -Load type is: Reload from origin"); 250 rejectReasons |= 1 << IsReloadFromOrigin; 251 } 252 if (loadType == FrameLoadTypeSame) { 253 PCLOG(" -Load type is: Same"); 254 rejectReasons |= 1 << IsSameLoad; 255 } 256 257 PCLOG(rejectReasons ? " Page CANNOT be cached\n--------" : " Page CAN be cached\n--------"); 258 259 HistogramSupport::histogramEnumeration("PageCache.PageCacheable", !rejectReasons, 2); 260 int reasonCount = 0; 261 for (int i = 0; i < NumberOfReasonsPagesCannotBeInPageCache; ++i) { 262 if (rejectReasons & (1 << i)) { 263 ++reasonCount; 264 HistogramSupport::histogramEnumeration("PageCache.PageRejectReason", i, NumberOfReasonsPagesCannotBeInPageCache); 265 } 266 } 267 HistogramSupport::histogramEnumeration("PageCache.PageRejectReasonCount", reasonCount, 1 + NumberOfReasonsPagesCannotBeInPageCache); 268 const bool settingsDisabledPageCache = rejectReasons & (1 << DisabledPageCache); 269 HistogramSupport::histogramEnumeration("PageCache.PageRejectReasonCountExcludingSettings", reasonCount - settingsDisabledPageCache, NumberOfReasonsPagesCannotBeInPageCache); 270 271 // Report also on the frame reasons by page; this is distinct from the per frame statistics since it coalesces the 272 // causes from all subframes together. 273 HistogramSupport::histogramEnumeration("PageCache.FrameCacheableByPage", !frameRejectReasons, 2); 274 int frameReasonCount = 0; 275 for (int i = 0; i <= NumberOfReasonsFramesCannotBeInPageCache; ++i) { 276 if (frameRejectReasons & (1 << i)) { 277 ++frameReasonCount; 278 HistogramSupport::histogramEnumeration("PageCache.FrameRejectReasonByPage", i, NumberOfReasonsFramesCannotBeInPageCache); 279 } 280 } 281 282 HistogramSupport::histogramEnumeration("PageCache.FrameRejectReasonCountByPage", frameReasonCount, 1 + NumberOfReasonsFramesCannotBeInPageCache); 283} 284 285#endif // !defined(NDEBUG) 286 287PageCache* pageCache() 288{ 289 static PageCache* staticPageCache = new PageCache; 290 return staticPageCache; 291} 292 293PageCache::PageCache() 294 : m_capacity(0) 295 , m_size(0) 296 , m_head(0) 297 , m_tail(0) 298#if USE(ACCELERATED_COMPOSITING) 299 , m_shouldClearBackingStores(false) 300#endif 301{ 302} 303 304bool PageCache::canCachePageContainingThisFrame(Frame* frame) 305{ 306 for (Frame* child = frame->tree()->firstChild(); child; child = child->tree()->nextSibling()) { 307 if (!canCachePageContainingThisFrame(child)) 308 return false; 309 } 310 311 FrameLoader* frameLoader = frame->loader(); 312 DocumentLoader* documentLoader = frameLoader->documentLoader(); 313 Document* document = frame->document(); 314 315 return documentLoader 316 && documentLoader->mainDocumentError().isNull() 317 // Do not cache error pages (these can be recognized as pages with substitute data or unreachable URLs). 318 && !(documentLoader->substituteData().isValid() && !documentLoader->substituteData().failingURL().isEmpty()) 319 && (!frameLoader->subframeLoader()->containsPlugins() || frame->page()->settings()->pageCacheSupportsPlugins()) 320 && (!document->url().protocolIs("https") || (!documentLoader->response().cacheControlContainsNoCache() && !documentLoader->response().cacheControlContainsNoStore())) 321 && (!document->domWindow() || !document->domWindow()->hasEventListeners(eventNames().unloadEvent)) 322#if ENABLE(SQL_DATABASE) 323 && !DatabaseManager::manager().hasOpenDatabases(document) 324#endif 325#if ENABLE(SHARED_WORKERS) 326 && !SharedWorkerRepository::hasSharedWorkers(document) 327#endif 328 && frameLoader->history()->currentItem() 329 && !frameLoader->quickRedirectComing() 330 && !documentLoader->isLoadingInAPISense() 331 && !documentLoader->isStopping() 332 && document->canSuspendActiveDOMObjects() 333 // FIXME: We should investigating caching frames that have an associated 334 // application cache. <rdar://problem/5917899> tracks that work. 335 && documentLoader->applicationCacheHost()->canCacheInPageCache() 336 && frameLoader->client()->canCachePage(); 337} 338 339bool PageCache::canCache(Page* page) const 340{ 341 if (!page) 342 return false; 343 344#if !defined(NDEBUG) 345 logCanCachePageDecision(page); 346#endif 347 348 // Cache the page, if possible. 349 // Don't write to the cache if in the middle of a redirect, since we will want to 350 // store the final page we end up on. 351 // No point writing to the cache on a reload or loadSame, since we will just write 352 // over it again when we leave that page. 353 FrameLoadType loadType = page->mainFrame()->loader()->loadType(); 354 355 return m_capacity > 0 356 && canCachePageContainingThisFrame(page->mainFrame()) 357 && page->backForward()->isActive() 358 && page->settings()->usesPageCache() 359#if ENABLE(DEVICE_ORIENTATION) 360 && !DeviceMotionController::isActiveAt(page) 361 && !DeviceOrientationController::isActiveAt(page) 362#endif 363#if ENABLE(PROXIMITY_EVENTS) 364 && !DeviceProximityController::isActiveAt(page) 365#endif 366 && (loadType == FrameLoadTypeStandard 367 || loadType == FrameLoadTypeBack 368 || loadType == FrameLoadTypeForward 369 || loadType == FrameLoadTypeIndexedBackForward); 370} 371 372void PageCache::setCapacity(int capacity) 373{ 374 ASSERT(capacity >= 0); 375 m_capacity = max(capacity, 0); 376 377 prune(); 378} 379 380int PageCache::frameCount() const 381{ 382 int frameCount = 0; 383 for (HistoryItem* current = m_head; current; current = current->m_next) { 384 ++frameCount; 385 ASSERT(current->m_cachedPage); 386 frameCount += current->m_cachedPage ? current->m_cachedPage->cachedMainFrame()->descendantFrameCount() : 0; 387 } 388 389 return frameCount; 390} 391 392void PageCache::markPagesForVistedLinkStyleRecalc() 393{ 394 for (HistoryItem* current = m_head; current; current = current->m_next) { 395 if (current->m_cachedPage) 396 current->m_cachedPage->markForVistedLinkStyleRecalc(); 397 } 398} 399 400void PageCache::markPagesForFullStyleRecalc(Page* page) 401{ 402 Frame* mainFrame = page->mainFrame(); 403 404 for (HistoryItem* current = m_head; current; current = current->m_next) { 405 CachedPage* cachedPage = current->m_cachedPage.get(); 406 if (cachedPage && cachedPage->cachedMainFrame()->view()->frame() == mainFrame) 407 cachedPage->markForFullStyleRecalc(); 408 } 409} 410 411 412#if USE(ACCELERATED_COMPOSITING) 413void PageCache::markPagesForDeviceScaleChanged(Page* page) 414{ 415 Frame* mainFrame = page->mainFrame(); 416 417 for (HistoryItem* current = m_head; current; current = current->m_next) { 418 CachedPage* cachedPage = current->m_cachedPage.get(); 419 if (cachedPage && cachedPage->cachedMainFrame()->view()->frame() == mainFrame) 420 cachedPage->markForDeviceScaleChanged(); 421 } 422} 423#endif 424 425#if ENABLE(VIDEO_TRACK) 426void PageCache::markPagesForCaptionPreferencesChanged() 427{ 428 for (HistoryItem* current = m_head; current; current = current->m_next) { 429 if (current->m_cachedPage) 430 current->m_cachedPage->markForCaptionPreferencesChanged(); 431 } 432} 433#endif 434 435void PageCache::add(PassRefPtr<HistoryItem> prpItem, Page* page) 436{ 437 ASSERT(prpItem); 438 ASSERT(page); 439 ASSERT(canCache(page)); 440 441 HistoryItem* item = prpItem.leakRef(); // Balanced in remove(). 442 443 // Remove stale cache entry if necessary. 444 if (item->m_cachedPage) 445 remove(item); 446 447 item->m_cachedPage = CachedPage::create(page); 448 addToLRUList(item); 449 ++m_size; 450 451 prune(); 452} 453 454CachedPage* PageCache::get(HistoryItem* item) 455{ 456 if (!item) 457 return 0; 458 459 if (CachedPage* cachedPage = item->m_cachedPage.get()) { 460 if (!cachedPage->hasExpired()) 461 return cachedPage; 462 463 LOG(PageCache, "Not restoring page for %s from back/forward cache because cache entry has expired", item->url().string().ascii().data()); 464 pageCache()->remove(item); 465 } 466 return 0; 467} 468 469void PageCache::remove(HistoryItem* item) 470{ 471 // Safely ignore attempts to remove items not in the cache. 472 if (!item || !item->m_cachedPage) 473 return; 474 475 item->m_cachedPage.clear(); 476 removeFromLRUList(item); 477 --m_size; 478 479 item->deref(); // Balanced in add(). 480} 481 482void PageCache::prune() 483{ 484 while (m_size > m_capacity) { 485 ASSERT(m_tail && m_tail->m_cachedPage); 486 remove(m_tail); 487 } 488} 489 490void PageCache::addToLRUList(HistoryItem* item) 491{ 492 item->m_next = m_head; 493 item->m_prev = 0; 494 495 if (m_head) { 496 ASSERT(m_tail); 497 m_head->m_prev = item; 498 } else { 499 ASSERT(!m_tail); 500 m_tail = item; 501 } 502 503 m_head = item; 504} 505 506void PageCache::removeFromLRUList(HistoryItem* item) 507{ 508 if (!item->m_next) { 509 ASSERT(item == m_tail); 510 m_tail = item->m_prev; 511 } else { 512 ASSERT(item != m_tail); 513 item->m_next->m_prev = item->m_prev; 514 } 515 516 if (!item->m_prev) { 517 ASSERT(item == m_head); 518 m_head = item->m_next; 519 } else { 520 ASSERT(item != m_head); 521 item->m_prev->m_next = item->m_next; 522 } 523} 524 525} // namespace WebCore 526