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