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