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
49namespace WebKit {
50using namespace WebCore;
51
52DrawingAreaProxyCoordinatedGraphics::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
63DrawingAreaProxyCoordinatedGraphics::~DrawingAreaProxyCoordinatedGraphics()
64{
65 // Make sure to exit accelerated compositing mode.
66 if (isInAcceleratedCompositingMode())
67 exitAcceleratedCompositingMode();
68}
69
70#if !PLATFORM(WPE)
71void 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
115void DrawingAreaProxyCoordinatedGraphics::sizeDidChange()
116{
117 backingStoreStateDidChange(RespondImmediately);
118}
119
120void DrawingAreaProxyCoordinatedGraphics::deviceScaleFactorDidChange()
121{
122 backingStoreStateDidChange(RespondImmediately);
123}
124
125void DrawingAreaProxyCoordinatedGraphics::waitForBackingStoreUpdateOnNextPaint()
126{
127 m_hasReceivedFirstUpdate = true;
128}
129
130void 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
144void 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
158void 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
200void 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
209void 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
221void 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)
231void 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
253bool DrawingAreaProxyCoordinatedGraphics::alwaysUseCompositing() const
254{
255 return m_webPageProxy.preferences().acceleratedCompositingEnabled() && m_webPageProxy.preferences().forceCompositingMode();
256}
257
258void 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
268void DrawingAreaProxyCoordinatedGraphics::exitAcceleratedCompositingMode()
269{
270 ASSERT(isInAcceleratedCompositingMode());
271
272 m_layerTreeContext = { };
273 m_webPageProxy.exitAcceleratedCompositingMode();
274}
275
276void DrawingAreaProxyCoordinatedGraphics::updateAcceleratedCompositingMode(const LayerTreeContext& layerTreeContext)
277{
278 ASSERT(isInAcceleratedCompositingMode());
279
280 m_layerTreeContext = layerTreeContext;
281 m_webPageProxy.updateAcceleratedCompositingMode(layerTreeContext);
282}
283
284void DrawingAreaProxyCoordinatedGraphics::backingStoreStateDidChange(RespondImmediatelyOrNot respondImmediatelyOrNot)
285{
286 ++m_nextBackingStoreStateID;
287 sendUpdateBackingStoreState(respondImmediatelyOrNot);
288}
289
290void 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
321void 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)
348void 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
360void DrawingAreaProxyCoordinatedGraphics::discardBackingStore()
361{
362 if (!m_backingStore)
363 return;
364 m_backingStore = nullptr;
365 backingStoreStateDidChange(DoNotRespondImmediately);
366}
367#endif
368
369DrawingAreaProxyCoordinatedGraphics::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
385DrawingAreaProxyCoordinatedGraphics::DrawingMonitor::~DrawingMonitor()
386{
387 m_callback = nullptr;
388 stop();
389}
390
391int DrawingAreaProxyCoordinatedGraphics::DrawingMonitor::webViewDrawCallback(DrawingAreaProxyCoordinatedGraphics::DrawingMonitor* monitor)
392{
393 monitor->didDraw();
394 return FALSE;
395}
396
397void 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
409void 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
422void 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
433void 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