1 | /* |
2 | * Copyright (C) 2011 Apple Inc. All rights reserved. |
3 | * Copyright (C) 2013 Nokia Corporation and/or its subsidiary(-ies). |
4 | * Copyright (C) 2016-2019 Igalia S.L. |
5 | * |
6 | * Redistribution and use in source and binary forms, with or without |
7 | * modification, are permitted provided that the following conditions |
8 | * are met: |
9 | * 1. Redistributions of source code must retain the above copyright |
10 | * notice, this list of conditions and the following disclaimer. |
11 | * 2. Redistributions in binary form must reproduce the above copyright |
12 | * notice, this list of conditions and the following disclaimer in the |
13 | * documentation and/or other materials provided with the distribution. |
14 | * |
15 | * THIS SOFTWARE IS PROVIDED BY APPLE INC. AND ITS CONTRIBUTORS ``AS IS'' |
16 | * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, |
17 | * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR |
18 | * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL APPLE INC. OR ITS CONTRIBUTORS |
19 | * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR |
20 | * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF |
21 | * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS |
22 | * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN |
23 | * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) |
24 | * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF |
25 | * THE POSSIBILITY OF SUCH DAMAGE. |
26 | */ |
27 | |
28 | #include "config.h" |
29 | #include "DrawingAreaProxyCoordinatedGraphics.h" |
30 | |
31 | #include "DrawingAreaMessages.h" |
32 | #include "DrawingAreaProxyMessages.h" |
33 | #include "LayerTreeContext.h" |
34 | #include "UpdateInfo.h" |
35 | #include "WebPageProxy.h" |
36 | #include "WebPreferences.h" |
37 | #include "WebProcessProxy.h" |
38 | #include <WebCore/PlatformDisplay.h> |
39 | #include <WebCore/Region.h> |
40 | |
41 | #if PLATFORM(GTK) |
42 | #include <gtk/gtk.h> |
43 | #endif |
44 | |
45 | #if USE(GLIB_EVENT_LOOP) |
46 | #include <wtf/glib/RunLoopSourcePriority.h> |
47 | #endif |
48 | |
49 | namespace WebKit { |
50 | using namespace WebCore; |
51 | |
52 | DrawingAreaProxyCoordinatedGraphics::DrawingAreaProxyCoordinatedGraphics(WebPageProxy& webPageProxy, WebProcessProxy& process) |
53 | : DrawingAreaProxy(DrawingAreaTypeCoordinatedGraphics, webPageProxy, process) |
54 | #if !PLATFORM(WPE) |
55 | , m_discardBackingStoreTimer(RunLoop::current(), this, &DrawingAreaProxyCoordinatedGraphics::discardBackingStore) |
56 | #endif |
57 | { |
58 | #if USE(GLIB_EVENT_LOOP) && !PLATFORM(WPE) |
59 | m_discardBackingStoreTimer.setPriority(RunLoopSourcePriority::ReleaseUnusedResourcesTimer); |
60 | #endif |
61 | } |
62 | |
63 | DrawingAreaProxyCoordinatedGraphics::~DrawingAreaProxyCoordinatedGraphics() |
64 | { |
65 | // Make sure to exit accelerated compositing mode. |
66 | if (isInAcceleratedCompositingMode()) |
67 | exitAcceleratedCompositingMode(); |
68 | } |
69 | |
70 | #if !PLATFORM(WPE) |
71 | void DrawingAreaProxyCoordinatedGraphics::paint(BackingStore::PlatformGraphicsContext context, const IntRect& rect, Region& unpaintedRegion) |
72 | { |
73 | unpaintedRegion = rect; |
74 | |
75 | if (isInAcceleratedCompositingMode()) |
76 | return; |
77 | |
78 | ASSERT(m_currentBackingStoreStateID <= m_nextBackingStoreStateID); |
79 | if (m_currentBackingStoreStateID < m_nextBackingStoreStateID) { |
80 | // Tell the web process to do a full backing store update now, in case we previously told |
81 | // it about our next state but didn't request an immediate update. |
82 | sendUpdateBackingStoreState(RespondImmediately); |
83 | |
84 | // If we haven't yet received our first bits from the WebProcess then don't paint anything. |
85 | if (!m_hasReceivedFirstUpdate) |
86 | return; |
87 | |
88 | if (m_isWaitingForDidUpdateBackingStoreState) { |
89 | // Wait for a DidUpdateBackingStoreState message that contains the new bits before we paint |
90 | // what's currently in the backing store. |
91 | waitForAndDispatchDidUpdateBackingStoreState(); |
92 | } |
93 | |
94 | // Dispatching DidUpdateBackingStoreState (either beneath sendUpdateBackingStoreState or |
95 | // beneath waitForAndDispatchDidUpdateBackingStoreState) could destroy our backing store or |
96 | // change the compositing mode. |
97 | if (!m_backingStore || isInAcceleratedCompositingMode()) |
98 | return; |
99 | } else { |
100 | ASSERT(!m_isWaitingForDidUpdateBackingStoreState); |
101 | if (!m_backingStore) { |
102 | // The view has asked us to paint before the web process has painted anything. There's |
103 | // nothing we can do. |
104 | return; |
105 | } |
106 | } |
107 | |
108 | m_backingStore->paint(context, rect); |
109 | unpaintedRegion.subtract(IntRect(IntPoint(), m_backingStore->size())); |
110 | |
111 | discardBackingStoreSoon(); |
112 | } |
113 | #endif |
114 | |
115 | void DrawingAreaProxyCoordinatedGraphics::sizeDidChange() |
116 | { |
117 | backingStoreStateDidChange(RespondImmediately); |
118 | } |
119 | |
120 | void DrawingAreaProxyCoordinatedGraphics::deviceScaleFactorDidChange() |
121 | { |
122 | backingStoreStateDidChange(RespondImmediately); |
123 | } |
124 | |
125 | void DrawingAreaProxyCoordinatedGraphics::waitForBackingStoreUpdateOnNextPaint() |
126 | { |
127 | m_hasReceivedFirstUpdate = true; |
128 | } |
129 | |
130 | void DrawingAreaProxyCoordinatedGraphics::setBackingStoreIsDiscardable(bool isBackingStoreDiscardable) |
131 | { |
132 | #if !PLATFORM(WPE) |
133 | if (m_isBackingStoreDiscardable == isBackingStoreDiscardable) |
134 | return; |
135 | |
136 | m_isBackingStoreDiscardable = isBackingStoreDiscardable; |
137 | if (m_isBackingStoreDiscardable) |
138 | discardBackingStoreSoon(); |
139 | else |
140 | m_discardBackingStoreTimer.stop(); |
141 | #endif |
142 | } |
143 | |
144 | void DrawingAreaProxyCoordinatedGraphics::update(uint64_t backingStoreStateID, const UpdateInfo& updateInfo) |
145 | { |
146 | ASSERT_ARG(backingStoreStateID, backingStoreStateID <= m_currentBackingStoreStateID); |
147 | if (backingStoreStateID < m_currentBackingStoreStateID) |
148 | return; |
149 | |
150 | // FIXME: Handle the case where the view is hidden. |
151 | |
152 | #if !PLATFORM(WPE) |
153 | incorporateUpdate(updateInfo); |
154 | #endif |
155 | send(Messages::DrawingArea::DidUpdate()); |
156 | } |
157 | |
158 | void DrawingAreaProxyCoordinatedGraphics::didUpdateBackingStoreState(uint64_t backingStoreStateID, const UpdateInfo& updateInfo, const LayerTreeContext& layerTreeContext) |
159 | { |
160 | ASSERT_ARG(backingStoreStateID, backingStoreStateID <= m_nextBackingStoreStateID); |
161 | ASSERT_ARG(backingStoreStateID, backingStoreStateID > m_currentBackingStoreStateID); |
162 | m_currentBackingStoreStateID = backingStoreStateID; |
163 | |
164 | m_isWaitingForDidUpdateBackingStoreState = false; |
165 | |
166 | // Stop the responsiveness timer that was started in sendUpdateBackingStoreState. |
167 | process().responsivenessTimer().stop(); |
168 | |
169 | if (layerTreeContext != m_layerTreeContext) { |
170 | if (layerTreeContext.isEmpty() && !m_layerTreeContext.isEmpty()) { |
171 | exitAcceleratedCompositingMode(); |
172 | ASSERT(m_layerTreeContext.isEmpty()); |
173 | } else if (!layerTreeContext.isEmpty() && m_layerTreeContext.isEmpty()) { |
174 | enterAcceleratedCompositingMode(layerTreeContext); |
175 | ASSERT(layerTreeContext == m_layerTreeContext); |
176 | } else { |
177 | updateAcceleratedCompositingMode(layerTreeContext); |
178 | ASSERT(layerTreeContext == m_layerTreeContext); |
179 | } |
180 | } |
181 | |
182 | if (m_nextBackingStoreStateID != m_currentBackingStoreStateID) |
183 | sendUpdateBackingStoreState(RespondImmediately); |
184 | else |
185 | m_hasReceivedFirstUpdate = true; |
186 | |
187 | #if !PLATFORM(WPE) |
188 | if (isInAcceleratedCompositingMode()) { |
189 | ASSERT(!m_backingStore); |
190 | return; |
191 | } |
192 | |
193 | // If we have a backing store the right size, reuse it. |
194 | if (m_backingStore && (m_backingStore->size() != updateInfo.viewSize || m_backingStore->deviceScaleFactor() != updateInfo.deviceScaleFactor)) |
195 | m_backingStore = nullptr; |
196 | incorporateUpdate(updateInfo); |
197 | #endif |
198 | } |
199 | |
200 | void DrawingAreaProxyCoordinatedGraphics::enterAcceleratedCompositingMode(uint64_t backingStoreStateID, const LayerTreeContext& layerTreeContext) |
201 | { |
202 | ASSERT_ARG(backingStoreStateID, backingStoreStateID <= m_currentBackingStoreStateID); |
203 | if (backingStoreStateID < m_currentBackingStoreStateID) |
204 | return; |
205 | |
206 | enterAcceleratedCompositingMode(layerTreeContext); |
207 | } |
208 | |
209 | void DrawingAreaProxyCoordinatedGraphics::exitAcceleratedCompositingMode(uint64_t backingStoreStateID, const UpdateInfo& updateInfo) |
210 | { |
211 | ASSERT_ARG(backingStoreStateID, backingStoreStateID <= m_currentBackingStoreStateID); |
212 | if (backingStoreStateID < m_currentBackingStoreStateID) |
213 | return; |
214 | |
215 | exitAcceleratedCompositingMode(); |
216 | #if !PLATFORM(WPE) |
217 | incorporateUpdate(updateInfo); |
218 | #endif |
219 | } |
220 | |
221 | void DrawingAreaProxyCoordinatedGraphics::updateAcceleratedCompositingMode(uint64_t backingStoreStateID, const LayerTreeContext& layerTreeContext) |
222 | { |
223 | ASSERT_ARG(backingStoreStateID, backingStoreStateID <= m_currentBackingStoreStateID); |
224 | if (backingStoreStateID < m_currentBackingStoreStateID) |
225 | return; |
226 | |
227 | updateAcceleratedCompositingMode(layerTreeContext); |
228 | } |
229 | |
230 | #if !PLATFORM(WPE) |
231 | void DrawingAreaProxyCoordinatedGraphics::incorporateUpdate(const UpdateInfo& updateInfo) |
232 | { |
233 | ASSERT(!isInAcceleratedCompositingMode()); |
234 | |
235 | if (updateInfo.updateRectBounds.isEmpty()) |
236 | return; |
237 | |
238 | if (!m_backingStore) |
239 | m_backingStore = std::make_unique<BackingStore>(updateInfo.viewSize, updateInfo.deviceScaleFactor, m_webPageProxy); |
240 | |
241 | m_backingStore->incorporateUpdate(updateInfo); |
242 | |
243 | Region damageRegion; |
244 | if (updateInfo.scrollRect.isEmpty()) { |
245 | for (const auto& rect : updateInfo.updateRects) |
246 | damageRegion.unite(rect); |
247 | } else |
248 | damageRegion = IntRect(IntPoint(), m_webPageProxy.viewSize()); |
249 | m_webPageProxy.setViewNeedsDisplay(damageRegion); |
250 | } |
251 | #endif |
252 | |
253 | bool DrawingAreaProxyCoordinatedGraphics::alwaysUseCompositing() const |
254 | { |
255 | return m_webPageProxy.preferences().acceleratedCompositingEnabled() && m_webPageProxy.preferences().forceCompositingMode(); |
256 | } |
257 | |
258 | void DrawingAreaProxyCoordinatedGraphics::enterAcceleratedCompositingMode(const LayerTreeContext& layerTreeContext) |
259 | { |
260 | ASSERT(!isInAcceleratedCompositingMode()); |
261 | #if !PLATFORM(WPE) |
262 | m_backingStore = nullptr; |
263 | #endif |
264 | m_layerTreeContext = layerTreeContext; |
265 | m_webPageProxy.enterAcceleratedCompositingMode(layerTreeContext); |
266 | } |
267 | |
268 | void DrawingAreaProxyCoordinatedGraphics::exitAcceleratedCompositingMode() |
269 | { |
270 | ASSERT(isInAcceleratedCompositingMode()); |
271 | |
272 | m_layerTreeContext = { }; |
273 | m_webPageProxy.exitAcceleratedCompositingMode(); |
274 | } |
275 | |
276 | void DrawingAreaProxyCoordinatedGraphics::updateAcceleratedCompositingMode(const LayerTreeContext& layerTreeContext) |
277 | { |
278 | ASSERT(isInAcceleratedCompositingMode()); |
279 | |
280 | m_layerTreeContext = layerTreeContext; |
281 | m_webPageProxy.updateAcceleratedCompositingMode(layerTreeContext); |
282 | } |
283 | |
284 | void DrawingAreaProxyCoordinatedGraphics::backingStoreStateDidChange(RespondImmediatelyOrNot respondImmediatelyOrNot) |
285 | { |
286 | ++m_nextBackingStoreStateID; |
287 | sendUpdateBackingStoreState(respondImmediatelyOrNot); |
288 | } |
289 | |
290 | void DrawingAreaProxyCoordinatedGraphics::sendUpdateBackingStoreState(RespondImmediatelyOrNot respondImmediatelyOrNot) |
291 | { |
292 | ASSERT(m_currentBackingStoreStateID < m_nextBackingStoreStateID); |
293 | |
294 | if (!m_webPageProxy.hasRunningProcess()) |
295 | return; |
296 | |
297 | if (m_isWaitingForDidUpdateBackingStoreState) |
298 | return; |
299 | |
300 | if (m_webPageProxy.viewSize().isEmpty() && !m_webPageProxy.useFixedLayout()) |
301 | return; |
302 | |
303 | m_isWaitingForDidUpdateBackingStoreState = respondImmediatelyOrNot == RespondImmediately; |
304 | |
305 | send(Messages::DrawingArea::UpdateBackingStoreState(m_nextBackingStoreStateID, respondImmediatelyOrNot == RespondImmediately, m_webPageProxy.deviceScaleFactor(), m_size, m_scrollOffset)); |
306 | m_scrollOffset = IntSize(); |
307 | |
308 | if (m_isWaitingForDidUpdateBackingStoreState) { |
309 | // Start the responsiveness timer. We will stop it when we hear back from the WebProcess |
310 | // in didUpdateBackingStoreState. |
311 | process().responsivenessTimer().start(); |
312 | } |
313 | |
314 | if (m_isWaitingForDidUpdateBackingStoreState && !m_layerTreeContext.isEmpty()) { |
315 | // Wait for the DidUpdateBackingStoreState message. Normally we do this in DrawingAreaProxyCoordinatedGraphics::paint, but that |
316 | // function is never called when in accelerated compositing mode. |
317 | waitForAndDispatchDidUpdateBackingStoreState(); |
318 | } |
319 | } |
320 | |
321 | void DrawingAreaProxyCoordinatedGraphics::waitForAndDispatchDidUpdateBackingStoreState() |
322 | { |
323 | ASSERT(m_isWaitingForDidUpdateBackingStoreState); |
324 | |
325 | if (!m_webPageProxy.hasRunningProcess()) |
326 | return; |
327 | if (process().state() == WebProcessProxy::State::Launching) |
328 | return; |
329 | if (!m_webPageProxy.isViewVisible()) |
330 | return; |
331 | #if PLATFORM(WAYLAND) && USE(EGL) |
332 | // Never block the UI process in Wayland when waiting for DidUpdateBackingStoreState after a resize, |
333 | // because the nested compositor needs to handle the web process requests that happens while resizing. |
334 | if (PlatformDisplay::sharedDisplay().type() == PlatformDisplay::Type::Wayland && isInAcceleratedCompositingMode()) |
335 | return; |
336 | #endif |
337 | |
338 | // FIXME: waitForAndDispatchImmediately will always return the oldest DidUpdateBackingStoreState message that |
339 | // hasn't yet been processed. But it might be better to skip ahead to some other DidUpdateBackingStoreState |
340 | // message, if multiple DidUpdateBackingStoreState messages are waiting to be processed. For instance, we could |
341 | // choose the most recent one, or the one that is closest to our current size. |
342 | |
343 | // The timeout, in seconds, we use when waiting for a DidUpdateBackingStoreState message when we're asked to paint. |
344 | process().connection()->waitForAndDispatchImmediately<Messages::DrawingAreaProxy::DidUpdateBackingStoreState>(m_identifier.toUInt64(), Seconds::fromMilliseconds(500)); |
345 | } |
346 | |
347 | #if !PLATFORM(WPE) |
348 | void DrawingAreaProxyCoordinatedGraphics::discardBackingStoreSoon() |
349 | { |
350 | if (!m_backingStore || !m_isBackingStoreDiscardable || m_discardBackingStoreTimer.isActive()) |
351 | return; |
352 | |
353 | // We'll wait this many seconds after the last paint before throwing away our backing store to save memory. |
354 | // FIXME: It would be smarter to make this delay based on how expensive painting is. See <http://webkit.org/b/55733>. |
355 | static const Seconds discardBackingStoreDelay = 2_s; |
356 | |
357 | m_discardBackingStoreTimer.startOneShot(discardBackingStoreDelay); |
358 | } |
359 | |
360 | void DrawingAreaProxyCoordinatedGraphics::discardBackingStore() |
361 | { |
362 | if (!m_backingStore) |
363 | return; |
364 | m_backingStore = nullptr; |
365 | backingStoreStateDidChange(DoNotRespondImmediately); |
366 | } |
367 | #endif |
368 | |
369 | DrawingAreaProxyCoordinatedGraphics::DrawingMonitor::DrawingMonitor(WebPageProxy& webPage) |
370 | : m_timer(RunLoop::main(), this, &DrawingMonitor::stop) |
371 | #if PLATFORM(GTK) |
372 | , m_webPage(webPage) |
373 | #endif |
374 | { |
375 | #if USE(GLIB_EVENT_LOOP) |
376 | #if PLATFORM(GTK) |
377 | // Give redraws more priority. |
378 | m_timer.setPriority(GDK_PRIORITY_REDRAW - 10); |
379 | #else |
380 | m_timer.setPriority(RunLoopSourcePriority::RunLoopDispatcher); |
381 | #endif |
382 | #endif |
383 | } |
384 | |
385 | DrawingAreaProxyCoordinatedGraphics::DrawingMonitor::~DrawingMonitor() |
386 | { |
387 | m_callback = nullptr; |
388 | stop(); |
389 | } |
390 | |
391 | int DrawingAreaProxyCoordinatedGraphics::DrawingMonitor::webViewDrawCallback(DrawingAreaProxyCoordinatedGraphics::DrawingMonitor* monitor) |
392 | { |
393 | monitor->didDraw(); |
394 | return FALSE; |
395 | } |
396 | |
397 | void DrawingAreaProxyCoordinatedGraphics::DrawingMonitor::start(WTF::Function<void(CallbackBase::Error)>&& callback) |
398 | { |
399 | m_startTime = MonotonicTime::now(); |
400 | m_callback = WTFMove(callback); |
401 | #if PLATFORM(GTK) |
402 | g_signal_connect_swapped(m_webPage.viewWidget(), "draw" , reinterpret_cast<GCallback>(webViewDrawCallback), this); |
403 | m_timer.startOneShot(1_s); |
404 | #else |
405 | m_timer.startOneShot(0_s); |
406 | #endif |
407 | } |
408 | |
409 | void DrawingAreaProxyCoordinatedGraphics::DrawingMonitor::stop() |
410 | { |
411 | m_timer.stop(); |
412 | #if PLATFORM(GTK) |
413 | g_signal_handlers_disconnect_by_func(m_webPage.viewWidget(), reinterpret_cast<gpointer>(webViewDrawCallback), this); |
414 | #endif |
415 | m_startTime = MonotonicTime(); |
416 | if (m_callback) { |
417 | m_callback(CallbackBase::Error::None); |
418 | m_callback = nullptr; |
419 | } |
420 | } |
421 | |
422 | void DrawingAreaProxyCoordinatedGraphics::DrawingMonitor::didDraw() |
423 | { |
424 | // We wait up to 1 second for draw events. If there are several draw events queued quickly, |
425 | // we want to wait until all of them have been processed, so after receiving a draw, we wait |
426 | // up to 100ms for the next one or stop. |
427 | if (MonotonicTime::now() - m_startTime > 1_s) |
428 | stop(); |
429 | else |
430 | m_timer.startOneShot(100_ms); |
431 | } |
432 | |
433 | void DrawingAreaProxyCoordinatedGraphics::dispatchAfterEnsuringDrawing(WTF::Function<void(CallbackBase::Error)>&& callbackFunction) |
434 | { |
435 | if (!m_webPageProxy.hasRunningProcess()) { |
436 | callbackFunction(CallbackBase::Error::OwnerWasInvalidated); |
437 | return; |
438 | } |
439 | |
440 | if (!m_drawingMonitor) |
441 | m_drawingMonitor = std::make_unique<DrawingAreaProxyCoordinatedGraphics::DrawingMonitor>(m_webPageProxy); |
442 | m_drawingMonitor->start(WTFMove(callbackFunction)); |
443 | } |
444 | |
445 | } // namespace WebKit |
446 | |