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