1/*
2 * Copyright (C) 2011 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 "DrawingAreaProxyImpl.h"
28
29#include "DrawingAreaMessages.h"
30#include "DrawingAreaProxyMessages.h"
31#include "LayerTreeContext.h"
32#include "Region.h"
33#include "UpdateInfo.h"
34#include "WebPageProxy.h"
35#include "WebProcessProxy.h"
36
37#if !PLATFORM(MAC) && !PLATFORM(WIN)
38#error "This drawing area is not ready for use by other ports yet."
39#endif
40
41using namespace WebCore;
42
43namespace WebKit {
44
45PassOwnPtr<DrawingAreaProxyImpl> DrawingAreaProxyImpl::create(WebPageProxy* webPageProxy)
46{
47    return adoptPtr(new DrawingAreaProxyImpl(webPageProxy));
48}
49
50DrawingAreaProxyImpl::DrawingAreaProxyImpl(WebPageProxy* webPageProxy)
51    : DrawingAreaProxy(DrawingAreaTypeImpl, webPageProxy)
52    , m_currentBackingStoreStateID(0)
53    , m_nextBackingStoreStateID(0)
54    , m_isWaitingForDidUpdateBackingStoreState(false)
55    , m_isBackingStoreDiscardable(true)
56    , m_discardBackingStoreTimer(RunLoop::current(), this, &DrawingAreaProxyImpl::discardBackingStore)
57{
58}
59
60DrawingAreaProxyImpl::~DrawingAreaProxyImpl()
61{
62#if USE(ACCELERATED_COMPOSITING)
63    // Make sure to exit accelerated compositing mode.
64    if (isInAcceleratedCompositingMode())
65        exitAcceleratedCompositingMode();
66#endif
67}
68
69void DrawingAreaProxyImpl::paint(BackingStore::PlatformGraphicsContext context, const IntRect& rect, Region& unpaintedRegion)
70{
71    unpaintedRegion = rect;
72
73    if (isInAcceleratedCompositingMode())
74        return;
75
76    ASSERT(m_currentBackingStoreStateID <= m_nextBackingStoreStateID);
77    if (m_currentBackingStoreStateID < m_nextBackingStoreStateID) {
78        // Tell the web process to do a full backing store update now, in case we previously told
79        // it about our next state but didn't request an immediate update.
80        sendUpdateBackingStoreState(RespondImmediately);
81
82        if (m_isWaitingForDidUpdateBackingStoreState) {
83            // Wait for a DidUpdateBackingStoreState message that contains the new bits before we paint
84            // what's currently in the backing store.
85            waitForAndDispatchDidUpdateBackingStoreState();
86        }
87
88        // Dispatching DidUpdateBackingStoreState (either beneath sendUpdateBackingStoreState or
89        // beneath waitForAndDispatchDidUpdateBackingStoreState) could destroy our backing store or
90        // change the compositing mode.
91        if (!m_backingStore || isInAcceleratedCompositingMode())
92            return;
93    } else {
94        ASSERT(!m_isWaitingForDidUpdateBackingStoreState);
95        if (!m_backingStore) {
96            // The view has asked us to paint before the web process has painted anything. There's
97            // nothing we can do.
98            return;
99        }
100    }
101
102    m_backingStore->paint(context, rect);
103    unpaintedRegion.subtract(IntRect(IntPoint(), m_backingStore->size()));
104
105    discardBackingStoreSoon();
106}
107
108void DrawingAreaProxyImpl::didReceiveMessage(CoreIPC::Connection*, CoreIPC::MessageID, CoreIPC::ArgumentDecoder*)
109{
110    ASSERT_NOT_REACHED();
111}
112
113void DrawingAreaProxyImpl::didReceiveSyncMessage(CoreIPC::Connection*, CoreIPC::MessageID, CoreIPC::ArgumentDecoder*, CoreIPC::ArgumentEncoder*)
114{
115    ASSERT_NOT_REACHED();
116}
117
118bool DrawingAreaProxyImpl::paint(const WebCore::IntRect&, PlatformDrawingContext)
119{
120    ASSERT_NOT_REACHED();
121    return false;
122}
123
124void DrawingAreaProxyImpl::sizeDidChange()
125{
126    backingStoreStateDidChange(RespondImmediately);
127}
128
129void DrawingAreaProxyImpl::visibilityDidChange()
130{
131    if (!m_webPageProxy->isViewVisible()) {
132        // Suspend painting.
133        m_webPageProxy->process()->send(Messages::DrawingArea::SuspendPainting(), m_webPageProxy->pageID());
134        return;
135    }
136
137    // Resume painting.
138    m_webPageProxy->process()->send(Messages::DrawingArea::ResumePainting(), m_webPageProxy->pageID());
139
140#if USE(ACCELERATED_COMPOSITING)
141    // If we don't have a backing store, go ahead and mark the backing store as being changed so
142    // that when paint we'll actually wait for something to paint and not flash white.
143    if (!m_backingStore && m_layerTreeContext.isEmpty())
144        backingStoreStateDidChange(DoNotRespondImmediately);
145#endif
146}
147
148void DrawingAreaProxyImpl::setPageIsVisible(bool)
149{
150}
151
152void DrawingAreaProxyImpl::setBackingStoreIsDiscardable(bool isBackingStoreDiscardable)
153{
154    if (m_isBackingStoreDiscardable == isBackingStoreDiscardable)
155        return;
156
157    m_isBackingStoreDiscardable = isBackingStoreDiscardable;
158    if (m_isBackingStoreDiscardable)
159        discardBackingStoreSoon();
160    else
161        m_discardBackingStoreTimer.stop();
162}
163
164void DrawingAreaProxyImpl::update(uint64_t backingStoreStateID, const UpdateInfo& updateInfo)
165{
166    ASSERT_ARG(backingStoreStateID, backingStoreStateID <= m_currentBackingStoreStateID);
167    if (backingStoreStateID < m_currentBackingStoreStateID)
168        return;
169
170    // FIXME: Handle the case where the view is hidden.
171
172    incorporateUpdate(updateInfo);
173    m_webPageProxy->process()->send(Messages::DrawingArea::DidUpdate(), m_webPageProxy->pageID());
174}
175
176void DrawingAreaProxyImpl::didUpdateBackingStoreState(uint64_t backingStoreStateID, const UpdateInfo& updateInfo, const LayerTreeContext& layerTreeContext)
177{
178    ASSERT_ARG(backingStoreStateID, backingStoreStateID <= m_nextBackingStoreStateID);
179    ASSERT_ARG(backingStoreStateID, backingStoreStateID > m_currentBackingStoreStateID);
180    m_currentBackingStoreStateID = backingStoreStateID;
181
182    m_isWaitingForDidUpdateBackingStoreState = false;
183
184    if (m_nextBackingStoreStateID != m_currentBackingStoreStateID)
185        sendUpdateBackingStoreState(RespondImmediately);
186
187#if USE(ACCELERATED_COMPOSITING)
188    if (layerTreeContext != m_layerTreeContext) {
189        if (!m_layerTreeContext.isEmpty()) {
190            exitAcceleratedCompositingMode();
191            ASSERT(m_layerTreeContext.isEmpty());
192        }
193
194        if (!layerTreeContext.isEmpty()) {
195            enterAcceleratedCompositingMode(layerTreeContext);
196            ASSERT(layerTreeContext == m_layerTreeContext);
197        }
198    }
199
200    if (isInAcceleratedCompositingMode()) {
201        ASSERT(!m_backingStore);
202        return;
203    }
204#endif
205
206    // FIXME: We could just reuse our existing backing store if it's the same size as
207    // updateInfo.viewSize.
208    m_backingStore = nullptr;
209    incorporateUpdate(updateInfo);
210}
211
212void DrawingAreaProxyImpl::enterAcceleratedCompositingMode(uint64_t backingStoreStateID, const LayerTreeContext& layerTreeContext)
213{
214    ASSERT_ARG(backingStoreStateID, backingStoreStateID <= m_currentBackingStoreStateID);
215    if (backingStoreStateID < m_currentBackingStoreStateID)
216        return;
217
218#if USE(ACCELERATED_COMPOSITING)
219    enterAcceleratedCompositingMode(layerTreeContext);
220#endif
221}
222
223void DrawingAreaProxyImpl::exitAcceleratedCompositingMode(uint64_t backingStoreStateID, const UpdateInfo& updateInfo)
224{
225    ASSERT_ARG(backingStoreStateID, backingStoreStateID <= m_currentBackingStoreStateID);
226    if (backingStoreStateID < m_currentBackingStoreStateID)
227        return;
228
229#if USE(ACCELERATED_COMPOSITING)
230    exitAcceleratedCompositingMode();
231#endif
232
233    incorporateUpdate(updateInfo);
234}
235
236void DrawingAreaProxyImpl::incorporateUpdate(const UpdateInfo& updateInfo)
237{
238    ASSERT(!isInAcceleratedCompositingMode());
239
240    if (updateInfo.updateRectBounds.isEmpty())
241        return;
242
243    if (!m_backingStore)
244        m_backingStore = BackingStore::create(updateInfo.viewSize, 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    for (size_t i = 0; i < updateInfo.updateRects.size(); ++i)
254        m_webPageProxy->setViewNeedsDisplay(updateInfo.updateRects[i]);
255
256    if (WebPageProxy::debugPaintFlags() & kWKDebugFlashBackingStoreUpdates)
257        m_webPageProxy->flashBackingStoreUpdates(updateInfo.updateRects);
258
259    if (shouldScroll)
260        m_webPageProxy->displayView();
261}
262
263void DrawingAreaProxyImpl::backingStoreStateDidChange(RespondImmediatelyOrNot respondImmediatelyOrNot)
264{
265    ++m_nextBackingStoreStateID;
266    sendUpdateBackingStoreState(respondImmediatelyOrNot);
267}
268
269void DrawingAreaProxyImpl::sendUpdateBackingStoreState(RespondImmediatelyOrNot respondImmediatelyOrNot)
270{
271    ASSERT(m_currentBackingStoreStateID < m_nextBackingStoreStateID);
272
273    if (!m_webPageProxy->isValid())
274        return;
275
276    if (m_isWaitingForDidUpdateBackingStoreState)
277        return;
278
279    m_isWaitingForDidUpdateBackingStoreState = respondImmediatelyOrNot == RespondImmediately;
280    m_webPageProxy->process()->send(Messages::DrawingArea::UpdateBackingStoreState(m_nextBackingStoreStateID, respondImmediatelyOrNot == RespondImmediately, m_size, m_scrollOffset), m_webPageProxy->pageID());
281    m_scrollOffset = IntSize();
282
283#if USE(ACCELERATED_COMPOSITING)
284    if (m_isWaitingForDidUpdateBackingStoreState && !m_layerTreeContext.isEmpty()) {
285        // Wait for the DidUpdateBackingStoreState message. Normally we don this in DrawingAreaProxyImpl::paint, but that
286        // function is never called when in accelerated compositing mode.
287        waitForAndDispatchDidUpdateBackingStoreState();
288    }
289#endif
290}
291
292void DrawingAreaProxyImpl::waitForAndDispatchDidUpdateBackingStoreState()
293{
294    ASSERT(m_isWaitingForDidUpdateBackingStoreState);
295
296    if (!m_webPageProxy->isValid())
297        return;
298    if (m_webPageProxy->process()->isLaunching())
299        return;
300
301#if USE(ACCELERATED_COMPOSITING)
302    // FIXME: waitForAndDispatchImmediately will always return the oldest DidUpdateBackingStoreState message that
303    // hasn't yet been processed. But it might be better to skip ahead to some other DidUpdateBackingStoreState
304    // message, if multiple DidUpdateBackingStoreState messages are waiting to be processed. For instance, we could
305    // choose the most recent one, or the one that is closest to our current size.
306
307    // The timeout, in seconds, we use when waiting for a DidUpdateBackingStoreState message when we're asked to paint.
308    static const double didUpdateBackingStoreStateTimeout = 0.5;
309    m_webPageProxy->process()->connection()->waitForAndDispatchImmediately<Messages::DrawingAreaProxy::DidUpdateBackingStoreState>(m_webPageProxy->pageID(), didUpdateBackingStoreStateTimeout);
310#endif
311}
312
313#if USE(ACCELERATED_COMPOSITING)
314void DrawingAreaProxyImpl::enterAcceleratedCompositingMode(const LayerTreeContext& layerTreeContext)
315{
316    ASSERT(!isInAcceleratedCompositingMode());
317
318    m_backingStore = nullptr;
319    m_layerTreeContext = layerTreeContext;
320    m_webPageProxy->enterAcceleratedCompositingMode(layerTreeContext);
321}
322
323void DrawingAreaProxyImpl::exitAcceleratedCompositingMode()
324{
325    ASSERT(isInAcceleratedCompositingMode());
326
327    m_layerTreeContext = LayerTreeContext();
328    m_webPageProxy->exitAcceleratedCompositingMode();
329}
330#endif
331
332void DrawingAreaProxyImpl::discardBackingStoreSoon()
333{
334    if (!m_isBackingStoreDiscardable)
335        return;
336
337    // We'll wait this many seconds after the last paint before throwing away our backing store to save memory.
338    // FIXME: It would be smarter to make this delay based on how expensive painting is. See <http://webkit.org/b/55733>.
339    static const double discardBackingStoreDelay = 5;
340
341    m_discardBackingStoreTimer.startOneShot(discardBackingStoreDelay);
342}
343
344void DrawingAreaProxyImpl::discardBackingStore()
345{
346    m_backingStore = nullptr;
347    backingStoreStateDidChange(DoNotRespondImmediately);
348}
349
350} // namespace WebKit
351