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 "CachedFrame.h"
28
29#include "CSSAnimationController.h"
30#include "CachedFramePlatformData.h"
31#include "CachedPage.h"
32#include "CustomHeaderFields.h"
33#include "DOMWindow.h"
34#include "Document.h"
35#include "DocumentLoader.h"
36#include "DocumentTimeline.h"
37#include "Frame.h"
38#include "FrameLoader.h"
39#include "FrameLoaderClient.h"
40#include "FrameView.h"
41#include "Logging.h"
42#include "Page.h"
43#include "PageCache.h"
44#include "RuntimeEnabledFeatures.h"
45#include "SVGDocumentExtensions.h"
46#include "ScriptController.h"
47#include "SerializedScriptValue.h"
48#include <wtf/RefCountedLeakCounter.h>
49#include <wtf/text/CString.h>
50
51#if PLATFORM(IOS_FAMILY) || ENABLE(TOUCH_EVENTS)
52#include "Chrome.h"
53#include "ChromeClient.h"
54#endif
55
56namespace WebCore {
57
58DEFINE_DEBUG_ONLY_GLOBAL(WTF::RefCountedLeakCounter, cachedFrameCounter, ("CachedFrame"));
59
60CachedFrameBase::CachedFrameBase(Frame& frame)
61 : m_document(frame.document())
62 , m_documentLoader(frame.loader().documentLoader())
63 , m_view(frame.view())
64 , m_url(frame.document()->url())
65 , m_isMainFrame(!frame.tree().parent())
66{
67}
68
69CachedFrameBase::~CachedFrameBase()
70{
71#ifndef NDEBUG
72 cachedFrameCounter.decrement();
73#endif
74 // CachedFrames should always have had destroy() called by their parent CachedPage
75 ASSERT(!m_document);
76}
77
78void CachedFrameBase::pruneDetachedChildFrames()
79{
80 for (size_t i = m_childFrames.size(); i;) {
81 --i;
82 if (m_childFrames[i]->view()->frame().page())
83 continue;
84 m_childFrames[i]->destroy();
85 m_childFrames.remove(i);
86 }
87}
88
89void CachedFrameBase::restore()
90{
91 ASSERT(m_document->view() == m_view);
92
93 if (m_isMainFrame)
94 m_view->setParentVisible(true);
95
96 Frame& frame = m_view->frame();
97 m_cachedFrameScriptData->restore(frame);
98
99 if (m_document->svgExtensions())
100 m_document->accessSVGExtensions().unpauseAnimations();
101
102 m_document->resume(ReasonForSuspension::PageCache);
103
104 // It is necessary to update any platform script objects after restoring the
105 // cached page.
106 frame.script().updatePlatformScriptObjects();
107
108 frame.loader().client().didRestoreFromPageCache();
109
110 pruneDetachedChildFrames();
111
112 // Reconstruct the FrameTree. And open the child CachedFrames in their respective FrameLoaders.
113 for (auto& childFrame : m_childFrames) {
114 ASSERT(childFrame->view()->frame().page());
115 frame.tree().appendChild(childFrame->view()->frame());
116 childFrame->open();
117 ASSERT_WITH_SECURITY_IMPLICATION(m_document == frame.document());
118 }
119
120#if PLATFORM(IOS_FAMILY)
121 if (m_isMainFrame) {
122 frame.loader().client().didRestoreFrameHierarchyForCachedFrame();
123
124 if (DOMWindow* domWindow = m_document->domWindow()) {
125 // FIXME: Add SCROLL_LISTENER to the list of event types on Document, and use m_document->hasListenerType(). See <rdar://problem/9615482>.
126 // FIXME: Can use Document::hasListenerType() now.
127 if (domWindow->scrollEventListenerCount() && frame.page())
128 frame.page()->chrome().client().setNeedsScrollNotifications(frame, true);
129 }
130 }
131#endif
132
133 frame.view()->didRestoreFromPageCache();
134}
135
136CachedFrame::CachedFrame(Frame& frame)
137 : CachedFrameBase(frame)
138{
139#ifndef NDEBUG
140 cachedFrameCounter.increment();
141#endif
142 ASSERT(m_document);
143 ASSERT(m_documentLoader);
144 ASSERT(m_view);
145 ASSERT(m_document->pageCacheState() == Document::InPageCache);
146
147 RELEASE_ASSERT(m_document->domWindow());
148 RELEASE_ASSERT(m_document->frame());
149 RELEASE_ASSERT(m_document->domWindow()->frame());
150
151 // FIXME: We have evidence that constructing CachedFrames for descendant frames may detach the document from its frame (rdar://problem/49877867).
152 // This sets the flag to help find the guilty code.
153 m_document->setMayBeDetachedFromFrame(false);
154
155 // Create the CachedFrames for all Frames in the FrameTree.
156 for (Frame* child = frame.tree().firstChild(); child; child = child->tree().nextSibling())
157 m_childFrames.append(std::make_unique<CachedFrame>(*child));
158
159 RELEASE_ASSERT(m_document->domWindow());
160 RELEASE_ASSERT(m_document->frame());
161 RELEASE_ASSERT(m_document->domWindow()->frame());
162
163 // Active DOM objects must be suspended before we cache the frame script data.
164 m_document->suspend(ReasonForSuspension::PageCache);
165
166 m_cachedFrameScriptData = std::make_unique<ScriptCachedFrameData>(frame);
167
168 m_document->domWindow()->suspendForPageCache();
169
170 // Clear FrameView to reset flags such as 'firstVisuallyNonEmptyLayoutCallbackPending' so that the
171 // 'DidFirstVisuallyNonEmptyLayout' callback gets called against when restoring from PageCache.
172 m_view->resetLayoutMilestones();
173
174 frame.loader().client().savePlatformDataToCachedFrame(this);
175
176 // documentWillSuspendForPageCache() can set up a layout timer on the FrameView, so clear timers after that.
177 frame.clearTimers();
178
179 // Deconstruct the FrameTree, to restore it later.
180 // We do this for two reasons:
181 // 1 - We reuse the main frame, so when it navigates to a new page load it needs to start with a blank FrameTree.
182 // 2 - It's much easier to destroy a CachedFrame while it resides in the PageCache if it is disconnected from its parent.
183 for (unsigned i = 0; i < m_childFrames.size(); ++i)
184 frame.tree().removeChild(m_childFrames[i]->view()->frame());
185
186 if (!m_isMainFrame)
187 frame.page()->decrementSubframeCount();
188
189 frame.loader().client().didSaveToPageCache();
190
191#ifndef NDEBUG
192 if (m_isMainFrame)
193 LOG(PageCache, "Finished creating CachedFrame for main frame url '%s' and DocumentLoader %p\n", m_url.string().utf8().data(), m_documentLoader.get());
194 else
195 LOG(PageCache, "Finished creating CachedFrame for child frame with url '%s' and DocumentLoader %p\n", m_url.string().utf8().data(), m_documentLoader.get());
196#endif
197
198#if PLATFORM(IOS_FAMILY)
199 if (m_isMainFrame) {
200 if (DOMWindow* domWindow = m_document->domWindow()) {
201 if (domWindow->scrollEventListenerCount() && frame.page())
202 frame.page()->chrome().client().setNeedsScrollNotifications(frame, false);
203 }
204 }
205#endif
206
207 m_document->setMayBeDetachedFromFrame(true);
208 m_document->detachFromCachedFrame(*this);
209
210 ASSERT_WITH_SECURITY_IMPLICATION(!m_documentLoader->isLoading());
211}
212
213void CachedFrame::open()
214{
215 ASSERT(m_view);
216 ASSERT(m_document);
217 if (!m_isMainFrame)
218 m_view->frame().page()->incrementSubframeCount();
219
220 m_document->attachToCachedFrame(*this);
221
222 m_view->frame().loader().open(*this);
223}
224
225void CachedFrame::clear()
226{
227 if (!m_document)
228 return;
229
230 // clear() should only be called for Frames representing documents that are no longer in the page cache.
231 // This means the CachedFrame has been:
232 // 1 - Successfully restore()'d by going back/forward.
233 // 2 - destroy()'ed because the PageCache is pruning or the WebView was closed.
234 ASSERT(m_document->pageCacheState() == Document::NotInPageCache);
235 ASSERT(m_view);
236 ASSERT(!m_document->frame() || m_document->frame() == &m_view->frame());
237
238 for (int i = m_childFrames.size() - 1; i >= 0; --i)
239 m_childFrames[i]->clear();
240
241 m_document = nullptr;
242 m_view = nullptr;
243 m_url = URL();
244
245 m_cachedFramePlatformData = nullptr;
246 m_cachedFrameScriptData = nullptr;
247}
248
249void CachedFrame::destroy()
250{
251 if (!m_document)
252 return;
253
254 // Only CachedFrames that are still in the PageCache should be destroyed in this manner
255 ASSERT(m_document->pageCacheState() == Document::InPageCache);
256 ASSERT(m_view);
257 ASSERT(!m_document->frame());
258
259 m_document->domWindow()->willDestroyCachedFrame();
260
261 if (!m_isMainFrame && m_view->frame().page()) {
262 m_view->frame().loader().detachViewsAndDocumentLoader();
263 m_view->frame().detachFromPage();
264 }
265
266 for (int i = m_childFrames.size() - 1; i >= 0; --i)
267 m_childFrames[i]->destroy();
268
269 if (m_cachedFramePlatformData)
270 m_cachedFramePlatformData->clear();
271
272 Frame::clearTimers(m_view.get(), m_document.get());
273
274 m_view->frame().animation().detachFromDocument(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->setPageCacheState(Document::NotInPageCache);
281 m_document->prepareForDestruction();
282
283 clear();
284}
285
286void CachedFrame::setCachedFramePlatformData(std::unique_ptr<CachedFramePlatformData> data)
287{
288 m_cachedFramePlatformData = WTFMove(data);
289}
290
291CachedFramePlatformData* CachedFrame::cachedFramePlatformData()
292{
293 return m_cachedFramePlatformData.get();
294}
295
296void CachedFrame::setHasInsecureContent(HasInsecureContent hasInsecureContent)
297{
298 m_hasInsecureContent = hasInsecureContent;
299}
300
301int CachedFrame::descendantFrameCount() const
302{
303 int count = m_childFrames.size();
304 for (size_t i = 0; i < m_childFrames.size(); ++i)
305 count += m_childFrames[i]->descendantFrameCount();
306
307 return count;
308}
309
310} // namespace WebCore
311