1/*
2 * Copyright (C) 2018 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. AND ITS CONTRIBUTORS ``AS IS''
14 * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
15 * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
16 * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL APPLE INC. OR ITS CONTRIBUTORS
17 * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
18 * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
19 * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
20 * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
21 * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
22 * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF
23 * THE POSSIBILITY OF SUCH DAMAGE.
24 */
25
26#include "config.h"
27#include "SuspendedPageProxy.h"
28
29#include "DrawingAreaProxy.h"
30#include "Logging.h"
31#include "WebPageMessages.h"
32#include "WebPageProxy.h"
33#include "WebPageProxyMessages.h"
34#include "WebProcessMessages.h"
35#include "WebProcessPool.h"
36#include "WebProcessProxy.h"
37#include <wtf/DebugUtilities.h>
38#include <wtf/HexNumber.h>
39#include <wtf/URL.h>
40
41namespace WebKit {
42using namespace WebCore;
43
44static const Seconds suspensionTimeout { 10_s };
45
46#if !LOG_DISABLED
47static const HashSet<IPC::StringReference>& messageNamesToIgnoreWhileSuspended()
48{
49 static NeverDestroyed<HashSet<IPC::StringReference>> messageNames;
50 static std::once_flag onceFlag;
51 std::call_once(onceFlag, [] {
52 messageNames.get().add("BackForwardAddItem");
53 messageNames.get().add("ClearAllEditCommands");
54 messageNames.get().add("DidChangeContentSize");
55 messageNames.get().add("DidChangeMainDocument");
56 messageNames.get().add("DidChangeProgress");
57 messageNames.get().add("DidCommitLoadForFrame");
58 messageNames.get().add("DidDestroyNavigation");
59 messageNames.get().add("DidFinishDocumentLoadForFrame");
60 messageNames.get().add("DidFinishProgress");
61 messageNames.get().add("DidCompletePageTransition");
62 messageNames.get().add("DidFirstLayoutForFrame");
63 messageNames.get().add("DidFirstVisuallyNonEmptyLayoutForFrame");
64 messageNames.get().add("DidNavigateWithNavigationData");
65 messageNames.get().add("DidReachLayoutMilestone");
66 messageNames.get().add("DidRestoreScrollPosition");
67 messageNames.get().add("DidSaveToPageCache");
68 messageNames.get().add("DidStartProgress");
69 messageNames.get().add("DidStartProvisionalLoadForFrame");
70 messageNames.get().add("EditorStateChanged");
71 messageNames.get().add("PageExtendedBackgroundColorDidChange");
72 messageNames.get().add("SetRenderTreeSize");
73 messageNames.get().add("SetStatusText");
74 messageNames.get().add("SetNetworkRequestsInProgress");
75 });
76
77 return messageNames;
78}
79#endif
80
81SuspendedPageProxy::SuspendedPageProxy(WebPageProxy& page, Ref<WebProcessProxy>&& process, uint64_t mainFrameID, ShouldDelayClosingUntilEnteringAcceleratedCompositingMode shouldDelayClosingUntilEnteringAcceleratedCompositingMode)
82 : m_page(page)
83 , m_process(WTFMove(process))
84 , m_mainFrameID(mainFrameID)
85 , m_shouldDelayClosingUntilEnteringAcceleratedCompositingMode(shouldDelayClosingUntilEnteringAcceleratedCompositingMode)
86 , m_suspensionTimeoutTimer(RunLoop::main(), this, &SuspendedPageProxy::suspensionTimedOut)
87#if PLATFORM(IOS_FAMILY)
88 , m_suspensionToken(m_process->throttler().backgroundActivityToken())
89#endif
90{
91 m_process->incrementSuspendedPageCount();
92 m_process->addMessageReceiver(Messages::WebPageProxy::messageReceiverName(), m_page.pageID(), *this);
93
94 m_suspensionTimeoutTimer.startOneShot(suspensionTimeout);
95 m_process->send(Messages::WebPage::SetIsSuspended(true), m_page.pageID());
96}
97
98SuspendedPageProxy::~SuspendedPageProxy()
99{
100 m_process->decrementSuspendedPageCount();
101
102 if (m_readyToUnsuspendHandler) {
103 RunLoop::main().dispatch([readyToUnsuspendHandler = WTFMove(m_readyToUnsuspendHandler)]() mutable {
104 readyToUnsuspendHandler(nullptr);
105 });
106 }
107
108 if (m_suspensionState == SuspensionState::Resumed)
109 return;
110
111 // If the suspended page was not consumed before getting destroyed, then close the corresponding page
112 // on the WebProcess side.
113 close();
114
115 if (m_suspensionState == SuspensionState::Suspending)
116 m_process->removeMessageReceiver(Messages::WebPageProxy::messageReceiverName(), m_page.pageID());
117
118 // We call maybeShutDown() asynchronously since the SuspendedPage is currently being removed from the WebProcessPool
119 // and we want to avoid re-entering WebProcessPool methods.
120 RunLoop::main().dispatch([process = m_process.copyRef()] {
121 process->maybeShutDown();
122 });
123}
124
125void SuspendedPageProxy::waitUntilReadyToUnsuspend(CompletionHandler<void(SuspendedPageProxy*)>&& completionHandler)
126{
127 if (m_readyToUnsuspendHandler)
128 m_readyToUnsuspendHandler(nullptr);
129
130 switch (m_suspensionState) {
131 case SuspensionState::Suspending:
132 m_readyToUnsuspendHandler = WTFMove(completionHandler);
133 break;
134 case SuspensionState::FailedToSuspend:
135 case SuspensionState::Suspended:
136 completionHandler(this);
137 break;
138 case SuspensionState::Resumed:
139 ASSERT_NOT_REACHED();
140 completionHandler(nullptr);
141 break;
142 }
143}
144
145void SuspendedPageProxy::unsuspend()
146{
147 ASSERT(m_suspensionState == SuspensionState::Suspended);
148
149 m_suspensionState = SuspensionState::Resumed;
150 m_process->send(Messages::WebPage::SetIsSuspended(false), m_page.pageID());
151}
152
153void SuspendedPageProxy::close()
154{
155 ASSERT(m_suspensionState != SuspensionState::Resumed);
156
157 if (m_isClosed)
158 return;
159
160 RELEASE_LOG(ProcessSwapping, "%p - SuspendedPageProxy::close()", this);
161 m_isClosed = true;
162 m_process->send(Messages::WebPage::Close(), m_page.pageID());
163}
164
165void SuspendedPageProxy::pageEnteredAcceleratedCompositingMode()
166{
167 m_shouldDelayClosingUntilEnteringAcceleratedCompositingMode = ShouldDelayClosingUntilEnteringAcceleratedCompositingMode::No;
168
169 if (m_shouldCloseWhenEnteringAcceleratedCompositingMode) {
170 // We needed the suspended page to stay alive to avoid flashing. Now we can get rid of it.
171 close();
172 }
173}
174
175bool SuspendedPageProxy::pageIsClosedOrClosing() const
176{
177 return m_isClosed || m_shouldCloseWhenEnteringAcceleratedCompositingMode;
178}
179
180void SuspendedPageProxy::closeWithoutFlashing()
181{
182 RELEASE_LOG(ProcessSwapping, "%p - SuspendedPageProxy::closeWithoutFlashing() shouldDelayClosingUntilEnteringAcceleratedCompositingMode? %d", this, m_shouldDelayClosingUntilEnteringAcceleratedCompositingMode == ShouldDelayClosingUntilEnteringAcceleratedCompositingMode::Yes);
183 if (m_shouldDelayClosingUntilEnteringAcceleratedCompositingMode == ShouldDelayClosingUntilEnteringAcceleratedCompositingMode::Yes) {
184 m_shouldCloseWhenEnteringAcceleratedCompositingMode = true;
185 return;
186 }
187 close();
188}
189
190void SuspendedPageProxy::didProcessRequestToSuspend(SuspensionState newSuspensionState)
191{
192 LOG(ProcessSwapping, "SuspendedPageProxy %s from process %i finished transition to suspended", loggingString(), m_process->processIdentifier());
193 RELEASE_LOG(ProcessSwapping, "%p - SuspendedPageProxy::didProcessRequestToSuspend() success? %d", this, newSuspensionState == SuspensionState::Suspended);
194
195 ASSERT(m_suspensionState == SuspensionState::Suspending);
196 ASSERT(newSuspensionState == SuspensionState::Suspended || newSuspensionState == SuspensionState::FailedToSuspend);
197
198 m_suspensionState = newSuspensionState;
199
200 m_suspensionTimeoutTimer.stop();
201
202#if PLATFORM(IOS_FAMILY)
203 m_suspensionToken = nullptr;
204#endif
205
206 m_process->removeMessageReceiver(Messages::WebPageProxy::messageReceiverName(), m_page.pageID());
207
208 if (m_suspensionState == SuspensionState::FailedToSuspend)
209 closeWithoutFlashing();
210
211 if (m_readyToUnsuspendHandler)
212 m_readyToUnsuspendHandler(this);
213}
214
215void SuspendedPageProxy::suspensionTimedOut()
216{
217 RELEASE_LOG_ERROR(ProcessSwapping, "%p - SuspendedPageProxy::suspensionTimedOut() destroying the suspended page because it failed to suspend in time", this);
218 m_process->processPool().removeSuspendedPage(*this); // Will destroy |this|.
219}
220
221void SuspendedPageProxy::didReceiveMessage(IPC::Connection&, IPC::Decoder& decoder)
222{
223 ASSERT(decoder.messageReceiverName() == Messages::WebPageProxy::messageReceiverName());
224
225 if (decoder.messageName() == Messages::WebPageProxy::DidSuspendAfterProcessSwap::name()) {
226 didProcessRequestToSuspend(SuspensionState::Suspended);
227 return;
228 }
229
230 if (decoder.messageName() == Messages::WebPageProxy::DidFailToSuspendAfterProcessSwap::name()) {
231 didProcessRequestToSuspend(SuspensionState::FailedToSuspend);
232 return;
233 }
234
235#if !LOG_DISABLED
236 if (!messageNamesToIgnoreWhileSuspended().contains(decoder.messageName()))
237 LOG(ProcessSwapping, "SuspendedPageProxy received unexpected WebPageProxy message '%s'", decoder.messageName().toString().data());
238#endif
239}
240
241void SuspendedPageProxy::didReceiveSyncMessage(IPC::Connection&, IPC::Decoder&, std::unique_ptr<IPC::Encoder>&)
242{
243}
244
245#if !LOG_DISABLED
246
247const char* SuspendedPageProxy::loggingString() const
248{
249 return debugString("(0x", hex(reinterpret_cast<uintptr_t>(this)), " page ID ", m_page.pageID().toUInt64(), ", m_suspensionState ", static_cast<unsigned>(m_suspensionState), ')');
250}
251
252#endif
253
254} // namespace WebKit
255