Cached Page and Frame don't need to be ref-counted.
[WebKit-https.git] / Source / WebCore / history / PageCache.cpp
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
59 using namespace std;
60
61 namespace 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.
69 enum 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 };
87 COMPILE_ASSERT(NumberOfReasonsFramesCannotBeInPageCache <= sizeof(unsigned)*8, ReasonFrameCannotBeInPageCacheDoesNotFitInBitmap);
88
89 static 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.
189 enum ReasonPageCannotBeInPageCache {
190     FrameCannotBeInPageCache = 0,
191     DisabledBackForwardList,
192     DisabledPageCache,
193     UsesDeviceMotion,
194     UsesDeviceOrientation,
195     IsReload,
196     IsReloadFromOrigin,
197     IsSameLoad,
198     NumberOfReasonsPagesCannotBeInPageCache,
199 };
200 COMPILE_ASSERT(NumberOfReasonsPagesCannotBeInPageCache <= sizeof(unsigned)*8, ReasonPageCannotBeInPageCacheDoesNotFitInBitmap);
201
202 static 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
287 PageCache* pageCache()
288 {
289     static PageCache* staticPageCache = new PageCache;
290     return staticPageCache;
291 }
292
293 PageCache::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     
304 bool 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     
339 bool 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
372 void PageCache::setCapacity(int capacity)
373 {
374     ASSERT(capacity >= 0);
375     m_capacity = max(capacity, 0);
376
377     prune();
378 }
379
380 int 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
392 void PageCache::markPagesForVistedLinkStyleRecalc()
393 {
394     for (HistoryItem* current = m_head; current; current = current->m_next)
395         current->m_cachedPage->markForVistedLinkStyleRecalc();
396 }
397
398 void PageCache::markPagesForFullStyleRecalc(Page* page)
399 {
400     for (HistoryItem* current = m_head; current; current = current->m_next) {
401         CachedPage* cachedPage = current->m_cachedPage.get();
402         if (page->frameIsMainFrame(&cachedPage->cachedMainFrame()->view()->frame()))
403             cachedPage->markForFullStyleRecalc();
404     }
405 }
406
407
408 #if USE(ACCELERATED_COMPOSITING)
409 void PageCache::markPagesForDeviceScaleChanged(Page* page)
410 {
411     for (HistoryItem* current = m_head; current; current = current->m_next) {
412         CachedPage* cachedPage = current->m_cachedPage.get();
413         if (page->frameIsMainFrame(&cachedPage->cachedMainFrame()->view()->frame()))
414             cachedPage->markForDeviceScaleChanged();
415     }
416 }
417 #endif
418
419 #if ENABLE(VIDEO_TRACK)
420 void PageCache::markPagesForCaptionPreferencesChanged()
421 {
422     for (HistoryItem* current = m_head; current; current = current->m_next)
423         current->m_cachedPage->markForCaptionPreferencesChanged();
424 }
425 #endif
426
427 void PageCache::add(PassRefPtr<HistoryItem> prpItem, Page& page)
428 {
429     ASSERT(prpItem);
430     ASSERT(canCache(&page));
431     
432     HistoryItem* item = prpItem.leakRef(); // Balanced in remove().
433
434     // Remove stale cache entry if necessary.
435     if (item->m_cachedPage)
436         remove(item);
437
438     item->m_cachedPage = CachedPage::create(page);
439     addToLRUList(item);
440     ++m_size;
441     
442     prune();
443 }
444
445 PassOwnPtr<CachedPage> PageCache::take(HistoryItem* item)
446 {
447     if (!item)
448         return nullptr;
449
450     OwnPtr<CachedPage> cachedPage = item->m_cachedPage.release();
451
452     removeFromLRUList(item);
453     --m_size;
454
455     item->deref(); // Balanced in add().
456
457     if (!cachedPage)
458         return nullptr;
459
460     if (cachedPage->hasExpired()) {
461         LOG(PageCache, "Not restoring page for %s from back/forward cache because cache entry has expired", item->url().string().ascii().data());
462         return nullptr;
463     }
464
465     return cachedPage.release();
466 }
467
468 CachedPage* PageCache::get(HistoryItem* item)
469 {
470     if (!item)
471         return 0;
472
473     if (CachedPage* cachedPage = item->m_cachedPage.get()) {
474         if (!cachedPage->hasExpired())
475             return cachedPage;
476         
477         LOG(PageCache, "Not restoring page for %s from back/forward cache because cache entry has expired", item->url().string().ascii().data());
478         pageCache()->remove(item);
479     }
480     return 0;
481 }
482
483 void PageCache::remove(HistoryItem* item)
484 {
485     // Safely ignore attempts to remove items not in the cache.
486     if (!item || !item->m_cachedPage)
487         return;
488
489     item->m_cachedPage.clear();
490     removeFromLRUList(item);
491     --m_size;
492
493     item->deref(); // Balanced in add().
494 }
495
496 void PageCache::prune()
497 {
498     while (m_size > m_capacity) {
499         ASSERT(m_tail && m_tail->m_cachedPage);
500         remove(m_tail);
501     }
502 }
503
504 void PageCache::addToLRUList(HistoryItem* item)
505 {
506     item->m_next = m_head;
507     item->m_prev = 0;
508
509     if (m_head) {
510         ASSERT(m_tail);
511         m_head->m_prev = item;
512     } else {
513         ASSERT(!m_tail);
514         m_tail = item;
515     }
516
517     m_head = item;
518 }
519
520 void PageCache::removeFromLRUList(HistoryItem* item)
521 {
522     if (!item->m_next) {
523         ASSERT(item == m_tail);
524         m_tail = item->m_prev;
525     } else {
526         ASSERT(item != m_tail);
527         item->m_next->m_prev = item->m_prev;
528     }
529
530     if (!item->m_prev) {
531         ASSERT(item == m_head);
532         m_head = item->m_next;
533     } else {
534         ASSERT(item != m_head);
535         item->m_prev->m_next = item->m_next;
536     }
537 }
538
539 } // namespace WebCore