1cc236ca33d05fae6866174464d155185dd25813
[WebKit-https.git] / Source / WebKit2 / UIProcess / mac / RemoteLayerTreeDrawingAreaProxy.mm
1 /*
2  * Copyright (C) 2012-2014 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 #import "config.h"
27 #import "RemoteLayerTreeDrawingAreaProxy.h"
28
29 #import "Logging.h"
30 #import "RemoteLayerTreeDrawingAreaProxyMessages.h"
31 #import "DrawingAreaMessages.h"
32 #import "RemoteScrollingCoordinatorProxy.h"
33 #import "RemoteScrollingCoordinatorTransaction.h"
34 #import "WebPageProxy.h"
35 #import "WebProcessProxy.h"
36 #import <QuartzCore/QuartzCore.h>
37 #import <WebCore/IOSurfacePool.h>
38 #import <WebCore/WebActionDisablingCALayerDelegate.h>
39
40 using namespace IPC;
41 using namespace WebCore;
42
43 // FIXME: Mac will need something similar; we should figure out how to share this with DisplayRefreshMonitor without
44 // breaking WebKit1 behavior or WebKit2-WebKit1 coexistence.
45 #if PLATFORM(IOS)
46 @interface OneShotDisplayLinkHandler : NSObject {
47     WebKit::RemoteLayerTreeDrawingAreaProxy* _drawingAreaProxy;
48     CADisplayLink *_displayLink;
49 }
50
51 - (id)initWithDrawingAreaProxy:(WebKit::RemoteLayerTreeDrawingAreaProxy*)drawingAreaProxy;
52 - (void)displayLinkFired:(CADisplayLink *)sender;
53 - (void)invalidate;
54 - (void)schedule;
55
56 @end
57
58 @implementation OneShotDisplayLinkHandler
59
60 - (id)initWithDrawingAreaProxy:(WebKit::RemoteLayerTreeDrawingAreaProxy*)drawingAreaProxy
61 {
62     if (self = [super init]) {
63         _drawingAreaProxy = drawingAreaProxy;
64         // Note that CADisplayLink retains its target (self), so a call to -invalidate is needed on teardown.
65         _displayLink = [CADisplayLink displayLinkWithTarget:self selector:@selector(displayLinkFired:)];
66         [_displayLink addToRunLoop:[NSRunLoop mainRunLoop] forMode:NSRunLoopCommonModes];
67         _displayLink.paused = YES;
68     }
69     return self;
70 }
71
72 - (void)dealloc
73 {
74     ASSERT(!_displayLink);
75     [super dealloc];
76 }
77
78 - (void)displayLinkFired:(CADisplayLink *)sender
79 {
80     ASSERT(isUIThread());
81     _drawingAreaProxy->didRefreshDisplay(sender.timestamp);
82     _displayLink.paused = YES;
83 }
84
85 - (void)invalidate
86 {
87     [_displayLink invalidate];
88     _displayLink = nullptr;
89 }
90
91 - (void)schedule
92 {
93     _displayLink.paused = NO;
94 }
95
96 @end
97 #endif
98
99 namespace WebKit {
100
101 RemoteLayerTreeDrawingAreaProxy::RemoteLayerTreeDrawingAreaProxy(WebPageProxy* webPageProxy)
102     : DrawingAreaProxy(DrawingAreaTypeRemoteLayerTree, webPageProxy)
103     , m_remoteLayerTreeHost(*this)
104     , m_isWaitingForDidUpdateGeometry(false)
105     , m_pendingLayerTreeTransactionID(0)
106     , m_lastVisibleTransactionID(0)
107     , m_transactionIDForPendingCACommit(0)
108 #if PLATFORM(IOS)
109     , m_displayLinkHandler(adoptNS([[OneShotDisplayLinkHandler alloc] initWithDrawingAreaProxy:this]))
110 #endif
111 {
112 #if USE(IOSURFACE)
113     // We don't want to pool surfaces in the UI process.
114     // FIXME: We should do this somewhere else.
115     IOSurfacePool::sharedPool().setPoolSize(0);
116 #endif
117
118     m_webPageProxy->process().addMessageReceiver(Messages::RemoteLayerTreeDrawingAreaProxy::messageReceiverName(), m_webPageProxy->pageID(), *this);
119
120     if (m_webPageProxy->preferences().tiledScrollingIndicatorVisible())
121         initializeDebugIndicator();
122 }
123
124 RemoteLayerTreeDrawingAreaProxy::~RemoteLayerTreeDrawingAreaProxy()
125 {
126     m_callbacks.invalidate(CallbackBase::Error::OwnerWasInvalidated);
127     m_webPageProxy->process().removeMessageReceiver(Messages::RemoteLayerTreeDrawingAreaProxy::messageReceiverName(), m_webPageProxy->pageID());
128
129 #if PLATFORM(IOS)
130     [m_displayLinkHandler invalidate];
131 #endif
132 }
133
134 void RemoteLayerTreeDrawingAreaProxy::sizeDidChange()
135 {
136     if (!m_webPageProxy->isValid())
137         return;
138
139     if (m_isWaitingForDidUpdateGeometry)
140         return;
141
142     sendUpdateGeometry();
143 }
144
145 void RemoteLayerTreeDrawingAreaProxy::deviceScaleFactorDidChange()
146 {
147     m_webPageProxy->process().send(Messages::DrawingArea::SetDeviceScaleFactor(m_webPageProxy->deviceScaleFactor()), m_webPageProxy->pageID());
148 }
149
150 void RemoteLayerTreeDrawingAreaProxy::didUpdateGeometry()
151 {
152     ASSERT(m_isWaitingForDidUpdateGeometry);
153
154     m_isWaitingForDidUpdateGeometry = false;
155
156     // If the WKView was resized while we were waiting for a DidUpdateGeometry reply from the web process,
157     // we need to resend the new size here.
158     if (m_lastSentSize != m_size || m_lastSentLayerPosition != m_layerPosition)
159         sendUpdateGeometry();
160 }
161
162 FloatRect RemoteLayerTreeDrawingAreaProxy::scaledExposedRect() const
163 {
164 #if PLATFORM(IOS)
165     return m_webPageProxy->exposedContentRect();
166 #else
167     FloatRect scaledExposedRect = exposedRect();
168     float scale = 1 / m_webPageProxy->pageScaleFactor();
169     scaledExposedRect.scale(scale, scale);
170     return scaledExposedRect;
171 #endif
172 }
173
174 void RemoteLayerTreeDrawingAreaProxy::sendUpdateGeometry()
175 {
176     m_lastSentSize = m_size;
177     m_lastSentLayerPosition = m_layerPosition;
178     m_webPageProxy->process().send(Messages::DrawingArea::UpdateGeometry(m_size, m_layerPosition), m_webPageProxy->pageID());
179     m_isWaitingForDidUpdateGeometry = true;
180 }
181
182 void RemoteLayerTreeDrawingAreaProxy::willCommitLayerTree(uint64_t transactionID)
183 {
184     m_pendingLayerTreeTransactionID = transactionID;
185 }
186
187 void RemoteLayerTreeDrawingAreaProxy::commitLayerTree(const RemoteLayerTreeTransaction& layerTreeTransaction, const RemoteScrollingCoordinatorTransaction& scrollingTreeTransaction)
188 {
189     LOG(RemoteLayerTree, "%s", layerTreeTransaction.description().data());
190     LOG(RemoteLayerTree, "%s", scrollingTreeTransaction.description().data());
191
192     ASSERT(layerTreeTransaction.transactionID() == m_lastVisibleTransactionID + 1);
193     m_transactionIDForPendingCACommit = layerTreeTransaction.transactionID();
194
195     for (auto& callbackID : layerTreeTransaction.callbackIDs()) {
196         if (auto callback = m_callbacks.take<VoidCallback>(callbackID))
197             callback->performCallback();
198     }
199
200     if (m_remoteLayerTreeHost.updateLayerTree(layerTreeTransaction))
201         m_webPageProxy->setAcceleratedCompositingRootLayer(m_remoteLayerTreeHost.rootLayer());
202
203 #if ENABLE(ASYNC_SCROLLING)
204     RemoteScrollingCoordinatorProxy::RequestedScrollInfo requestedScrollInfo;
205     m_webPageProxy->scrollingCoordinatorProxy()->updateScrollingTree(scrollingTreeTransaction, requestedScrollInfo);
206 #endif
207
208     m_webPageProxy->didCommitLayerTree(layerTreeTransaction);
209
210 #if ENABLE(ASYNC_SCROLLING)
211 #if PLATFORM(IOS)
212     if (m_webPageProxy->scrollingCoordinatorProxy()->hasFixedOrSticky()) {
213         // If we got a new layer for a fixed or sticky node, its position from the WebProcess is probably stale. We need to re-run the "viewport" changed logic to udpate it with our UI-side state.
214         FloatRect customFixedPositionRect = m_webPageProxy->computeCustomFixedPositionRect(m_webPageProxy->unobscuredContentRect(), m_webPageProxy->displayedContentScale());
215         m_webPageProxy->scrollingCoordinatorProxy()->viewportChangedViaDelegatedScrolling(m_webPageProxy->scrollingCoordinatorProxy()->rootScrollingNodeID(), customFixedPositionRect, m_webPageProxy->displayedContentScale());
216     }
217 #endif
218
219     // Handle requested scroll position updates from the scrolling tree transaction after didCommitLayerTree()
220     // has updated the view size based on the content size.
221     if (requestedScrollInfo.requestsScrollPositionUpdate)
222         m_webPageProxy->requestScroll(requestedScrollInfo.requestedScrollPosition, requestedScrollInfo.requestIsProgrammaticScroll);
223 #endif // ENABLE(ASYNC_SCROLLING)
224
225     if (m_debugIndicatorLayerTreeHost) {
226         float scale = indicatorScale(layerTreeTransaction.contentsSize());
227         bool rootLayerChanged = m_debugIndicatorLayerTreeHost->updateLayerTree(layerTreeTransaction, scale);
228         updateDebugIndicator(layerTreeTransaction.contentsSize(), rootLayerChanged, scale);
229         asLayer(m_debugIndicatorLayerTreeHost->rootLayer()).name = @"Indicator host root";
230     }
231
232 #if PLATFORM(IOS)
233     [m_displayLinkHandler schedule];
234 #else
235     didRefreshDisplay(monotonicallyIncreasingTime());
236 #endif
237 }
238
239 void RemoteLayerTreeDrawingAreaProxy::acceleratedAnimationDidStart(uint64_t layerID, const String& key, double startTime)
240 {
241     m_webPageProxy->process().send(Messages::DrawingArea::AcceleratedAnimationDidStart(layerID, key, startTime), m_webPageProxy->pageID());
242 }
243
244 static const float indicatorInset = 10;
245
246 #if PLATFORM(MAC)
247 void RemoteLayerTreeDrawingAreaProxy::setExposedRect(const WebCore::FloatRect& r)
248 {
249     DrawingAreaProxy::setExposedRect(r);
250     updateDebugIndicatorPosition();
251 }
252 #endif
253
254 FloatPoint RemoteLayerTreeDrawingAreaProxy::indicatorLocation() const
255 {
256     if (m_webPageProxy->delegatesScrolling()) {
257 #if PLATFORM(IOS)
258         FloatPoint tiledMapLocation = m_webPageProxy->unobscuredContentRect().location();
259         float absoluteInset = indicatorInset / m_webPageProxy->displayedContentScale();
260         tiledMapLocation += FloatSize(absoluteInset, absoluteInset);
261 #else
262         FloatPoint tiledMapLocation = exposedRect().location();
263         tiledMapLocation += FloatSize(indicatorInset, indicatorInset);
264         float scale = 1 / m_webPageProxy->pageScaleFactor();;
265         tiledMapLocation.scale(scale, scale);
266 #endif
267         return tiledMapLocation;
268     }
269     
270     return FloatPoint(indicatorInset, indicatorInset);
271 }
272
273 void RemoteLayerTreeDrawingAreaProxy::updateDebugIndicatorPosition()
274 {
275     if (!m_tileMapHostLayer)
276         return;
277
278     [m_tileMapHostLayer setPosition:indicatorLocation()];
279 }
280
281 float RemoteLayerTreeDrawingAreaProxy::indicatorScale(IntSize contentsSize) const
282 {
283     // Pick a good scale.
284     IntSize viewSize = m_webPageProxy->viewSize();
285
286     float scale = 1;
287     if (!contentsSize.isEmpty()) {
288         float widthScale = std::min<float>((viewSize.width() - 2 * indicatorInset) / contentsSize.width(), 0.05);
289         scale = std::min(widthScale, static_cast<float>(viewSize.height() - 2 * indicatorInset) / contentsSize.height());
290     }
291     
292     return scale;
293 }
294
295 void RemoteLayerTreeDrawingAreaProxy::updateDebugIndicator()
296 {
297     // FIXME: we should also update live information during scale.
298     updateDebugIndicatorPosition();
299 }
300
301 void RemoteLayerTreeDrawingAreaProxy::updateDebugIndicator(IntSize contentsSize, bool rootLayerChanged, float scale)
302 {
303     // Make sure we're the last sublayer.
304     CALayer *rootLayer = asLayer(m_remoteLayerTreeHost.rootLayer());
305     [m_tileMapHostLayer removeFromSuperlayer];
306     [rootLayer addSublayer:m_tileMapHostLayer.get()];
307
308     // Pick a good scale.
309     IntSize viewSize = m_webPageProxy->viewSize();
310
311     [m_tileMapHostLayer setBounds:FloatRect(FloatPoint(), contentsSize)];
312     [m_tileMapHostLayer setPosition:indicatorLocation()];
313     [m_tileMapHostLayer setTransform:CATransform3DMakeScale(scale, scale, 1)];
314
315     if (rootLayerChanged) {
316         [m_tileMapHostLayer setSublayers:@[]];
317         [m_tileMapHostLayer addSublayer:asLayer(m_debugIndicatorLayerTreeHost->rootLayer())];
318         [m_tileMapHostLayer addSublayer:m_exposedRectIndicatorLayer.get()];
319     }
320     
321     const float indicatorBorderWidth = 1;
322     float counterScaledBorder = indicatorBorderWidth / scale;
323
324     [m_exposedRectIndicatorLayer setBorderWidth:counterScaledBorder];
325
326     if (m_webPageProxy->delegatesScrolling()) {
327         FloatRect scaledExposedRect = this->scaledExposedRect();
328         [m_exposedRectIndicatorLayer setPosition:scaledExposedRect.location()];
329         [m_exposedRectIndicatorLayer setBounds:FloatRect(FloatPoint(), scaledExposedRect.size())];
330     } else {
331         // FIXME: Get the correct scroll position.
332         [m_exposedRectIndicatorLayer setBounds:FloatRect(FloatPoint(), viewSize)];
333     }
334 }
335
336 void RemoteLayerTreeDrawingAreaProxy::initializeDebugIndicator()
337 {
338     m_debugIndicatorLayerTreeHost = std::make_unique<RemoteLayerTreeHost>(*this);
339     m_debugIndicatorLayerTreeHost->setIsDebugLayerTreeHost(true);
340
341     m_tileMapHostLayer = adoptNS([[CALayer alloc] init]);
342     [m_tileMapHostLayer setName:@"Tile map host"];
343     [m_tileMapHostLayer setDelegate:[WebActionDisablingCALayerDelegate shared]];
344     [m_tileMapHostLayer setAnchorPoint:CGPointZero];
345     [m_tileMapHostLayer setOpacity:0.8];
346     [m_tileMapHostLayer setMasksToBounds:YES];
347     [m_tileMapHostLayer setBorderWidth:2];
348
349     RetainPtr<CGColorSpaceRef> colorSpace = adoptCF(CGColorSpaceCreateDeviceRGB());
350     {
351         const CGFloat components[] = { 1, 1, 1, 0.6 };
352         RetainPtr<CGColorRef> color = adoptCF(CGColorCreate(colorSpace.get(), components));
353         [m_tileMapHostLayer setBackgroundColor:color.get()];
354
355         const CGFloat borderComponents[] = { 0, 0, 0, 1 };
356         RetainPtr<CGColorRef> borderColor = adoptCF(CGColorCreate(colorSpace.get(), borderComponents));
357         [m_tileMapHostLayer setBorderColor:borderColor.get()];
358     }
359     
360     m_exposedRectIndicatorLayer = adoptNS([[CALayer alloc] init]);
361     [m_exposedRectIndicatorLayer setDelegate:[WebActionDisablingCALayerDelegate shared]];
362     [m_exposedRectIndicatorLayer setAnchorPoint:CGPointZero];
363
364     {
365         const CGFloat components[] = { 0, 1, 0, 1 };
366         RetainPtr<CGColorRef> color = adoptCF(CGColorCreate(colorSpace.get(), components));
367         [m_exposedRectIndicatorLayer setBorderColor:color.get()];
368     }
369 }
370
371 void RemoteLayerTreeDrawingAreaProxy::didRefreshDisplay(double)
372 {
373     if (!m_webPageProxy->isValid())
374         return;
375
376     // Waiting for CA to commit is insufficient, because the render server can still be
377     // using our backing store. We can improve this by waiting for the render server to commit
378     // if we find API to do so, but for now we will make extra buffers if need be.
379     m_webPageProxy->process().send(Messages::DrawingArea::DidUpdate(), m_webPageProxy->pageID());
380
381     m_lastVisibleTransactionID = m_transactionIDForPendingCACommit;
382
383     m_webPageProxy->didUpdateViewState();
384 }
385
386 void RemoteLayerTreeDrawingAreaProxy::waitForDidUpdateViewState()
387 {
388 #if PLATFORM(IOS)
389     auto viewStateUpdateTimeout = std::chrono::milliseconds(500);
390 #else
391     auto viewStateUpdateTimeout = std::chrono::milliseconds(250);
392 #endif
393     m_webPageProxy->process().connection()->waitForAndDispatchImmediately<Messages::RemoteLayerTreeDrawingAreaProxy::CommitLayerTree>(m_webPageProxy->pageID(), viewStateUpdateTimeout, InterruptWaitingIfSyncMessageArrives);
394 }
395
396 void RemoteLayerTreeDrawingAreaProxy::dispatchAfterEnsuringDrawing(std::function<void (CallbackBase::Error)> callbackFunction)
397 {
398     if (!m_webPageProxy->isValid()) {
399         callbackFunction(CallbackBase::Error::OwnerWasInvalidated);
400         return;
401     }
402
403     m_webPageProxy->process().send(Messages::DrawingArea::AddTransactionCallbackID(m_callbacks.put(WTF::move(callbackFunction), nullptr)), m_webPageProxy->pageID());
404 }
405
406 void RemoteLayerTreeDrawingAreaProxy::hideContentUntilNextUpdate()
407 {
408     m_remoteLayerTreeHost.detachRootLayer();
409 }
410
411 } // namespace WebKit