11c1615c8ad4d2df3d6882498a9eefe9bf5ed342
[WebKit-https.git] / Source / WebCore / history / PageCache.cpp
1 /*
2  * Copyright (C) 2007, 2014, 2015 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 "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 "DiagnosticLoggingClient.h"
38 #include "DiagnosticLoggingKeys.h"
39 #include "Document.h"
40 #include "DocumentLoader.h"
41 #include "FrameLoader.h"
42 #include "FrameLoaderClient.h"
43 #include "FrameLoaderStateMachine.h"
44 #include "FrameView.h"
45 #include "HistoryController.h"
46 #include "Logging.h"
47 #include "MainFrame.h"
48 #include "MemoryPressureHandler.h"
49 #include "Page.h"
50 #include "Settings.h"
51 #include "SubframeLoader.h"
52 #include <wtf/CurrentTime.h>
53 #include <wtf/NeverDestroyed.h>
54 #include <wtf/TemporaryChange.h>
55 #include <wtf/text/CString.h>
56 #include <wtf/text/StringConcatenate.h>
57
58 #if ENABLE(PROXIMITY_EVENTS)
59 #include "DeviceProximityController.h"
60 #endif
61
62 #if PLATFORM(IOS)
63 #include "MemoryPressureHandler.h"
64 #endif
65
66 namespace WebCore {
67
68 #if !defined(NDEBUG)
69
70 #define PCLOG(...) LOG(PageCache, "%*s%s", indentLevel*4, "", makeString(__VA_ARGS__).utf8().data())
71
72 #else
73
74 #define PCLOG(...) ((void)0)
75
76 #endif // !defined(NDEBUG)
77     
78 // Used in histograms, please only add at the end, and do not remove elements (renaming e.g. to "FooEnumUnused1" is fine).
79 // This is because statistics may be gathered from histograms between versions over time, and re-using values causes collisions.
80 enum ReasonFrameCannotBeInPageCache {
81     NoDocumentLoader = 0,
82     MainDocumentError,
83     IsErrorPage,
84     HasPlugins,
85     IsHttpsAndCacheControlled,
86     HasDatabaseHandles,
87     HasSharedWorkers, // FIXME: Remove.
88     NoHistoryItem,
89     QuickRedirectComing,
90     IsLoadingInAPISense,
91     IsStopping,
92     CannotSuspendActiveDOMObjects,
93     DocumentLoaderUsesApplicationCache,
94     ClientDeniesCaching,
95     NumberOfReasonsFramesCannotBeInPageCache,
96 };
97 COMPILE_ASSERT(NumberOfReasonsFramesCannotBeInPageCache <= sizeof(unsigned)*8, ReasonFrameCannotBeInPageCacheDoesNotFitInBitmap);
98
99 static inline void logPageCacheFailureDiagnosticMessage(DiagnosticLoggingClient& client, const String& reason)
100 {
101     client.logDiagnosticMessageWithValue(DiagnosticLoggingKeys::pageCacheKey(), DiagnosticLoggingKeys::failureKey(), reason);
102 }
103
104 static inline void logPageCacheFailureDiagnosticMessage(Page* page, const String& reason)
105 {
106     if (!page)
107         return;
108
109     logPageCacheFailureDiagnosticMessage(page->mainFrame().diagnosticLoggingClient(), reason);
110 }
111
112 static unsigned logCanCacheFrameDecision(Frame& frame, DiagnosticLoggingClient& diagnosticLoggingClient, unsigned indentLevel)
113 {
114     PCLOG("+---");
115     if (!frame.loader().documentLoader()) {
116         PCLOG("   -There is no DocumentLoader object");
117         logPageCacheFailureDiagnosticMessage(diagnosticLoggingClient, DiagnosticLoggingKeys::noDocumentLoaderKey());
118         return 1 << NoDocumentLoader;
119     }
120
121     URL currentURL = frame.loader().documentLoader()->url();
122     URL newURL = frame.loader().provisionalDocumentLoader() ? frame.loader().provisionalDocumentLoader()->url() : URL();
123     if (!newURL.isEmpty())
124         PCLOG(" Determining if frame can be cached navigating from (", currentURL.string(), ") to (", newURL.string(), "):");
125     else
126         PCLOG(" Determining if subframe with URL (", currentURL.string(), ") can be cached:");
127      
128     unsigned rejectReasons = 0;
129     if (!frame.loader().documentLoader()->mainDocumentError().isNull()) {
130         PCLOG("   -Main document has an error");
131         logPageCacheFailureDiagnosticMessage(diagnosticLoggingClient, DiagnosticLoggingKeys::mainDocumentErrorKey());
132 #if !PLATFORM(IOS)
133         rejectReasons |= 1 << MainDocumentError;
134 #else
135         if (frame.loader().documentLoader()->mainDocumentError().isCancellation() && frame.loader().documentLoader()->subresourceLoadersArePageCacheAcceptable())
136             PCLOG("    -But, it was a cancellation and all loaders during the cancel were loading images.");
137         else
138             rejectReasons |= 1 << MainDocumentError;
139 #endif
140     }
141     if (frame.loader().documentLoader()->substituteData().isValid() && frame.loader().documentLoader()->substituteData().failingURL().isEmpty()) {
142         PCLOG("   -Frame is an error page");
143         logPageCacheFailureDiagnosticMessage(diagnosticLoggingClient, DiagnosticLoggingKeys::isErrorPageKey());
144         rejectReasons |= 1 << IsErrorPage;
145     }
146     if (frame.loader().subframeLoader().containsPlugins() && !frame.page()->settings().pageCacheSupportsPlugins()) {
147         PCLOG("   -Frame contains plugins");
148         logPageCacheFailureDiagnosticMessage(diagnosticLoggingClient, DiagnosticLoggingKeys::hasPluginsKey());
149         rejectReasons |= 1 << HasPlugins;
150     }
151     if (frame.isMainFrame() && frame.document()->url().protocolIs("https") && frame.loader().documentLoader()->response().cacheControlContainsNoStore()) {
152         PCLOG("   -Frame is HTTPS, and cache control prohibits storing");
153         logPageCacheFailureDiagnosticMessage(diagnosticLoggingClient, DiagnosticLoggingKeys::httpsNoStoreKey());
154         rejectReasons |= 1 << IsHttpsAndCacheControlled;
155     }
156     if (DatabaseManager::manager().hasOpenDatabases(frame.document())) {
157         PCLOG("   -Frame has open database handles");
158         logPageCacheFailureDiagnosticMessage(diagnosticLoggingClient, DiagnosticLoggingKeys::hasOpenDatabasesKey());
159         rejectReasons |= 1 << HasDatabaseHandles;
160     }
161     if (!frame.loader().history().currentItem()) {
162         PCLOG("   -No current history item");
163         logPageCacheFailureDiagnosticMessage(diagnosticLoggingClient, DiagnosticLoggingKeys::noCurrentHistoryItemKey());
164         rejectReasons |= 1 << NoHistoryItem;
165     }
166     if (frame.loader().quickRedirectComing()) {
167         PCLOG("   -Quick redirect is coming");
168         logPageCacheFailureDiagnosticMessage(diagnosticLoggingClient, DiagnosticLoggingKeys::quirkRedirectComingKey());
169         rejectReasons |= 1 << QuickRedirectComing;
170     }
171     if (frame.loader().documentLoader()->isLoadingInAPISense()) {
172         PCLOG("   -DocumentLoader is still loading in API sense");
173         logPageCacheFailureDiagnosticMessage(diagnosticLoggingClient, DiagnosticLoggingKeys::loadingAPISenseKey());
174         rejectReasons |= 1 << IsLoadingInAPISense;
175     }
176     if (frame.loader().documentLoader()->isStopping()) {
177         PCLOG("   -DocumentLoader is in the middle of stopping");
178         logPageCacheFailureDiagnosticMessage(diagnosticLoggingClient, DiagnosticLoggingKeys::documentLoaderStoppingKey());
179         rejectReasons |= 1 << IsStopping;
180     }
181
182     Vector<ActiveDOMObject*> unsuspendableObjects;
183     if (!frame.document()->canSuspendActiveDOMObjects(&unsuspendableObjects)) {
184         PCLOG("   -The document cannot suspend its active DOM Objects");
185         for (auto* activeDOMObject : unsuspendableObjects) {
186             PCLOG("    - Unsuspendable: ", activeDOMObject->activeDOMObjectName());
187             UNUSED_PARAM(activeDOMObject);
188         }
189         logPageCacheFailureDiagnosticMessage(diagnosticLoggingClient, DiagnosticLoggingKeys::cannotSuspendActiveDOMObjectsKey());
190         rejectReasons |= 1 << CannotSuspendActiveDOMObjects;
191     }
192     if (!frame.loader().documentLoader()->applicationCacheHost()->canCacheInPageCache()) {
193         PCLOG("   -The DocumentLoader uses an application cache");
194         logPageCacheFailureDiagnosticMessage(diagnosticLoggingClient, DiagnosticLoggingKeys::applicationCacheKey());
195         rejectReasons |= 1 << DocumentLoaderUsesApplicationCache;
196     }
197     if (!frame.loader().client().canCachePage()) {
198         PCLOG("   -The client says this frame cannot be cached");
199         logPageCacheFailureDiagnosticMessage(diagnosticLoggingClient, DiagnosticLoggingKeys::deniedByClientKey());
200         rejectReasons |= 1 << ClientDeniesCaching;
201     }
202
203     for (Frame* child = frame.tree().firstChild(); child; child = child->tree().nextSibling())
204         rejectReasons |= logCanCacheFrameDecision(*child, diagnosticLoggingClient, indentLevel + 1);
205     
206     PCLOG(rejectReasons ? " Frame CANNOT be cached" : " Frame CAN be cached");
207     PCLOG("+---");
208     
209     return rejectReasons;
210 }
211
212 // Used in histograms, please only add at the end, and do not remove elements (renaming e.g. to "FooEnumUnused1" is fine).
213 // This is because statistics may be gathered from histograms between versions over time, and re-using values causes collisions.
214 enum ReasonPageCannotBeInPageCache {
215     FrameCannotBeInPageCache = 0,
216     DisabledBackForwardList,
217     DisabledPageCache,
218     UsesDeviceMotion,
219     UsesDeviceOrientation,
220     IsReload,
221     IsReloadFromOrigin,
222     IsSameLoad,
223     NumberOfReasonsPagesCannotBeInPageCache,
224 };
225 COMPILE_ASSERT(NumberOfReasonsPagesCannotBeInPageCache <= sizeof(unsigned)*8, ReasonPageCannotBeInPageCacheDoesNotFitInBitmap);
226
227 static void logCanCachePageDecision(Page& page)
228 {
229     // Only bother logging for main frames that have actually loaded and have content.
230     if (page.mainFrame().loader().stateMachine().creatingInitialEmptyDocument())
231         return;
232     URL currentURL = page.mainFrame().loader().documentLoader() ? page.mainFrame().loader().documentLoader()->url() : URL();
233     if (currentURL.isEmpty())
234         return;
235     
236     unsigned indentLevel = 0;
237     PCLOG("--------\n Determining if page can be cached:");
238     
239     unsigned rejectReasons = 0;
240     MainFrame& mainFrame = page.mainFrame();
241     DiagnosticLoggingClient& diagnosticLoggingClient = mainFrame.diagnosticLoggingClient();
242     unsigned frameRejectReasons = logCanCacheFrameDecision(mainFrame, diagnosticLoggingClient, indentLevel + 1);
243     if (frameRejectReasons)
244         rejectReasons |= 1 << FrameCannotBeInPageCache;
245     
246     if (!page.settings().usesPageCache()) {
247         PCLOG("   -Page settings says b/f cache disabled");
248         rejectReasons |= 1 << DisabledPageCache;
249     }
250 #if ENABLE(DEVICE_ORIENTATION) && !PLATFORM(IOS)
251     if (DeviceMotionController::isActiveAt(page)) {
252         PCLOG("   -Page is using DeviceMotion");
253         logPageCacheFailureDiagnosticMessage(diagnosticLoggingClient, DiagnosticLoggingKeys::deviceMotionKey());
254         rejectReasons |= 1 << UsesDeviceMotion;
255     }
256     if (DeviceOrientationController::isActiveAt(page)) {
257         PCLOG("   -Page is using DeviceOrientation");
258         logPageCacheFailureDiagnosticMessage(diagnosticLoggingClient, DiagnosticLoggingKeys::deviceOrientationKey());
259         rejectReasons |= 1 << UsesDeviceOrientation;
260     }
261 #endif
262 #if ENABLE(PROXIMITY_EVENTS)
263     if (DeviceProximityController::isActiveAt(page)) {
264         PCLOG("   -Page is using DeviceProximity");
265         logPageCacheFailureDiagnosticMessage(diagnosticLoggingClient, deviceProximityKey);
266         rejectReasons |= 1 << UsesDeviceMotion;
267     }
268 #endif
269     FrameLoadType loadType = page.mainFrame().loader().loadType();
270     if (loadType == FrameLoadType::Reload) {
271         PCLOG("   -Load type is: Reload");
272         logPageCacheFailureDiagnosticMessage(diagnosticLoggingClient, DiagnosticLoggingKeys::reloadKey());
273         rejectReasons |= 1 << IsReload;
274     }
275     if (loadType == FrameLoadType::ReloadFromOrigin) {
276         PCLOG("   -Load type is: Reload from origin");
277         logPageCacheFailureDiagnosticMessage(diagnosticLoggingClient, DiagnosticLoggingKeys::reloadFromOriginKey());
278         rejectReasons |= 1 << IsReloadFromOrigin;
279     }
280     if (loadType == FrameLoadType::Same) {
281         PCLOG("   -Load type is: Same");
282         logPageCacheFailureDiagnosticMessage(diagnosticLoggingClient, DiagnosticLoggingKeys::sameLoadKey());
283         rejectReasons |= 1 << IsSameLoad;
284     }
285     
286     if (rejectReasons)
287         PCLOG(" Page CANNOT be cached\n--------");
288     else
289         PCLOG(" Page CAN be cached\n--------");
290
291     diagnosticLoggingClient.logDiagnosticMessageWithResult(DiagnosticLoggingKeys::pageCacheKey(), emptyString(), rejectReasons ? DiagnosticLoggingResultFail : DiagnosticLoggingResultPass);
292 }
293
294 PageCache& PageCache::singleton()
295 {
296     static NeverDestroyed<PageCache> globalPageCache;
297     return globalPageCache;
298 }
299     
300 bool PageCache::canCachePageContainingThisFrame(Frame& frame)
301 {
302     for (Frame* child = frame.tree().firstChild(); child; child = child->tree().nextSibling()) {
303         if (!canCachePageContainingThisFrame(*child))
304             return false;
305     }
306     
307     FrameLoader& frameLoader = frame.loader();
308     DocumentLoader* documentLoader = frameLoader.documentLoader();
309     Document* document = frame.document();
310     
311     return documentLoader
312 #if !PLATFORM(IOS)
313         && documentLoader->mainDocumentError().isNull()
314 #else
315         && (documentLoader->mainDocumentError().isNull() || (documentLoader->mainDocumentError().isCancellation() && documentLoader->subresourceLoadersArePageCacheAcceptable()))
316 #endif
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         && !(frame.isMainFrame() && document->url().protocolIs("https") && documentLoader->response().cacheControlContainsNoStore())
321         && !DatabaseManager::manager().hasOpenDatabases(document)
322         && frameLoader.history().currentItem()
323         && !frameLoader.quickRedirectComing()
324         && !documentLoader->isLoadingInAPISense()
325         && !documentLoader->isStopping()
326         && document->canSuspendActiveDOMObjects()
327         // FIXME: We should investigating caching frames that have an associated
328         // application cache. <rdar://problem/5917899> tracks that work.
329         && documentLoader->applicationCacheHost()->canCacheInPageCache()
330         && frameLoader.client().canCachePage();
331 }
332     
333 bool PageCache::canCache(Page* page) const
334 {
335     if (!page)
336         return false;
337     
338     logCanCachePageDecision(*page);
339
340     if (memoryPressureHandler().isUnderMemoryPressure())
341         return false;
342
343     // Cache the page, if possible.
344     // Don't write to the cache if in the middle of a redirect, since we will want to
345     // store the final page we end up on.
346     // No point writing to the cache on a reload or loadSame, since we will just write
347     // over it again when we leave that page.
348     FrameLoadType loadType = page->mainFrame().loader().loadType();
349     
350     return m_maxSize > 0
351         && canCachePageContainingThisFrame(page->mainFrame())
352         && page->settings().usesPageCache()
353 #if ENABLE(DEVICE_ORIENTATION) && !PLATFORM(IOS)
354         && !DeviceMotionController::isActiveAt(page)
355         && !DeviceOrientationController::isActiveAt(page)
356 #endif
357 #if ENABLE(PROXIMITY_EVENTS)
358         && !DeviceProximityController::isActiveAt(page)
359 #endif
360         && (loadType == FrameLoadType::Standard
361             || loadType == FrameLoadType::Back
362             || loadType == FrameLoadType::Forward
363             || loadType == FrameLoadType::IndexedBackForward);
364 }
365
366 void PageCache::pruneToSizeNow(unsigned size, PruningReason pruningReason)
367 {
368     TemporaryChange<unsigned>(m_maxSize, size);
369     prune(pruningReason);
370 }
371
372 void PageCache::setMaxSize(unsigned maxSize)
373 {
374     m_maxSize = maxSize;
375     prune(PruningReason::None);
376 }
377
378 unsigned PageCache::frameCount() const
379 {
380     unsigned frameCount = m_items.size();
381     for (auto& item : m_items) {
382         ASSERT(item->m_cachedPage);
383         frameCount += item->m_cachedPage->cachedMainFrame()->descendantFrameCount();
384     }
385     
386     return frameCount;
387 }
388
389 void PageCache::markPagesForVisitedLinkStyleRecalc()
390 {
391     for (auto& item : m_items) {
392         ASSERT(item->m_cachedPage);
393         item->m_cachedPage->markForVisitedLinkStyleRecalc();
394     }
395 }
396
397 void PageCache::markPagesForFullStyleRecalc(Page& page)
398 {
399     for (auto& item : m_items) {
400         CachedPage& cachedPage = *item->m_cachedPage;
401         if (&page.mainFrame() == &cachedPage.cachedMainFrame()->view()->frame())
402             cachedPage.markForFullStyleRecalc();
403     }
404 }
405
406 void PageCache::markPagesForDeviceScaleChanged(Page& page)
407 {
408     for (auto& item : m_items) {
409         CachedPage& cachedPage = *item->m_cachedPage;
410         if (&page.mainFrame() == &cachedPage.cachedMainFrame()->view()->frame())
411             cachedPage.markForDeviceScaleChanged();
412     }
413 }
414
415 #if ENABLE(VIDEO_TRACK)
416 void PageCache::markPagesForCaptionPreferencesChanged()
417 {
418     for (auto& item : m_items) {
419         ASSERT(item->m_cachedPage);
420         item->m_cachedPage->markForCaptionPreferencesChanged();
421     }
422 }
423 #endif
424
425 static String pruningReasonToDiagnosticLoggingKey(PruningReason pruningReason)
426 {
427     switch (pruningReason) {
428     case PruningReason::MemoryPressure:
429         return DiagnosticLoggingKeys::prunedDueToMemoryPressureKey();
430     case PruningReason::ProcessSuspended:
431         return DiagnosticLoggingKeys::prunedDueToProcessSuspended();
432     case PruningReason::ReachedMaxSize:
433         return DiagnosticLoggingKeys::prunedDueToMaxSizeReached();
434     case PruningReason::None:
435         break;
436     }
437     ASSERT_NOT_REACHED();
438     return emptyString();
439 }
440
441 void PageCache::add(HistoryItem& item, Page& page)
442 {
443     ASSERT(canCache(&page));
444
445     // Remove stale cache entry if necessary.
446     remove(item);
447
448     item.m_cachedPage = std::make_unique<CachedPage>(page);
449     item.m_pruningReason = PruningReason::None;
450     m_items.add(&item);
451     
452     prune(PruningReason::ReachedMaxSize);
453 }
454
455 std::unique_ptr<CachedPage> PageCache::take(HistoryItem& item, Page* page)
456 {
457     if (!item.m_cachedPage) {
458         if (item.m_pruningReason != PruningReason::None)
459             logPageCacheFailureDiagnosticMessage(page, pruningReasonToDiagnosticLoggingKey(item.m_pruningReason));
460         return nullptr;
461     }
462
463     std::unique_ptr<CachedPage> cachedPage = WTF::move(item.m_cachedPage);
464     m_items.remove(&item);
465
466     if (cachedPage->hasExpired()) {
467         LOG(PageCache, "Not restoring page for %s from back/forward cache because cache entry has expired", item.url().string().ascii().data());
468         logPageCacheFailureDiagnosticMessage(page, DiagnosticLoggingKeys::expiredKey());
469         return nullptr;
470     }
471
472     return cachedPage;
473 }
474
475 CachedPage* PageCache::get(HistoryItem& item, Page* page)
476 {
477     CachedPage* cachedPage = item.m_cachedPage.get();
478     if (!cachedPage) {
479         if (item.m_pruningReason != PruningReason::None)
480             logPageCacheFailureDiagnosticMessage(page, pruningReasonToDiagnosticLoggingKey(item.m_pruningReason));
481         return nullptr;
482     }
483
484     if (cachedPage->hasExpired()) {
485         LOG(PageCache, "Not restoring page for %s from back/forward cache because cache entry has expired", item.url().string().ascii().data());
486         logPageCacheFailureDiagnosticMessage(page, DiagnosticLoggingKeys::expiredKey());
487         remove(item);
488         return nullptr;
489     }
490     return cachedPage;
491 }
492
493 void PageCache::remove(HistoryItem& item)
494 {
495     // Safely ignore attempts to remove items not in the cache.
496     if (!item.m_cachedPage)
497         return;
498
499     item.m_cachedPage = nullptr;
500     m_items.remove(&item);
501 }
502
503 void PageCache::prune(PruningReason pruningReason)
504 {
505     while (pageCount() > maxSize()) {
506         auto& oldestItem = m_items.first();
507         oldestItem->m_cachedPage = nullptr;
508         oldestItem->m_pruningReason = pruningReason;
509         m_items.removeFirst();
510     }
511 }
512
513 } // namespace WebCore