1/*
2 * Copyright (C) 2011 Apple Inc. All rights reserved.
3 * Copyright (C) 2013 Nokia Corporation and/or its subsidiary(-ies).
4 *
5 * Redistribution and use in source and binary forms, with or without
6 * modification, are permitted provided that the following conditions
7 * are met:
8 * 1. Redistributions of source code must retain the above copyright
9 *    notice, this list of conditions and the following disclaimer.
10 * 2. Redistributions in binary form must reproduce the above copyright
11 *    notice, this list of conditions and the following disclaimer in the
12 *    documentation and/or other materials provided with the distribution.
13 *
14 * THIS SOFTWARE IS PROVIDED BY APPLE INC. AND ITS CONTRIBUTORS ``AS IS''
15 * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
16 * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
17 * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL APPLE INC. OR ITS CONTRIBUTORS
18 * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
19 * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
20 * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
21 * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
22 * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
23 * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF
24 * THE POSSIBILITY OF SUCH DAMAGE.
25 */
26
27#include "config.h"
28
29#if USE(COORDINATED_GRAPHICS)
30#include "CoordinatedDrawingAreaProxy.h"
31
32#include "CoordinatedLayerTreeHostProxy.h"
33#include "DrawingAreaMessages.h"
34#include "DrawingAreaProxyMessages.h"
35#include "LayerTreeContext.h"
36#include "UpdateInfo.h"
37#include "WebPageGroup.h"
38#include "WebPageProxy.h"
39#include "WebPreferences.h"
40#include "WebProcessProxy.h"
41#include <WebCore/Region.h>
42
43using namespace WebCore;
44
45namespace WebKit {
46
47CoordinatedDrawingAreaProxy::CoordinatedDrawingAreaProxy(WebPageProxy* webPageProxy)
48    : DrawingAreaProxy(DrawingAreaTypeCoordinated, webPageProxy)
49    , m_currentBackingStoreStateID(0)
50    , m_nextBackingStoreStateID(0)
51    , m_isWaitingForDidUpdateBackingStoreState(false)
52    , m_hasReceivedFirstUpdate(false)
53    , m_isBackingStoreDiscardable(true)
54    , m_discardBackingStoreTimer(RunLoop::current(), this, &CoordinatedDrawingAreaProxy::discardBackingStore)
55{
56    // Construct the proxy early to allow messages to be sent to the web process while AC is entered there.
57    if (webPageProxy->pageGroup().preferences().forceCompositingMode())
58        m_coordinatedLayerTreeHostProxy = adoptPtr(new CoordinatedLayerTreeHostProxy(this));
59}
60
61CoordinatedDrawingAreaProxy::~CoordinatedDrawingAreaProxy()
62{
63    // Make sure to exit accelerated compositing mode.
64    if (isInAcceleratedCompositingMode())
65        exitAcceleratedCompositingMode();
66}
67
68void CoordinatedDrawingAreaProxy::paint(BackingStore::PlatformGraphicsContext context, const IntRect& rect, Region& unpaintedRegion)
69{
70    unpaintedRegion = rect;
71
72    if (isInAcceleratedCompositingMode())
73        return;
74
75    ASSERT(m_currentBackingStoreStateID <= m_nextBackingStoreStateID);
76    if (m_currentBackingStoreStateID < m_nextBackingStoreStateID) {
77        // Tell the web process to do a full backing store update now, in case we previously told
78        // it about our next state but didn't request an immediate update.
79        sendUpdateBackingStoreState(RespondImmediately);
80
81        // If we haven't yet received our first bits from the WebProcess then don't paint anything.
82        if (!m_hasReceivedFirstUpdate)
83            return;
84
85        if (m_isWaitingForDidUpdateBackingStoreState) {
86            // Wait for a DidUpdateBackingStoreState message that contains the new bits before we paint
87            // what's currently in the backing store.
88            waitForAndDispatchDidUpdateBackingStoreState();
89        }
90
91        // Dispatching DidUpdateBackingStoreState (either beneath sendUpdateBackingStoreState or
92        // beneath waitForAndDispatchDidUpdateBackingStoreState) could destroy our backing store or
93        // change the compositing mode.
94        if (!m_backingStore || isInAcceleratedCompositingMode())
95            return;
96    } else {
97        ASSERT(!m_isWaitingForDidUpdateBackingStoreState);
98        if (!m_backingStore) {
99            // The view has asked us to paint before the web process has painted anything. There's
100            // nothing we can do.
101            return;
102        }
103    }
104
105    m_backingStore->paint(context, rect);
106    unpaintedRegion.subtract(IntRect(IntPoint(), m_backingStore->size()));
107
108    discardBackingStoreSoon();
109}
110
111void CoordinatedDrawingAreaProxy::updateViewport()
112{
113    m_webPageProxy->setViewNeedsDisplay(viewportVisibleRect());
114}
115
116WebCore::IntRect CoordinatedDrawingAreaProxy::contentsRect() const
117{
118    return IntRect(IntPoint::zero(), m_webPageProxy->viewSize());
119}
120
121void CoordinatedDrawingAreaProxy::sizeDidChange()
122{
123    backingStoreStateDidChange(RespondImmediately);
124}
125
126void CoordinatedDrawingAreaProxy::deviceScaleFactorDidChange()
127{
128    backingStoreStateDidChange(RespondImmediately);
129}
130
131void CoordinatedDrawingAreaProxy::visibilityDidChange()
132{
133    // If we don't have a backing store, go ahead and mark the backing store as being changed so
134    // that when paint we'll actually wait for something to paint and not flash white.
135    if (!m_backingStore && m_layerTreeContext.isEmpty())
136        backingStoreStateDidChange(DoNotRespondImmediately);
137}
138
139void CoordinatedDrawingAreaProxy::setBackingStoreIsDiscardable(bool isBackingStoreDiscardable)
140{
141    if (m_isBackingStoreDiscardable == isBackingStoreDiscardable)
142        return;
143
144    m_isBackingStoreDiscardable = isBackingStoreDiscardable;
145    if (m_isBackingStoreDiscardable)
146        discardBackingStoreSoon();
147    else
148        m_discardBackingStoreTimer.stop();
149}
150
151void CoordinatedDrawingAreaProxy::waitForBackingStoreUpdateOnNextPaint()
152{
153    m_hasReceivedFirstUpdate = true;
154}
155
156void CoordinatedDrawingAreaProxy::update(uint64_t backingStoreStateID, const UpdateInfo& updateInfo)
157{
158    ASSERT_ARG(backingStoreStateID, backingStoreStateID <= m_currentBackingStoreStateID);
159    if (backingStoreStateID < m_currentBackingStoreStateID)
160        return;
161
162    // FIXME: Handle the case where the view is hidden.
163
164    incorporateUpdate(updateInfo);
165    m_webPageProxy->process().send(Messages::DrawingArea::DidUpdate(), m_webPageProxy->pageID());
166}
167
168void CoordinatedDrawingAreaProxy::didUpdateBackingStoreState(uint64_t backingStoreStateID, const UpdateInfo& updateInfo, const LayerTreeContext& layerTreeContext)
169{
170    ASSERT_ARG(backingStoreStateID, backingStoreStateID <= m_nextBackingStoreStateID);
171    ASSERT_ARG(backingStoreStateID, backingStoreStateID > m_currentBackingStoreStateID);
172    m_currentBackingStoreStateID = backingStoreStateID;
173
174    m_isWaitingForDidUpdateBackingStoreState = false;
175
176    // Stop the responsiveness timer that was started in sendUpdateBackingStoreState.
177    m_webPageProxy->process().responsivenessTimer()->stop();
178
179    if (layerTreeContext != m_layerTreeContext) {
180        if (!m_layerTreeContext.isEmpty()) {
181            exitAcceleratedCompositingMode();
182            ASSERT(m_layerTreeContext.isEmpty());
183        }
184
185        if (!layerTreeContext.isEmpty()) {
186            enterAcceleratedCompositingMode(layerTreeContext);
187            ASSERT(layerTreeContext == m_layerTreeContext);
188        }
189    }
190
191    if (m_nextBackingStoreStateID != m_currentBackingStoreStateID)
192        sendUpdateBackingStoreState(RespondImmediately);
193    else
194        m_hasReceivedFirstUpdate = true;
195
196    if (isInAcceleratedCompositingMode()) {
197        ASSERT(!m_backingStore);
198        return;
199    }
200
201    // If we have a backing store the right size, reuse it.
202    if (m_backingStore && (m_backingStore->size() != updateInfo.viewSize || m_backingStore->deviceScaleFactor() != updateInfo.deviceScaleFactor))
203        m_backingStore = nullptr;
204    incorporateUpdate(updateInfo);
205}
206
207void CoordinatedDrawingAreaProxy::enterAcceleratedCompositingMode(uint64_t backingStoreStateID, const LayerTreeContext& layerTreeContext)
208{
209    ASSERT_ARG(backingStoreStateID, backingStoreStateID <= m_currentBackingStoreStateID);
210    if (backingStoreStateID < m_currentBackingStoreStateID)
211        return;
212
213    enterAcceleratedCompositingMode(layerTreeContext);
214}
215
216void CoordinatedDrawingAreaProxy::exitAcceleratedCompositingMode(uint64_t backingStoreStateID, const UpdateInfo& updateInfo)
217{
218    ASSERT_ARG(backingStoreStateID, backingStoreStateID <= m_currentBackingStoreStateID);
219    if (backingStoreStateID < m_currentBackingStoreStateID)
220        return;
221
222    exitAcceleratedCompositingMode();
223
224    incorporateUpdate(updateInfo);
225}
226
227void CoordinatedDrawingAreaProxy::updateAcceleratedCompositingMode(uint64_t backingStoreStateID, const LayerTreeContext& layerTreeContext)
228{
229    ASSERT_ARG(backingStoreStateID, backingStoreStateID <= m_currentBackingStoreStateID);
230    if (backingStoreStateID < m_currentBackingStoreStateID)
231        return;
232
233    updateAcceleratedCompositingMode(layerTreeContext);
234}
235
236void CoordinatedDrawingAreaProxy::incorporateUpdate(const UpdateInfo& updateInfo)
237{
238    ASSERT(!isInAcceleratedCompositingMode());
239
240    if (updateInfo.updateRectBounds.isEmpty())
241        return;
242
243    if (!m_backingStore)
244        m_backingStore = std::make_unique<BackingStore>(updateInfo.viewSize, updateInfo.deviceScaleFactor, m_webPageProxy);
245
246    m_backingStore->incorporateUpdate(updateInfo);
247
248    bool shouldScroll = !updateInfo.scrollRect.isEmpty();
249
250    if (shouldScroll)
251        m_webPageProxy->scrollView(updateInfo.scrollRect, updateInfo.scrollOffset);
252
253    if (shouldScroll && !m_webPageProxy->canScrollView())
254        m_webPageProxy->setViewNeedsDisplay(IntRect(IntPoint(), m_webPageProxy->viewSize()));
255    else {
256        for (size_t i = 0; i < updateInfo.updateRects.size(); ++i)
257            m_webPageProxy->setViewNeedsDisplay(updateInfo.updateRects[i]);
258    }
259
260    if (shouldScroll)
261        m_webPageProxy->displayView();
262}
263
264void CoordinatedDrawingAreaProxy::backingStoreStateDidChange(RespondImmediatelyOrNot respondImmediatelyOrNot)
265{
266    ++m_nextBackingStoreStateID;
267    sendUpdateBackingStoreState(respondImmediatelyOrNot);
268}
269
270void CoordinatedDrawingAreaProxy::sendUpdateBackingStoreState(RespondImmediatelyOrNot respondImmediatelyOrNot)
271{
272    ASSERT(m_currentBackingStoreStateID < m_nextBackingStoreStateID);
273
274    if (!m_webPageProxy->isValid())
275        return;
276
277    if (m_isWaitingForDidUpdateBackingStoreState)
278        return;
279
280    if (m_webPageProxy->viewSize().isEmpty() && !m_webPageProxy->useFixedLayout())
281        return;
282
283    m_isWaitingForDidUpdateBackingStoreState = respondImmediatelyOrNot == RespondImmediately;
284
285    m_webPageProxy->process().send(Messages::DrawingArea::UpdateBackingStoreState(m_nextBackingStoreStateID, respondImmediatelyOrNot == RespondImmediately, m_webPageProxy->deviceScaleFactor(), m_size, m_scrollOffset), m_webPageProxy->pageID());
286    m_scrollOffset = IntSize();
287
288    if (m_isWaitingForDidUpdateBackingStoreState) {
289        // Start the responsiveness timer. We will stop it when we hear back from the WebProcess
290        // in didUpdateBackingStoreState.
291        m_webPageProxy->process().responsivenessTimer()->start();
292    }
293
294    if (m_isWaitingForDidUpdateBackingStoreState && !m_layerTreeContext.isEmpty()) {
295        // Wait for the DidUpdateBackingStoreState message. Normally we do this in CoordinatedDrawingAreaProxy::paint, but that
296        // function is never called when in accelerated compositing mode.
297        waitForAndDispatchDidUpdateBackingStoreState();
298    }
299}
300
301void CoordinatedDrawingAreaProxy::waitForAndDispatchDidUpdateBackingStoreState()
302{
303    ASSERT(m_isWaitingForDidUpdateBackingStoreState);
304
305    if (!m_webPageProxy->isValid())
306        return;
307    if (m_webPageProxy->process().state() == WebProcessProxy::State::Launching)
308        return;
309
310    // FIXME: waitForAndDispatchImmediately will always return the oldest DidUpdateBackingStoreState message that
311    // hasn't yet been processed. But it might be better to skip ahead to some other DidUpdateBackingStoreState
312    // message, if multiple DidUpdateBackingStoreState messages are waiting to be processed. For instance, we could
313    // choose the most recent one, or the one that is closest to our current size.
314
315    // The timeout, in seconds, we use when waiting for a DidUpdateBackingStoreState message when we're asked to paint.
316    m_webPageProxy->process().connection()->waitForAndDispatchImmediately<Messages::DrawingAreaProxy::DidUpdateBackingStoreState>(m_webPageProxy->pageID(), std::chrono::milliseconds(500));
317}
318
319void CoordinatedDrawingAreaProxy::enterAcceleratedCompositingMode(const LayerTreeContext& layerTreeContext)
320{
321    ASSERT(!isInAcceleratedCompositingMode());
322
323    m_backingStore = nullptr;
324    m_layerTreeContext = layerTreeContext;
325    m_webPageProxy->enterAcceleratedCompositingMode(layerTreeContext);
326    if (!m_coordinatedLayerTreeHostProxy)
327        m_coordinatedLayerTreeHostProxy = adoptPtr(new CoordinatedLayerTreeHostProxy(this));
328}
329
330void CoordinatedDrawingAreaProxy::setVisibleContentsRect(const WebCore::FloatRect& visibleContentsRect, const WebCore::FloatPoint& trajectoryVector)
331{
332    if (m_coordinatedLayerTreeHostProxy)
333        m_coordinatedLayerTreeHostProxy->setVisibleContentsRect(visibleContentsRect, trajectoryVector);
334}
335
336void CoordinatedDrawingAreaProxy::exitAcceleratedCompositingMode()
337{
338    ASSERT(isInAcceleratedCompositingMode());
339
340    m_layerTreeContext = LayerTreeContext();
341    m_webPageProxy->exitAcceleratedCompositingMode();
342}
343
344void CoordinatedDrawingAreaProxy::updateAcceleratedCompositingMode(const LayerTreeContext& layerTreeContext)
345{
346    ASSERT(isInAcceleratedCompositingMode());
347
348    m_layerTreeContext = layerTreeContext;
349    m_webPageProxy->updateAcceleratedCompositingMode(layerTreeContext);
350}
351
352void CoordinatedDrawingAreaProxy::discardBackingStoreSoon()
353{
354    if (!m_isBackingStoreDiscardable || m_discardBackingStoreTimer.isActive())
355        return;
356
357    // We'll wait this many seconds after the last paint before throwing away our backing store to save memory.
358    // FIXME: It would be smarter to make this delay based on how expensive painting is. See <http://webkit.org/b/55733>.
359    static const double discardBackingStoreDelay = 2;
360
361    m_discardBackingStoreTimer.startOneShot(discardBackingStoreDelay);
362}
363
364void CoordinatedDrawingAreaProxy::discardBackingStore()
365{
366    m_backingStore = nullptr;
367    backingStoreStateDidChange(DoNotRespondImmediately);
368}
369
370} // namespace WebKit
371#endif // USE(COORDINATED_GRAPHICS)
372