1/*
2 * Copyright (C) 2009 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 "CachedPage.h"
28
29#include "AnimationController.h"
30#include "CachedFramePlatformData.h"
31#include "DOMWindow.h"
32#include "Document.h"
33#include "DocumentLoader.h"
34#include "EventNames.h"
35#include "ExceptionCode.h"
36#include "FocusController.h"
37#include "FrameLoader.h"
38#include "FrameLoaderClient.h"
39#include "FrameView.h"
40#include "HistoryController.h"
41#include "HistoryItem.h"
42#include "Logging.h"
43#include "MainFrame.h"
44#include "Page.h"
45#include "PageCache.h"
46#include "PageTransitionEvent.h"
47#include "SVGDocumentExtensions.h"
48#include "ScriptController.h"
49#include "SerializedScriptValue.h"
50#include <wtf/RefCountedLeakCounter.h>
51#include <wtf/text/CString.h>
52
53#if ENABLE(TOUCH_EVENTS)
54#include "Chrome.h"
55#include "ChromeClient.h"
56#endif
57
58namespace WebCore {
59
60DEFINE_DEBUG_ONLY_GLOBAL(WTF::RefCountedLeakCounter, cachedFrameCounter, ("CachedFrame"));
61
62CachedFrameBase::CachedFrameBase(Frame& frame)
63    : m_document(frame.document())
64    , m_documentLoader(frame.loader().documentLoader())
65    , m_view(frame.view())
66    , m_url(frame.document()->url())
67    , m_isMainFrame(!frame.tree().parent())
68    , m_isComposited(frame.view()->hasCompositedContent())
69{
70}
71
72CachedFrameBase::~CachedFrameBase()
73{
74#ifndef NDEBUG
75    cachedFrameCounter.decrement();
76#endif
77    // CachedFrames should always have had destroy() called by their parent CachedPage
78    ASSERT(!m_document);
79}
80
81void CachedFrameBase::restore()
82{
83    ASSERT(m_document->view() == m_view);
84
85    if (m_isMainFrame)
86        m_view->setParentVisible(true);
87
88    Frame& frame = m_view->frame();
89    m_cachedFrameScriptData->restore(frame);
90
91    if (m_document->svgExtensions())
92        m_document->accessSVGExtensions()->unpauseAnimations();
93
94    frame.animation().resumeAnimationsForDocument(m_document.get());
95    m_document->resumeActiveDOMObjects(ActiveDOMObject::DocumentWillBecomeInactive);
96    m_document->resumeScriptedAnimationControllerCallbacks();
97
98    // It is necessary to update any platform script objects after restoring the
99    // cached page.
100    frame.script().updatePlatformScriptObjects();
101
102    if (m_isComposited)
103        frame.view()->restoreBackingStores();
104
105    frame.loader().client().didRestoreFromPageCache();
106
107    // Reconstruct the FrameTree. And open the child CachedFrames in their respective FrameLoaders.
108    for (unsigned i = 0; i < m_childFrames.size(); ++i) {
109        frame.tree().appendChild(&m_childFrames[i]->view()->frame());
110        m_childFrames[i]->open();
111    }
112
113#if PLATFORM(IOS)
114    if (m_isMainFrame) {
115        frame.loader().client().didRestoreFrameHierarchyForCachedFrame();
116
117        if (DOMWindow* domWindow = m_document->domWindow()) {
118            // FIXME: Add SCROLL_LISTENER to the list of event types on Document, and use m_document->hasListenerType(). See <rdar://problem/9615482>.
119            if (domWindow->scrollEventListenerCount() && frame.page())
120                frame.page()->chrome().client().setNeedsScrollNotifications(&frame, true);
121        }
122    }
123#endif
124
125    // FIXME: update Page Visibility state here.
126    // https://bugs.webkit.org/show_bug.cgi?id=116770
127    m_document->enqueuePageshowEvent(PageshowEventPersisted);
128
129    HistoryItem* historyItem = frame.loader().history().currentItem();
130    m_document->enqueuePopstateEvent(historyItem && historyItem->stateObject() ? historyItem->stateObject() : SerializedScriptValue::nullValue());
131
132#if ENABLE(TOUCH_EVENTS) && !PLATFORM(IOS)
133    if (m_document->hasTouchEventHandlers())
134        m_document->page()->chrome().client().needTouchEvents(true);
135#endif
136
137    m_document->documentDidResumeFromPageCache();
138}
139
140CachedFrame::CachedFrame(Frame& frame)
141    : CachedFrameBase(frame)
142{
143#ifndef NDEBUG
144    cachedFrameCounter.increment();
145#endif
146    ASSERT(m_document);
147    ASSERT(m_documentLoader);
148    ASSERT(m_view);
149
150    if (frame.page()->focusController().focusedFrame() == &frame)
151        frame.page()->focusController().setFocusedFrame(&frame.mainFrame());
152
153    // Custom scrollbar renderers will get reattached when the document comes out of the page cache
154    m_view->detachCustomScrollbars();
155
156    m_document->setInPageCache(true);
157    frame.loader().stopLoading(UnloadEventPolicyUnloadAndPageHide);
158
159    // Create the CachedFrames for all Frames in the FrameTree.
160    for (Frame* child = frame.tree().firstChild(); child; child = child->tree().nextSibling())
161        m_childFrames.append(std::make_unique<CachedFrame>(*child));
162
163    // Active DOM objects must be suspended before we cache the frame script data,
164    // but after we've fired the pagehide event, in case that creates more objects.
165    // Suspending must also happen after we've recursed over child frames, in case
166    // those create more objects.
167    m_document->documentWillSuspendForPageCache();
168    m_document->suspendScriptedAnimationControllerCallbacks();
169    m_document->suspendActiveDOMObjects(ActiveDOMObject::DocumentWillBecomeInactive);
170    m_cachedFrameScriptData = std::make_unique<ScriptCachedFrameData>(frame);
171
172    m_document->domWindow()->suspendForPageCache();
173
174    frame.loader().client().savePlatformDataToCachedFrame(this);
175
176    if (m_isComposited && pageCache()->shouldClearBackingStores())
177        frame.view()->clearBackingStores();
178
179    // documentWillSuspendForPageCache() can set up a layout timer on the FrameView, so clear timers after that.
180    frame.clearTimers();
181
182    // Deconstruct the FrameTree, to restore it later.
183    // We do this for two reasons:
184    // 1 - We reuse the main frame, so when it navigates to a new page load it needs to start with a blank FrameTree.
185    // 2 - It's much easier to destroy a CachedFrame while it resides in the PageCache if it is disconnected from its parent.
186    for (unsigned i = 0; i < m_childFrames.size(); ++i)
187        frame.tree().removeChild(&m_childFrames[i]->view()->frame());
188
189    if (!m_isMainFrame)
190        frame.page()->decrementSubframeCount();
191
192    frame.loader().client().didSaveToPageCache();
193
194#ifndef NDEBUG
195    if (m_isMainFrame)
196        LOG(PageCache, "Finished creating CachedFrame for main frame url '%s' and DocumentLoader %p\n", m_url.string().utf8().data(), m_documentLoader.get());
197    else
198        LOG(PageCache, "Finished creating CachedFrame for child frame with url '%s' and DocumentLoader %p\n", m_url.string().utf8().data(), m_documentLoader.get());
199#endif
200
201#if PLATFORM(IOS)
202    if (m_isMainFrame) {
203        if (DOMWindow* domWindow = m_document->domWindow()) {
204            if (domWindow->scrollEventListenerCount() && frame.page())
205                frame.page()->chrome().client().setNeedsScrollNotifications(&frame, false);
206        }
207    }
208#endif
209}
210
211void CachedFrame::open()
212{
213    ASSERT(m_view);
214    if (!m_isMainFrame)
215        m_view->frame().page()->incrementSubframeCount();
216
217    m_view->frame().loader().open(*this);
218}
219
220void CachedFrame::clear()
221{
222    if (!m_document)
223        return;
224
225    // clear() should only be called for Frames representing documents that are no longer in the page cache.
226    // This means the CachedFrame has been:
227    // 1 - Successfully restore()'d by going back/forward.
228    // 2 - destroy()'ed because the PageCache is pruning or the WebView was closed.
229    ASSERT(!m_document->inPageCache());
230    ASSERT(m_view);
231    ASSERT(!m_document->frame() || m_document->frame() == &m_view->frame());
232
233    for (int i = m_childFrames.size() - 1; i >= 0; --i)
234        m_childFrames[i]->clear();
235
236    m_document = nullptr;
237    m_view = nullptr;
238    m_url = URL();
239
240    m_cachedFramePlatformData = nullptr;
241    m_cachedFrameScriptData = nullptr;
242}
243
244void CachedFrame::destroy()
245{
246    if (!m_document)
247        return;
248
249    // Only CachedFrames that are still in the PageCache should be destroyed in this manner
250    ASSERT(m_document->inPageCache());
251    ASSERT(m_view);
252    ASSERT(m_document->frame() == &m_view->frame());
253
254    m_document->domWindow()->willDestroyCachedFrame();
255
256    if (!m_isMainFrame) {
257        m_view->frame().detachFromPage();
258        m_view->frame().loader().detachViewsAndDocumentLoader();
259    }
260
261    for (int i = m_childFrames.size() - 1; i >= 0; --i)
262        m_childFrames[i]->destroy();
263
264    if (m_cachedFramePlatformData)
265        m_cachedFramePlatformData->clear();
266
267    Frame::clearTimers(m_view.get(), m_document.get());
268
269    // FIXME: Why do we need to call removeAllEventListeners here? When the document is in page cache, this method won't work
270    // fully anyway, because the document won't be able to access its DOMWindow object (due to being frameless).
271    m_document->removeAllEventListeners();
272
273    m_document->setInPageCache(false);
274    m_document->prepareForDestruction();
275
276    clear();
277}
278
279void CachedFrame::setCachedFramePlatformData(std::unique_ptr<CachedFramePlatformData> data)
280{
281    m_cachedFramePlatformData = WTF::move(data);
282}
283
284CachedFramePlatformData* CachedFrame::cachedFramePlatformData()
285{
286    return m_cachedFramePlatformData.get();
287}
288
289int CachedFrame::descendantFrameCount() const
290{
291    int count = m_childFrames.size();
292    for (size_t i = 0; i < m_childFrames.size(); ++i)
293        count += m_childFrames[i]->descendantFrameCount();
294
295    return count;
296}
297
298} // namespace WebCore
299