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 | |
41 | namespace WebKit { |
42 | using namespace WebCore; |
43 | |
44 | static const Seconds suspensionTimeout { 10_s }; |
45 | |
46 | #if !LOG_DISABLED |
47 | static 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 | |
81 | SuspendedPageProxy::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 | |
98 | SuspendedPageProxy::~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 | |
125 | void 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 | |
145 | void 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 | |
153 | void 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 | |
165 | void 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 | |
175 | bool SuspendedPageProxy::pageIsClosedOrClosing() const |
176 | { |
177 | return m_isClosed || m_shouldCloseWhenEnteringAcceleratedCompositingMode; |
178 | } |
179 | |
180 | void 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 | |
190 | void 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 | |
215 | void 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 | |
221 | void 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 | |
241 | void SuspendedPageProxy::didReceiveSyncMessage(IPC::Connection&, IPC::Decoder&, std::unique_ptr<IPC::Encoder>&) |
242 | { |
243 | } |
244 | |
245 | #if !LOG_DISABLED |
246 | |
247 | const 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 | |