/* * Copyright (C) 2009 Apple Inc. All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * 1. Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * 2. Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * * THIS SOFTWARE IS PROVIDED BY APPLE INC. ``AS IS'' AND ANY * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL APPLE INC. OR * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY * OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ #include "config.h" #include "CachedPage.h" #include "AnimationController.h" #include "CachedFramePlatformData.h" #include "DOMWindow.h" #include "Document.h" #include "DocumentLoader.h" #include "EventNames.h" #include "ExceptionCode.h" #include "FrameLoader.h" #include "FrameLoaderClient.h" #include "FrameView.h" #include "HistoryController.h" #include "HistoryItem.h" #include "Logging.h" #include "MainFrame.h" #include "Page.h" #include "PageCache.h" #include "PageTransitionEvent.h" #include "SVGDocumentExtensions.h" #include "ScriptController.h" #include "SerializedScriptValue.h" #include #include #if PLATFORM(IOS) || ENABLE(TOUCH_EVENTS) #include "Chrome.h" #include "ChromeClient.h" #endif namespace WebCore { DEFINE_DEBUG_ONLY_GLOBAL(WTF::RefCountedLeakCounter, cachedFrameCounter, ("CachedFrame")); CachedFrameBase::CachedFrameBase(Frame& frame) : m_document(frame.document()) , m_documentLoader(frame.loader().documentLoader()) , m_view(frame.view()) , m_url(frame.document()->url()) , m_isMainFrame(!frame.tree().parent()) , m_isComposited(frame.view()->hasCompositedContent()) { } CachedFrameBase::~CachedFrameBase() { #ifndef NDEBUG cachedFrameCounter.decrement(); #endif // CachedFrames should always have had destroy() called by their parent CachedPage ASSERT(!m_document); } void CachedFrameBase::restore() { ASSERT(m_document->view() == m_view); if (m_isMainFrame) m_view->setParentVisible(true); Frame& frame = m_view->frame(); m_cachedFrameScriptData->restore(frame); if (m_document->svgExtensions()) m_document->accessSVGExtensions().unpauseAnimations(); frame.animation().resumeAnimationsForDocument(m_document.get()); m_document->resume(ActiveDOMObject::PageCache); // It is necessary to update any platform script objects after restoring the // cached page. frame.script().updatePlatformScriptObjects(); if (m_isComposited) frame.view()->restoreBackingStores(); frame.loader().client().didRestoreFromPageCache(); // Reconstruct the FrameTree. And open the child CachedFrames in their respective FrameLoaders. for (unsigned i = 0; i < m_childFrames.size(); ++i) { frame.tree().appendChild(&m_childFrames[i]->view()->frame()); m_childFrames[i]->open(); } #if PLATFORM(IOS) if (m_isMainFrame) { frame.loader().client().didRestoreFrameHierarchyForCachedFrame(); if (DOMWindow* domWindow = m_document->domWindow()) { // FIXME: Add SCROLL_LISTENER to the list of event types on Document, and use m_document->hasListenerType(). See . // FIXME: Can use Document::hasListenerType() now. if (domWindow->scrollEventListenerCount() && frame.page()) frame.page()->chrome().client().setNeedsScrollNotifications(&frame, true); } } #endif // FIXME: update Page Visibility state here. // https://bugs.webkit.org/show_bug.cgi?id=116770 m_document->enqueuePageshowEvent(PageshowEventPersisted); HistoryItem* historyItem = frame.loader().history().currentItem(); m_document->enqueuePopstateEvent(historyItem && historyItem->stateObject() ? historyItem->stateObject() : SerializedScriptValue::nullValue()); #if ENABLE(TOUCH_EVENTS) && !PLATFORM(IOS) if (m_document->hasTouchEventHandlers()) m_document->page()->chrome().client().needTouchEvents(true); #endif } CachedFrame::CachedFrame(Frame& frame) : CachedFrameBase(frame) { #ifndef NDEBUG cachedFrameCounter.increment(); #endif ASSERT(m_document); ASSERT(m_documentLoader); ASSERT(m_view); // Custom scrollbar renderers will get reattached when the document comes out of the page cache m_view->detachCustomScrollbars(); ASSERT(m_document->inPageCache()); // Create the CachedFrames for all Frames in the FrameTree. for (Frame* child = frame.tree().firstChild(); child; child = child->tree().nextSibling()) m_childFrames.append(std::make_unique(*child)); // Active DOM objects must be suspended before we cache the frame script data. m_document->suspend(ActiveDOMObject::PageCache); m_cachedFrameScriptData = std::make_unique(frame); m_document->domWindow()->suspendForDocumentSuspension(); frame.loader().client().savePlatformDataToCachedFrame(this); if (m_isComposited && PageCache::singleton().shouldClearBackingStores()) frame.view()->clearBackingStores(); // documentWillSuspendForPageCache() can set up a layout timer on the FrameView, so clear timers after that. frame.clearTimers(); // Deconstruct the FrameTree, to restore it later. // We do this for two reasons: // 1 - We reuse the main frame, so when it navigates to a new page load it needs to start with a blank FrameTree. // 2 - It's much easier to destroy a CachedFrame while it resides in the PageCache if it is disconnected from its parent. for (unsigned i = 0; i < m_childFrames.size(); ++i) frame.tree().removeChild(&m_childFrames[i]->view()->frame()); if (!m_isMainFrame) frame.page()->decrementSubframeCount(); frame.loader().client().didSaveToPageCache(); #ifndef NDEBUG if (m_isMainFrame) LOG(PageCache, "Finished creating CachedFrame for main frame url '%s' and DocumentLoader %p\n", m_url.string().utf8().data(), m_documentLoader.get()); else LOG(PageCache, "Finished creating CachedFrame for child frame with url '%s' and DocumentLoader %p\n", m_url.string().utf8().data(), m_documentLoader.get()); #endif #if PLATFORM(IOS) if (m_isMainFrame) { if (DOMWindow* domWindow = m_document->domWindow()) { if (domWindow->scrollEventListenerCount() && frame.page()) frame.page()->chrome().client().setNeedsScrollNotifications(&frame, false); } } #endif ASSERT_WITH_SECURITY_IMPLICATION(!m_documentLoader->isLoading()); } void CachedFrame::open() { ASSERT(m_view); if (!m_isMainFrame) m_view->frame().page()->incrementSubframeCount(); m_view->frame().loader().open(*this); } void CachedFrame::clear() { if (!m_document) return; // clear() should only be called for Frames representing documents that are no longer in the page cache. // This means the CachedFrame has been: // 1 - Successfully restore()'d by going back/forward. // 2 - destroy()'ed because the PageCache is pruning or the WebView was closed. ASSERT(!m_document->inPageCache()); ASSERT(m_view); ASSERT(!m_document->frame() || m_document->frame() == &m_view->frame()); for (int i = m_childFrames.size() - 1; i >= 0; --i) m_childFrames[i]->clear(); m_document = nullptr; m_view = nullptr; m_url = URL(); m_cachedFramePlatformData = nullptr; m_cachedFrameScriptData = nullptr; } void CachedFrame::destroy() { if (!m_document) return; // Only CachedFrames that are still in the PageCache should be destroyed in this manner ASSERT(m_document->inPageCache()); ASSERT(m_view); ASSERT(m_document->frame() == &m_view->frame()); m_document->domWindow()->willDestroyCachedFrame(); if (!m_isMainFrame) { m_view->frame().detachFromPage(); m_view->frame().loader().detachViewsAndDocumentLoader(); } for (int i = m_childFrames.size() - 1; i >= 0; --i) m_childFrames[i]->destroy(); if (m_cachedFramePlatformData) m_cachedFramePlatformData->clear(); Frame::clearTimers(m_view.get(), m_document.get()); // FIXME: Why do we need to call removeAllEventListeners here? When the document is in page cache, this method won't work // fully anyway, because the document won't be able to access its DOMWindow object (due to being frameless). m_document->removeAllEventListeners(); m_document->setPageCacheState(Document::NotInPageCache); m_document->prepareForDestruction(); clear(); } void CachedFrame::setCachedFramePlatformData(std::unique_ptr data) { m_cachedFramePlatformData = WTFMove(data); } CachedFramePlatformData* CachedFrame::cachedFramePlatformData() { return m_cachedFramePlatformData.get(); } int CachedFrame::descendantFrameCount() const { int count = m_childFrames.size(); for (size_t i = 0; i < m_childFrames.size(); ++i) count += m_childFrames[i]->descendantFrameCount(); return count; } } // namespace WebCore