Source/WebCore:
[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 "FocusController.h"
41 #include "FrameLoader.h"
42 #include "FrameLoaderClient.h"
43 #include "FrameView.h"
44 #include "HistoryController.h"
45 #include "IgnoreOpensDuringUnloadCountIncrementer.h"
46 #include "Logging.h"
47 #include "MainFrame.h"
48 #include "MemoryPressureHandler.h"
49 #include "NoEventDispatchAssertion.h"
50 #include "Page.h"
51 #include "Settings.h"
52 #include "SubframeLoader.h"
53 #include <wtf/CurrentTime.h>
54 #include <wtf/NeverDestroyed.h>
55 #include <wtf/TemporaryChange.h>
56 #include <wtf/text/CString.h>
57 #include <wtf/text/StringConcatenate.h>
58
59 #if ENABLE(PROXIMITY_EVENTS)
60 #include "DeviceProximityController.h"
61 #endif
62
63 namespace WebCore {
64
65 #define PCLOG(...) LOG(PageCache, "%*s%s", indentLevel*4, "", makeString(__VA_ARGS__).utf8().data())
66
67 static inline void logPageCacheFailureDiagnosticMessage(DiagnosticLoggingClient& client, const String& reason)
68 {
69     client.logDiagnosticMessageWithValue(DiagnosticLoggingKeys::pageCacheKey(), DiagnosticLoggingKeys::failureKey(), reason, ShouldSample::Yes);
70 }
71
72 static inline void logPageCacheFailureDiagnosticMessage(Page* page, const String& reason)
73 {
74     if (!page)
75         return;
76
77     logPageCacheFailureDiagnosticMessage(page->diagnosticLoggingClient(), reason);
78 }
79
80 static bool canCacheFrame(Frame& frame, DiagnosticLoggingClient& diagnosticLoggingClient, unsigned indentLevel)
81 {
82     PCLOG("+---");
83     FrameLoader& frameLoader = frame.loader();
84
85     // Prevent page caching if a subframe is still in provisional load stage.
86     // We only do this check for subframes because the main frame is reused when navigating to a new page.
87     if (!frame.isMainFrame() && frameLoader.state() == FrameStateProvisional) {
88         PCLOG("   -Frame is in provisional load stage");
89         logPageCacheFailureDiagnosticMessage(diagnosticLoggingClient, DiagnosticLoggingKeys::provisionalLoadKey());
90         return false;
91     }
92
93     DocumentLoader* documentLoader = frameLoader.documentLoader();
94     if (!documentLoader) {
95         PCLOG("   -There is no DocumentLoader object");
96         logPageCacheFailureDiagnosticMessage(diagnosticLoggingClient, DiagnosticLoggingKeys::noDocumentLoaderKey());
97         return false;
98     }
99
100     URL currentURL = documentLoader->url();
101     URL newURL = frameLoader.provisionalDocumentLoader() ? frameLoader.provisionalDocumentLoader()->url() : URL();
102     if (!newURL.isEmpty())
103         PCLOG(" Determining if frame can be cached navigating from (", currentURL.string(), ") to (", newURL.string(), "):");
104     else
105         PCLOG(" Determining if subframe with URL (", currentURL.string(), ") can be cached:");
106      
107     bool isCacheable = true;
108     if (!documentLoader->mainDocumentError().isNull()) {
109         PCLOG("   -Main document has an error");
110         logPageCacheFailureDiagnosticMessage(diagnosticLoggingClient, DiagnosticLoggingKeys::mainDocumentErrorKey());
111
112         if (documentLoader->mainDocumentError().isCancellation() && documentLoader->subresourceLoadersArePageCacheAcceptable())
113             PCLOG("    -But, it was a cancellation and all loaders during the cancelation were loading images or XHR.");
114         else
115             isCacheable = false;
116     }
117     // Do not cache error pages (these can be recognized as pages with substitute data or unreachable URLs).
118     if (documentLoader->substituteData().isValid() && !documentLoader->substituteData().failingURL().isEmpty()) {
119         PCLOG("   -Frame is an error page");
120         logPageCacheFailureDiagnosticMessage(diagnosticLoggingClient, DiagnosticLoggingKeys::isErrorPageKey());
121         isCacheable = false;
122     }
123     if (frameLoader.subframeLoader().containsPlugins() && !frame.page()->settings().pageCacheSupportsPlugins()) {
124         PCLOG("   -Frame contains plugins");
125         logPageCacheFailureDiagnosticMessage(diagnosticLoggingClient, DiagnosticLoggingKeys::hasPluginsKey());
126         isCacheable = false;
127     }
128     if (frame.isMainFrame() && frame.document() && frame.document()->url().protocolIs("https") && documentLoader->response().cacheControlContainsNoStore()) {
129         PCLOG("   -Frame is HTTPS, and cache control prohibits storing");
130         logPageCacheFailureDiagnosticMessage(diagnosticLoggingClient, DiagnosticLoggingKeys::httpsNoStoreKey());
131         isCacheable = false;
132     }
133     if (frame.isMainFrame() && !frameLoader.history().currentItem()) {
134         PCLOG("   -Main frame has no current history item");
135         logPageCacheFailureDiagnosticMessage(diagnosticLoggingClient, DiagnosticLoggingKeys::noCurrentHistoryItemKey());
136         isCacheable = false;
137     }
138     if (frameLoader.quickRedirectComing()) {
139         PCLOG("   -Quick redirect is coming");
140         logPageCacheFailureDiagnosticMessage(diagnosticLoggingClient, DiagnosticLoggingKeys::quirkRedirectComingKey());
141         isCacheable = false;
142     }
143     if (documentLoader->isLoading()) {
144         PCLOG("   -DocumentLoader is still loading");
145         logPageCacheFailureDiagnosticMessage(diagnosticLoggingClient, DiagnosticLoggingKeys::isLoadingKey());
146         isCacheable = false;
147     }
148     if (documentLoader->isStopping()) {
149         PCLOG("   -DocumentLoader is in the middle of stopping");
150         logPageCacheFailureDiagnosticMessage(diagnosticLoggingClient, DiagnosticLoggingKeys::documentLoaderStoppingKey());
151         isCacheable = false;
152     }
153
154     Vector<ActiveDOMObject*> unsuspendableObjects;
155     if (frame.document() && !frame.document()->canSuspendActiveDOMObjectsForDocumentSuspension(&unsuspendableObjects)) {
156         PCLOG("   -The document cannot suspend its active DOM Objects");
157         for (auto* activeDOMObject : unsuspendableObjects) {
158             PCLOG("    - Unsuspendable: ", activeDOMObject->activeDOMObjectName());
159             diagnosticLoggingClient.logDiagnosticMessageWithValue(DiagnosticLoggingKeys::pageCacheKey(), DiagnosticLoggingKeys::unsuspendableDOMObjectKey(), activeDOMObject->activeDOMObjectName(), ShouldSample::Yes);
160             UNUSED_PARAM(activeDOMObject);
161         }
162         logPageCacheFailureDiagnosticMessage(diagnosticLoggingClient, DiagnosticLoggingKeys::cannotSuspendActiveDOMObjectsKey());
163         isCacheable = false;
164     }
165     // FIXME: We should investigating caching frames that have an associated
166     // application cache. <rdar://problem/5917899> tracks that work.
167     if (!documentLoader->applicationCacheHost()->canCacheInPageCache()) {
168         PCLOG("   -The DocumentLoader uses an application cache");
169         logPageCacheFailureDiagnosticMessage(diagnosticLoggingClient, DiagnosticLoggingKeys::applicationCacheKey());
170         isCacheable = false;
171     }
172     if (!frameLoader.client().canCachePage()) {
173         PCLOG("   -The client says this frame cannot be cached");
174         logPageCacheFailureDiagnosticMessage(diagnosticLoggingClient, DiagnosticLoggingKeys::deniedByClientKey());
175         isCacheable = false;
176     }
177
178     for (Frame* child = frame.tree().firstChild(); child; child = child->tree().nextSibling()) {
179         if (!canCacheFrame(*child, diagnosticLoggingClient, indentLevel + 1))
180             isCacheable = false;
181     }
182     
183     PCLOG(isCacheable ? " Frame CAN be cached" : " Frame CANNOT be cached");
184     PCLOG("+---");
185     
186     return isCacheable;
187 }
188
189 static bool canCachePage(Page& page)
190 {
191     unsigned indentLevel = 0;
192     PCLOG("--------\n Determining if page can be cached:");
193
194     DiagnosticLoggingClient& diagnosticLoggingClient = page.diagnosticLoggingClient();
195     bool isCacheable = canCacheFrame(page.mainFrame(), diagnosticLoggingClient, indentLevel + 1);
196     
197     if (!page.settings().usesPageCache() || page.isResourceCachingDisabled()) {
198         PCLOG("   -Page settings says b/f cache disabled");
199         logPageCacheFailureDiagnosticMessage(diagnosticLoggingClient, DiagnosticLoggingKeys::isDisabledKey());
200         isCacheable = false;
201     }
202 #if ENABLE(DEVICE_ORIENTATION) && !PLATFORM(IOS)
203     if (DeviceMotionController::isActiveAt(&page)) {
204         PCLOG("   -Page is using DeviceMotion");
205         logPageCacheFailureDiagnosticMessage(diagnosticLoggingClient, DiagnosticLoggingKeys::deviceMotionKey());
206         isCacheable = false;
207     }
208     if (DeviceOrientationController::isActiveAt(&page)) {
209         PCLOG("   -Page is using DeviceOrientation");
210         logPageCacheFailureDiagnosticMessage(diagnosticLoggingClient, DiagnosticLoggingKeys::deviceOrientationKey());
211         isCacheable = false;
212     }
213 #endif
214 #if ENABLE(PROXIMITY_EVENTS)
215     if (DeviceProximityController::isActiveAt(page)) {
216         PCLOG("   -Page is using DeviceProximity");
217         logPageCacheFailureDiagnosticMessage(diagnosticLoggingClient, deviceProximityKey);
218         isCacheable = false;
219     }
220 #endif
221     FrameLoadType loadType = page.mainFrame().loader().loadType();
222     switch (loadType) {
223     case FrameLoadType::Reload:
224         // No point writing to the cache on a reload, since we will just write over it again when we leave that page.
225         PCLOG("   -Load type is: Reload");
226         logPageCacheFailureDiagnosticMessage(diagnosticLoggingClient, DiagnosticLoggingKeys::reloadKey());
227         isCacheable = false;
228         break;
229     case FrameLoadType::Same: // user loads same URL again (but not reload button)
230         // No point writing to the cache on a same load, since we will just write over it again when we leave that page.
231         PCLOG("   -Load type is: Same");
232         logPageCacheFailureDiagnosticMessage(diagnosticLoggingClient, DiagnosticLoggingKeys::sameLoadKey());
233         isCacheable = false;
234         break;
235     case FrameLoadType::RedirectWithLockedBackForwardList:
236         // Don't write to the cache if in the middle of a redirect, since we will want to store the final page we end up on.
237         PCLOG("   -Load type is: RedirectWithLockedBackForwardList");
238         logPageCacheFailureDiagnosticMessage(diagnosticLoggingClient, DiagnosticLoggingKeys::redirectKey());
239         isCacheable = false;
240         break;
241     case FrameLoadType::Replace:
242         // No point writing to the cache on a replace, since we will just write over it again when we leave that page.
243         PCLOG("   -Load type is: Replace");
244         logPageCacheFailureDiagnosticMessage(diagnosticLoggingClient, DiagnosticLoggingKeys::replaceKey());
245         isCacheable = false;
246         break;
247     case FrameLoadType::ReloadFromOrigin: {
248         // No point writing to the cache on a reload, since we will just write over it again when we leave that page.
249         PCLOG("   -Load type is: ReloadFromOrigin");
250         logPageCacheFailureDiagnosticMessage(diagnosticLoggingClient, DiagnosticLoggingKeys::reloadFromOriginKey());
251         isCacheable = false;
252         break;
253     }
254     case FrameLoadType::Standard:
255     case FrameLoadType::Back:
256     case FrameLoadType::Forward:
257     case FrameLoadType::IndexedBackForward: // a multi-item hop in the backforward list
258         // Cacheable.
259         break;
260     }
261     
262     if (isCacheable)
263         PCLOG(" Page CAN be cached\n--------");
264     else
265         PCLOG(" Page CANNOT be cached\n--------");
266
267     diagnosticLoggingClient.logDiagnosticMessageWithResult(DiagnosticLoggingKeys::pageCacheKey(), DiagnosticLoggingKeys::canCacheKey(), isCacheable ? DiagnosticLoggingResultPass : DiagnosticLoggingResultFail, ShouldSample::Yes);
268     return isCacheable;
269 }
270
271 PageCache& PageCache::singleton()
272 {
273     static NeverDestroyed<PageCache> globalPageCache;
274     return globalPageCache;
275 }
276     
277 bool PageCache::canCache(Page& page) const
278 {
279     if (!m_maxSize) {
280         logPageCacheFailureDiagnosticMessage(&page, DiagnosticLoggingKeys::isDisabledKey());
281         return false;
282     }
283
284     if (MemoryPressureHandler::singleton().isUnderMemoryPressure()) {
285         logPageCacheFailureDiagnosticMessage(&page, DiagnosticLoggingKeys::underMemoryPressureKey());
286         return false;
287     }
288     
289     return canCachePage(page);
290 }
291
292 void PageCache::pruneToSizeNow(unsigned size, PruningReason pruningReason)
293 {
294     TemporaryChange<unsigned> change(m_maxSize, size);
295     prune(pruningReason);
296 }
297
298 void PageCache::setMaxSize(unsigned maxSize)
299 {
300     m_maxSize = maxSize;
301     prune(PruningReason::None);
302 }
303
304 unsigned PageCache::frameCount() const
305 {
306     unsigned frameCount = m_items.size();
307     for (auto& item : m_items) {
308         ASSERT(item->m_cachedPage);
309         frameCount += item->m_cachedPage->cachedMainFrame()->descendantFrameCount();
310     }
311     
312     return frameCount;
313 }
314
315 void PageCache::markPagesForVisitedLinkStyleRecalc()
316 {
317     for (auto& item : m_items) {
318         ASSERT(item->m_cachedPage);
319         item->m_cachedPage->markForVisitedLinkStyleRecalc();
320     }
321 }
322
323 void PageCache::markPagesForFullStyleRecalc(Page& page)
324 {
325     for (auto& item : m_items) {
326         CachedPage& cachedPage = *item->m_cachedPage;
327         if (&page.mainFrame() == &cachedPage.cachedMainFrame()->view()->frame())
328             cachedPage.markForFullStyleRecalc();
329     }
330 }
331
332 void PageCache::markPagesForDeviceOrPageScaleChanged(Page& page)
333 {
334     for (auto& item : m_items) {
335         CachedPage& cachedPage = *item->m_cachedPage;
336         if (&page.mainFrame() == &cachedPage.cachedMainFrame()->view()->frame())
337             cachedPage.markForDeviceOrPageScaleChanged();
338     }
339 }
340
341 void PageCache::markPagesForContentsSizeChanged(Page& page)
342 {
343     for (auto& item : m_items) {
344         CachedPage& cachedPage = *item->m_cachedPage;
345         if (&page.mainFrame() == &cachedPage.cachedMainFrame()->view()->frame())
346             cachedPage.markForContentsSizeChanged();
347     }
348 }
349
350 #if ENABLE(VIDEO_TRACK)
351 void PageCache::markPagesForCaptionPreferencesChanged()
352 {
353     for (auto& item : m_items) {
354         ASSERT(item->m_cachedPage);
355         item->m_cachedPage->markForCaptionPreferencesChanged();
356     }
357 }
358 #endif
359
360 static String pruningReasonToDiagnosticLoggingKey(PruningReason pruningReason)
361 {
362     switch (pruningReason) {
363     case PruningReason::MemoryPressure:
364         return DiagnosticLoggingKeys::prunedDueToMemoryPressureKey();
365     case PruningReason::ProcessSuspended:
366         return DiagnosticLoggingKeys::prunedDueToProcessSuspended();
367     case PruningReason::ReachedMaxSize:
368         return DiagnosticLoggingKeys::prunedDueToMaxSizeReached();
369     case PruningReason::None:
370         break;
371     }
372     ASSERT_NOT_REACHED();
373     return emptyString();
374 }
375
376 static void setInPageCache(Page& page, bool isInPageCache)
377 {
378     for (Frame* frame = &page.mainFrame(); frame; frame = frame->tree().traverseNext()) {
379         if (auto* document = frame->document())
380             document->setInPageCache(isInPageCache);
381     }
382 }
383
384 static void firePageHideEventRecursively(Frame& frame)
385 {
386     auto* document = frame.document();
387     if (!document)
388         return;
389
390     // stopLoading() will fire the pagehide event in each subframe and the HTML specification states
391     // that the parent document's ignore-opens-during-unload counter should be incremented while the
392     // pagehide event is being fired in its subframes:
393     // https://html.spec.whatwg.org/multipage/browsers.html#unload-a-document
394     IgnoreOpensDuringUnloadCountIncrementer ignoreOpensDuringUnloadCountIncrementer(document);
395
396     frame.loader().stopLoading(UnloadEventPolicyUnloadAndPageHide);
397
398     for (RefPtr<Frame> child = frame.tree().firstChild(); child; child = child->tree().nextSibling())
399         firePageHideEventRecursively(*child);
400 }
401
402 void PageCache::addIfCacheable(HistoryItem& item, Page* page)
403 {
404     if (item.isInPageCache())
405         return;
406
407     if (!page || !canCache(*page))
408         return;
409
410     // Make sure all the documents know they are being added to the PageCache.
411     setInPageCache(*page, true);
412
413     // Focus the main frame, defocusing a focused subframe (if we have one). We do this here,
414     // before the page enters the page cache, while we still can dispatch DOM blur/focus events.
415     if (page->focusController().focusedFrame())
416         page->focusController().setFocusedFrame(&page->mainFrame());
417
418     // Fire the pagehide event in all frames.
419     firePageHideEventRecursively(page->mainFrame());
420
421     // Check that the page is still page-cacheable after firing the pagehide event. The JS event handlers
422     // could have altered the page in a way that could prevent caching.
423     if (!canCache(*page)) {
424         setInPageCache(*page, false);
425         return;
426     }
427
428     // Make sure we no longer fire any JS events past this point.
429     NoEventDispatchAssertion assertNoEventDispatch;
430
431     item.m_cachedPage = std::make_unique<CachedPage>(*page);
432     item.m_pruningReason = PruningReason::None;
433     m_items.add(&item);
434     
435     prune(PruningReason::ReachedMaxSize);
436 }
437
438 std::unique_ptr<CachedPage> PageCache::take(HistoryItem& item, Page* page)
439 {
440     if (!item.m_cachedPage) {
441         if (item.m_pruningReason != PruningReason::None)
442             logPageCacheFailureDiagnosticMessage(page, pruningReasonToDiagnosticLoggingKey(item.m_pruningReason));
443         return nullptr;
444     }
445
446     m_items.remove(&item);
447     std::unique_ptr<CachedPage> cachedPage = WTFMove(item.m_cachedPage);
448
449     if (cachedPage->hasExpired() || (page && page->isResourceCachingDisabled())) {
450         LOG(PageCache, "Not restoring page for %s from back/forward cache because cache entry has expired", item.url().string().ascii().data());
451         logPageCacheFailureDiagnosticMessage(page, DiagnosticLoggingKeys::expiredKey());
452         return nullptr;
453     }
454
455     return cachedPage;
456 }
457
458 CachedPage* PageCache::get(HistoryItem& item, Page* page)
459 {
460     CachedPage* cachedPage = item.m_cachedPage.get();
461     if (!cachedPage) {
462         if (item.m_pruningReason != PruningReason::None)
463             logPageCacheFailureDiagnosticMessage(page, pruningReasonToDiagnosticLoggingKey(item.m_pruningReason));
464         return nullptr;
465     }
466
467     if (cachedPage->hasExpired() || (page && page->isResourceCachingDisabled())) {
468         LOG(PageCache, "Not restoring page for %s from back/forward cache because cache entry has expired", item.url().string().ascii().data());
469         logPageCacheFailureDiagnosticMessage(page, DiagnosticLoggingKeys::expiredKey());
470         remove(item);
471         return nullptr;
472     }
473     return cachedPage;
474 }
475
476 void PageCache::remove(HistoryItem& item)
477 {
478     // Safely ignore attempts to remove items not in the cache.
479     if (!item.m_cachedPage)
480         return;
481
482     m_items.remove(&item);
483     item.m_cachedPage = nullptr;
484 }
485
486 void PageCache::prune(PruningReason pruningReason)
487 {
488     while (pageCount() > maxSize()) {
489         auto oldestItem = m_items.takeFirst();
490         oldestItem->m_cachedPage = nullptr;
491         oldestItem->m_pruningReason = pruningReason;
492     }
493 }
494
495 } // namespace WebCore