REGRESSION (iOS 13): Top fixed element on apple.com flickers in size while pinching in
[WebKit-https.git] / Source / WebKit / UIProcess / RemoteLayerTree / RemoteLayerTreeDrawingAreaProxy.mm
1 /*
2  * Copyright (C) 2012-2018 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 "DrawingAreaMessages.h"
30 #import "Logging.h"
31 #import "RemoteLayerTreeDrawingAreaProxyMessages.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/GraphicsContextCG.h>
38 #import <WebCore/IOSurfacePool.h>
39 #import <WebCore/WebActionDisablingCALayerDelegate.h>
40 #import <wtf/MachSendRight.h>
41 #import <wtf/SystemTracing.h>
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_FAMILY)
46 @interface WKOneShotDisplayLinkHandler : 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 WKOneShotDisplayLinkHandler
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         _displayLink.preferredFramesPerSecond = 60;
69     }
70     return self;
71 }
72
73 - (void)dealloc
74 {
75     ASSERT(!_displayLink);
76     [super dealloc];
77 }
78
79 - (void)displayLinkFired:(CADisplayLink *)sender
80 {
81     ASSERT(isUIThread());
82     _drawingAreaProxy->didRefreshDisplay();
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 - (void)pause
97 {
98     _displayLink.paused = YES;
99 }
100
101 @end
102 #endif
103
104 namespace WebKit {
105 using namespace IPC;
106 using namespace WebCore;
107
108 RemoteLayerTreeDrawingAreaProxy::RemoteLayerTreeDrawingAreaProxy(WebPageProxy& webPageProxy, WebProcessProxy& process)
109     : DrawingAreaProxy(DrawingAreaTypeRemoteLayerTree, webPageProxy, process)
110     , m_remoteLayerTreeHost(makeUnique<RemoteLayerTreeHost>(*this))
111 {
112 #if HAVE(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     process.addMessageReceiver(Messages::RemoteLayerTreeDrawingAreaProxy::messageReceiverName(), m_identifier, *this);
119
120     if (m_webPageProxy.preferences().tiledScrollingIndicatorVisible())
121         initializeDebugIndicator();
122 }
123
124 RemoteLayerTreeDrawingAreaProxy::~RemoteLayerTreeDrawingAreaProxy()
125 {
126     m_callbacks.invalidate(CallbackBase::Error::OwnerWasInvalidated);
127     process().removeMessageReceiver(Messages::RemoteLayerTreeDrawingAreaProxy::messageReceiverName(), m_identifier);
128
129 #if PLATFORM(IOS_FAMILY)
130     [m_displayLinkHandler invalidate];
131 #endif
132 }
133
134
135 std::unique_ptr<RemoteLayerTreeHost> RemoteLayerTreeDrawingAreaProxy::detachRemoteLayerTreeHost()
136 {
137     m_remoteLayerTreeHost->detachFromDrawingArea();
138     return WTFMove(m_remoteLayerTreeHost);
139 }
140
141
142 #if PLATFORM(IOS_FAMILY)
143 WKOneShotDisplayLinkHandler *RemoteLayerTreeDrawingAreaProxy::displayLinkHandler()
144 {
145     if (!m_displayLinkHandler)
146         m_displayLinkHandler = adoptNS([[WKOneShotDisplayLinkHandler alloc] initWithDrawingAreaProxy:this]);
147     return m_displayLinkHandler.get();
148 }
149 #endif
150
151 void RemoteLayerTreeDrawingAreaProxy::sizeDidChange()
152 {
153     if (!m_webPageProxy.hasRunningProcess())
154         return;
155
156     if (m_isWaitingForDidUpdateGeometry)
157         return;
158
159     sendUpdateGeometry();
160 }
161
162 void RemoteLayerTreeDrawingAreaProxy::deviceScaleFactorDidChange()
163 {
164     send(Messages::DrawingArea::SetDeviceScaleFactor(m_webPageProxy.deviceScaleFactor()));
165 }
166
167 void RemoteLayerTreeDrawingAreaProxy::didUpdateGeometry()
168 {
169     ASSERT(m_isWaitingForDidUpdateGeometry);
170
171     m_isWaitingForDidUpdateGeometry = false;
172
173     // If the WKView was resized while we were waiting for a DidUpdateGeometry reply from the web process,
174     // we need to resend the new size here.
175     if (m_lastSentSize != m_size)
176         sendUpdateGeometry();
177 }
178
179 void RemoteLayerTreeDrawingAreaProxy::sendUpdateGeometry()
180 {
181     m_lastSentSize = m_size;
182     send(Messages::DrawingArea::UpdateGeometry(m_size, false /* flushSynchronously */, MachSendRight()));
183     m_isWaitingForDidUpdateGeometry = true;
184 }
185
186 void RemoteLayerTreeDrawingAreaProxy::willCommitLayerTree(TransactionID transactionID)
187 {
188     m_pendingLayerTreeTransactionID = transactionID;
189 }
190
191 void RemoteLayerTreeDrawingAreaProxy::commitLayerTree(const RemoteLayerTreeTransaction& layerTreeTransaction, const RemoteScrollingCoordinatorTransaction& scrollingTreeTransaction)
192 {
193     TraceScope tracingScope(CommitLayerTreeStart, CommitLayerTreeEnd);
194
195     LOG(RemoteLayerTree, "%s", layerTreeTransaction.description().data());
196     LOG(RemoteLayerTree, "%s", scrollingTreeTransaction.description().data());
197
198     ASSERT(layerTreeTransaction.transactionID() == m_lastVisibleTransactionID.next());
199     m_transactionIDForPendingCACommit = layerTreeTransaction.transactionID();
200     m_activityStateChangeID = layerTreeTransaction.activityStateChangeID();
201
202     if (layerTreeTransaction.hasEditorState())
203         m_webPageProxy.updateEditorState(layerTreeTransaction.editorState());
204
205     if (m_remoteLayerTreeHost->updateLayerTree(layerTreeTransaction)) {
206         if (layerTreeTransaction.transactionID() >= m_transactionIDForUnhidingContent)
207             m_webPageProxy.setRemoteLayerTreeRootNode(m_remoteLayerTreeHost->rootNode());
208         else
209             m_remoteLayerTreeHost->detachRootLayer();
210     }
211
212 #if ENABLE(ASYNC_SCROLLING)
213     RemoteScrollingCoordinatorProxy::RequestedScrollInfo requestedScrollInfo;
214     m_webPageProxy.scrollingCoordinatorProxy()->commitScrollingTreeState(scrollingTreeTransaction, requestedScrollInfo);
215 #endif
216
217     m_webPageProxy.didCommitLayerTree(layerTreeTransaction);
218
219 #if ENABLE(ASYNC_SCROLLING)
220     m_webPageProxy.scrollingCoordinatorProxy()->applyScrollingTreeLayerPositionsAfterCommit();
221 #if PLATFORM(IOS_FAMILY)
222     m_webPageProxy.adjustLayersForLayoutViewport(m_webPageProxy.unconstrainedLayoutViewportRect());
223 #endif
224
225     // Handle requested scroll position updates from the scrolling tree transaction after didCommitLayerTree()
226     // has updated the view size based on the content size.
227     if (requestedScrollInfo.requestsScrollPositionUpdate)
228         m_webPageProxy.requestScroll(requestedScrollInfo.requestedScrollPosition, layerTreeTransaction.scrollOrigin());
229 #endif // ENABLE(ASYNC_SCROLLING)
230
231     if (m_debugIndicatorLayerTreeHost) {
232         float scale = indicatorScale(layerTreeTransaction.contentsSize());
233         bool rootLayerChanged = m_debugIndicatorLayerTreeHost->updateLayerTree(layerTreeTransaction, scale);
234         IntPoint scrollPosition;
235 #if PLATFORM(MAC)
236         scrollPosition = layerTreeTransaction.scrollPosition();
237 #endif
238         updateDebugIndicator(layerTreeTransaction.contentsSize(), rootLayerChanged, scale, scrollPosition);
239         m_debugIndicatorLayerTreeHost->rootLayer().name = @"Indicator host root";
240     }
241
242     m_webPageProxy.layerTreeCommitComplete();
243
244 #if PLATFORM(IOS_FAMILY)
245     if (std::exchange(m_didUpdateMessageState, NeedsDidUpdate) == MissedCommit)
246         didRefreshDisplay();
247     [displayLinkHandler() schedule];
248 #else
249     m_didUpdateMessageState = NeedsDidUpdate;
250     didRefreshDisplay();
251 #endif
252
253     if (layerTreeTransaction.hasEditorState())
254         m_webPageProxy.dispatchDidReceiveEditorStateAfterFocus();
255
256     if (auto milestones = layerTreeTransaction.newlyReachedPaintingMilestones())
257         m_webPageProxy.didReachLayoutMilestone(milestones);
258
259     for (auto& callbackID : layerTreeTransaction.callbackIDs()) {
260         if (auto callback = m_callbacks.take<VoidCallback>(callbackID))
261             callback->performCallback();
262     }
263 }
264
265 void RemoteLayerTreeDrawingAreaProxy::acceleratedAnimationDidStart(uint64_t layerID, const String& key, MonotonicTime startTime)
266 {
267     send(Messages::DrawingArea::AcceleratedAnimationDidStart(layerID, key, startTime));
268 }
269
270 void RemoteLayerTreeDrawingAreaProxy::acceleratedAnimationDidEnd(uint64_t layerID, const String& key)
271 {
272     send(Messages::DrawingArea::AcceleratedAnimationDidEnd(layerID, key));
273 }
274
275 static const float indicatorInset = 10;
276
277 #if PLATFORM(MAC)
278 void RemoteLayerTreeDrawingAreaProxy::setViewExposedRect(Optional<WebCore::FloatRect> viewExposedRect)
279 {
280     DrawingAreaProxy::setViewExposedRect(viewExposedRect);
281     updateDebugIndicatorPosition();
282 }
283 #endif
284
285 FloatPoint RemoteLayerTreeDrawingAreaProxy::indicatorLocation() const
286 {
287     if (m_webPageProxy.delegatesScrolling()) {
288 #if PLATFORM(IOS_FAMILY)
289         FloatPoint tiledMapLocation = m_webPageProxy.unobscuredContentRect().location().expandedTo(FloatPoint());
290         tiledMapLocation = tiledMapLocation.expandedTo(m_webPageProxy.exposedContentRect().location());
291
292         float absoluteInset = indicatorInset / m_webPageProxy.displayedContentScale();
293         tiledMapLocation += FloatSize(absoluteInset, absoluteInset);
294 #else
295         FloatPoint tiledMapLocation;
296         if (viewExposedRect())
297             tiledMapLocation = viewExposedRect().value().location();
298
299         tiledMapLocation += FloatSize(indicatorInset, indicatorInset);
300         float scale = 1 / m_webPageProxy.pageScaleFactor();
301         tiledMapLocation.scale(scale);
302 #endif
303         return tiledMapLocation;
304     }
305     
306     return FloatPoint(indicatorInset, indicatorInset + m_webPageProxy.topContentInset());
307 }
308
309 void RemoteLayerTreeDrawingAreaProxy::updateDebugIndicatorPosition()
310 {
311     if (!m_tileMapHostLayer)
312         return;
313
314     [m_tileMapHostLayer setPosition:indicatorLocation()];
315 }
316
317 float RemoteLayerTreeDrawingAreaProxy::indicatorScale(IntSize contentsSize) const
318 {
319     // Pick a good scale.
320     IntSize viewSize = m_webPageProxy.viewSize();
321
322     float scale = 1;
323     if (!contentsSize.isEmpty()) {
324         float widthScale = std::min<float>((viewSize.width() - 2 * indicatorInset) / contentsSize.width(), 0.05);
325         scale = std::min(widthScale, static_cast<float>(viewSize.height() - 2 * indicatorInset) / contentsSize.height());
326     }
327     
328     return scale;
329 }
330
331 void RemoteLayerTreeDrawingAreaProxy::updateDebugIndicator()
332 {
333     // FIXME: we should also update live information during scale.
334     updateDebugIndicatorPosition();
335 }
336
337 void RemoteLayerTreeDrawingAreaProxy::updateDebugIndicator(IntSize contentsSize, bool rootLayerChanged, float scale, const IntPoint& scrollPosition)
338 {
339     // Make sure we're the last sublayer.
340     CALayer *rootLayer = m_remoteLayerTreeHost->rootLayer();
341     [m_tileMapHostLayer removeFromSuperlayer];
342     [rootLayer addSublayer:m_tileMapHostLayer.get()];
343
344     // Pick a good scale.
345     IntSize viewSize = m_webPageProxy.viewSize();
346
347     [m_tileMapHostLayer setBounds:FloatRect(FloatPoint(), contentsSize)];
348     [m_tileMapHostLayer setPosition:indicatorLocation()];
349     [m_tileMapHostLayer setTransform:CATransform3DMakeScale(scale, scale, 1)];
350
351     if (rootLayerChanged) {
352         [m_tileMapHostLayer setSublayers:@[]];
353         [m_tileMapHostLayer addSublayer:m_debugIndicatorLayerTreeHost->rootLayer()];
354         [m_tileMapHostLayer addSublayer:m_exposedRectIndicatorLayer.get()];
355     }
356     
357     const float indicatorBorderWidth = 1;
358     float counterScaledBorder = indicatorBorderWidth / scale;
359
360     [m_exposedRectIndicatorLayer setBorderWidth:counterScaledBorder];
361
362     if (m_webPageProxy.delegatesScrolling()) {
363         FloatRect scaledExposedRect;
364 #if PLATFORM(IOS_FAMILY)
365         scaledExposedRect = m_webPageProxy.exposedContentRect();
366 #else
367         if (viewExposedRect())
368             scaledExposedRect = viewExposedRect().value();
369         float scale = 1 / m_webPageProxy.pageScaleFactor();
370         scaledExposedRect.scale(scale);
371 #endif
372         [m_exposedRectIndicatorLayer setPosition:scaledExposedRect.location()];
373         [m_exposedRectIndicatorLayer setBounds:FloatRect(FloatPoint(), scaledExposedRect.size())];
374     } else {
375         [m_exposedRectIndicatorLayer setPosition:scrollPosition];
376         [m_exposedRectIndicatorLayer setBounds:FloatRect(FloatPoint(), viewSize)];
377     }
378 }
379
380 void RemoteLayerTreeDrawingAreaProxy::initializeDebugIndicator()
381 {
382     m_debugIndicatorLayerTreeHost = makeUnique<RemoteLayerTreeHost>(*this);
383     m_debugIndicatorLayerTreeHost->setIsDebugLayerTreeHost(true);
384
385     m_tileMapHostLayer = adoptNS([[CALayer alloc] init]);
386     [m_tileMapHostLayer setName:@"Tile map host"];
387     [m_tileMapHostLayer setDelegate:[WebActionDisablingCALayerDelegate shared]];
388     [m_tileMapHostLayer setAnchorPoint:CGPointZero];
389     [m_tileMapHostLayer setOpacity:0.8];
390     [m_tileMapHostLayer setMasksToBounds:YES];
391     [m_tileMapHostLayer setBorderWidth:2];
392
393     CGColorSpaceRef colorSpace = sRGBColorSpaceRef();
394     {
395         const CGFloat components[] = { 1, 1, 1, 0.6 };
396         RetainPtr<CGColorRef> color = adoptCF(CGColorCreate(colorSpace, components));
397         [m_tileMapHostLayer setBackgroundColor:color.get()];
398
399         const CGFloat borderComponents[] = { 0, 0, 0, 1 };
400         RetainPtr<CGColorRef> borderColor = adoptCF(CGColorCreate(colorSpace, borderComponents));
401         [m_tileMapHostLayer setBorderColor:borderColor.get()];
402     }
403     
404     m_exposedRectIndicatorLayer = adoptNS([[CALayer alloc] init]);
405     [m_exposedRectIndicatorLayer setDelegate:[WebActionDisablingCALayerDelegate shared]];
406     [m_exposedRectIndicatorLayer setAnchorPoint:CGPointZero];
407
408     {
409         const CGFloat components[] = { 0, 1, 0, 1 };
410         RetainPtr<CGColorRef> color = adoptCF(CGColorCreate(colorSpace, components));
411         [m_exposedRectIndicatorLayer setBorderColor:color.get()];
412     }
413 }
414
415 void RemoteLayerTreeDrawingAreaProxy::didRefreshDisplay()
416 {
417     if (!m_webPageProxy.hasRunningProcess())
418         return;
419
420     if (m_didUpdateMessageState != NeedsDidUpdate) {
421         m_didUpdateMessageState = MissedCommit;
422 #if PLATFORM(IOS_FAMILY)
423         [displayLinkHandler() pause];
424 #endif
425         return;
426     }
427     
428     m_didUpdateMessageState = DoesNotNeedDidUpdate;
429
430     // Waiting for CA to commit is insufficient, because the render server can still be
431     // using our backing store. We can improve this by waiting for the render server to commit
432     // if we find API to do so, but for now we will make extra buffers if need be.
433     send(Messages::DrawingArea::DidUpdate());
434
435     m_lastVisibleTransactionID = m_transactionIDForPendingCACommit;
436
437     m_webPageProxy.didUpdateActivityState();
438 }
439
440 void RemoteLayerTreeDrawingAreaProxy::waitForDidUpdateActivityState(ActivityStateChangeID activityStateChangeID)
441 {
442     ASSERT(activityStateChangeID != ActivityStateChangeAsynchronous);
443
444     // We must send the didUpdate message before blocking on the next commit, otherwise
445     // we can be guaranteed that the next commit won't come until after the waitForAndDispatchImmediately times out.
446     if (m_didUpdateMessageState != DoesNotNeedDidUpdate)
447         didRefreshDisplay();
448
449     static Seconds activityStateUpdateTimeout = [] {
450         if (id value = [[NSUserDefaults standardUserDefaults] objectForKey:@"WebKitOverrideActivityStateUpdateTimeout"])
451             return Seconds([value doubleValue]);
452
453 #if PLATFORM(IOS_FAMILY)
454         return Seconds::fromMilliseconds(500);
455 #else
456         return Seconds::fromMilliseconds(250);
457 #endif
458     }();
459
460     auto startTime = MonotonicTime::now();
461     while (process().connection()->waitForAndDispatchImmediately<Messages::RemoteLayerTreeDrawingAreaProxy::CommitLayerTree>(m_identifier, activityStateUpdateTimeout - (MonotonicTime::now() - startTime), IPC::WaitForOption::InterruptWaitingIfSyncMessageArrives)) {
462         if (activityStateChangeID == ActivityStateChangeAsynchronous || activityStateChangeID <= m_activityStateChangeID)
463             return;
464     }
465 }
466
467 void RemoteLayerTreeDrawingAreaProxy::dispatchAfterEnsuringDrawing(WTF::Function<void (CallbackBase::Error)>&& callbackFunction)
468 {
469     if (!m_webPageProxy.hasRunningProcess()) {
470         callbackFunction(CallbackBase::Error::OwnerWasInvalidated);
471         return;
472     }
473
474     send(Messages::DrawingArea::AddTransactionCallbackID(m_callbacks.put(WTFMove(callbackFunction), process().throttler().backgroundActivityToken())));
475 }
476
477 void RemoteLayerTreeDrawingAreaProxy::hideContentUntilPendingUpdate()
478 {
479     m_transactionIDForUnhidingContent = nextLayerTreeTransactionID();
480     m_remoteLayerTreeHost->detachRootLayer();
481 }
482
483 void RemoteLayerTreeDrawingAreaProxy::hideContentUntilAnyUpdate()
484 {
485     m_remoteLayerTreeHost->detachRootLayer();
486 }
487
488 void RemoteLayerTreeDrawingAreaProxy::prepareForAppSuspension()
489 {
490     m_remoteLayerTreeHost->mapAllIOSurfaceBackingStore();
491 }
492
493 bool RemoteLayerTreeDrawingAreaProxy::hasVisibleContent() const
494 {
495     return m_remoteLayerTreeHost->rootLayer();
496 }
497
498 bool RemoteLayerTreeDrawingAreaProxy::isAlwaysOnLoggingAllowed() const
499 {
500     return m_webPageProxy.isAlwaysOnLoggingAllowed();
501 }
502
503 CALayer *RemoteLayerTreeDrawingAreaProxy::layerWithIDForTesting(uint64_t layerID) const
504 {
505     return m_remoteLayerTreeHost->layerWithIDForTesting(layerID);
506 }
507
508 } // namespace WebKit