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