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