Scroll snapping on Mac should use AppKit animations
[WebKit-https.git] / Source / WebCore / page / scrolling / mac / ScrollingTreeFrameScrollingNodeMac.mm
1 /*
2  * Copyright (C) 2012, 2014-2015 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 "ScrollingTreeFrameScrollingNodeMac.h"
28
29 #if ENABLE(ASYNC_SCROLLING) && PLATFORM(MAC)
30
31 #import "FrameView.h"
32 #import "LayoutSize.h"
33 #import "Logging.h"
34 #import "NSScrollerImpSPI.h"
35 #import "PlatformWheelEvent.h"
36 #import "ScrollableArea.h"
37 #import "ScrollingCoordinator.h"
38 #import "ScrollingTree.h"
39 #import "ScrollingStateTree.h"
40 #import "Settings.h"
41 #import "TextStream.h"
42 #import "TileController.h"
43 #import "WebLayer.h"
44
45 #import <QuartzCore/QuartzCore.h>
46 #import <wtf/CurrentTime.h>
47 #import <wtf/Deque.h>
48 #import <wtf/text/StringBuilder.h>
49 #import <wtf/text/CString.h>
50
51 namespace WebCore {
52
53 static void logThreadedScrollingMode(unsigned synchronousScrollingReasons);
54
55 Ref<ScrollingTreeFrameScrollingNode> ScrollingTreeFrameScrollingNodeMac::create(ScrollingTree& scrollingTree, ScrollingNodeID nodeID)
56 {
57     return adoptRef(*new ScrollingTreeFrameScrollingNodeMac(scrollingTree, nodeID));
58 }
59
60 ScrollingTreeFrameScrollingNodeMac::ScrollingTreeFrameScrollingNodeMac(ScrollingTree& scrollingTree, ScrollingNodeID nodeID)
61     : ScrollingTreeFrameScrollingNode(scrollingTree, nodeID)
62     , m_scrollController(*this)
63     , m_verticalScrollerImp(nullptr)
64     , m_horizontalScrollerImp(nullptr)
65 {
66 }
67
68 ScrollingTreeFrameScrollingNodeMac::~ScrollingTreeFrameScrollingNodeMac()
69 {
70     releaseReferencesToScrollerImpsOnTheMainThread();
71 }
72
73 void ScrollingTreeFrameScrollingNodeMac::releaseReferencesToScrollerImpsOnTheMainThread()
74 {
75     if (m_verticalScrollerImp || m_horizontalScrollerImp) {
76         // FIXME: This is a workaround in place for the time being since NSScrollerImps cannot be deallocated
77         // on a non-main thread. rdar://problem/24535055
78         WTF::callOnMainThread([verticalScrollerImp = WTFMove(m_verticalScrollerImp), horizontalScrollerImp = WTFMove(m_horizontalScrollerImp)] {
79         });
80     }
81 }
82
83 #if ENABLE(CSS_SCROLL_SNAP)
84 static inline Vector<LayoutUnit> convertToLayoutUnits(const Vector<float>& snapOffsetsAsFloat)
85 {
86     Vector<LayoutUnit> snapOffsets;
87     snapOffsets.reserveInitialCapacity(snapOffsetsAsFloat.size());
88     for (auto offset : snapOffsetsAsFloat)
89         snapOffsets.append(offset);
90
91     return snapOffsets;
92 }
93 #endif
94
95 void ScrollingTreeFrameScrollingNodeMac::commitStateBeforeChildren(const ScrollingStateNode& stateNode)
96 {
97     ScrollingTreeFrameScrollingNode::commitStateBeforeChildren(stateNode);
98     const auto& scrollingStateNode = downcast<ScrollingStateFrameScrollingNode>(stateNode);
99
100     if (scrollingStateNode.hasChangedProperty(ScrollingStateNode::ScrollLayer))
101         m_scrollLayer = scrollingStateNode.layer();
102
103     if (scrollingStateNode.hasChangedProperty(ScrollingStateFrameScrollingNode::ScrolledContentsLayer))
104         m_scrolledContentsLayer = scrollingStateNode.scrolledContentsLayer();
105
106     if (scrollingStateNode.hasChangedProperty(ScrollingStateFrameScrollingNode::CounterScrollingLayer))
107         m_counterScrollingLayer = scrollingStateNode.counterScrollingLayer();
108
109     if (scrollingStateNode.hasChangedProperty(ScrollingStateFrameScrollingNode::InsetClipLayer))
110         m_insetClipLayer = scrollingStateNode.insetClipLayer();
111
112     if (scrollingStateNode.hasChangedProperty(ScrollingStateFrameScrollingNode::ContentShadowLayer))
113         m_contentShadowLayer = scrollingStateNode.contentShadowLayer();
114
115     if (scrollingStateNode.hasChangedProperty(ScrollingStateFrameScrollingNode::HeaderLayer))
116         m_headerLayer = scrollingStateNode.headerLayer();
117
118     if (scrollingStateNode.hasChangedProperty(ScrollingStateFrameScrollingNode::FooterLayer))
119         m_footerLayer = scrollingStateNode.footerLayer();
120
121     if (scrollingStateNode.hasChangedProperty(ScrollingStateFrameScrollingNode::PainterForScrollbar)) {
122         releaseReferencesToScrollerImpsOnTheMainThread();
123         m_verticalScrollerImp = scrollingStateNode.verticalScrollerImp();
124         m_horizontalScrollerImp = scrollingStateNode.horizontalScrollerImp();
125     }
126
127     bool logScrollingMode = !m_hadFirstUpdate;
128     if (scrollingStateNode.hasChangedProperty(ScrollingStateFrameScrollingNode::ReasonsForSynchronousScrolling)) {
129         if (shouldUpdateScrollLayerPositionSynchronously()) {
130             // We're transitioning to the slow "update scroll layer position on the main thread" mode.
131             // Initialize the probable main thread scroll position with the current scroll layer position.
132             if (scrollingStateNode.hasChangedProperty(ScrollingStateScrollingNode::RequestedScrollPosition))
133                 m_probableMainThreadScrollPosition = scrollingStateNode.requestedScrollPosition();
134             else {
135                 CGPoint scrollLayerPosition = m_scrollLayer.get().position;
136                 m_probableMainThreadScrollPosition = FloatPoint(-scrollLayerPosition.x, -scrollLayerPosition.y);
137             }
138         }
139
140         logScrollingMode = true;
141     }
142
143     if (logScrollingMode) {
144         if (scrollingTree().scrollingPerformanceLoggingEnabled())
145             logThreadedScrollingMode(synchronousScrollingReasons());
146     }
147
148 #if ENABLE(CSS_SCROLL_SNAP)
149     if (scrollingStateNode.hasChangedProperty(ScrollingStateFrameScrollingNode::HorizontalSnapOffsets))
150         m_scrollController.updateScrollSnapPoints(ScrollEventAxis::Horizontal, convertToLayoutUnits(scrollingStateNode.horizontalSnapOffsets()));
151
152     if (scrollingStateNode.hasChangedProperty(ScrollingStateFrameScrollingNode::VerticalSnapOffsets))
153         m_scrollController.updateScrollSnapPoints(ScrollEventAxis::Vertical, convertToLayoutUnits(scrollingStateNode.verticalSnapOffsets()));
154
155     if (scrollingStateNode.hasChangedProperty(ScrollingStateScrollingNode::CurrentHorizontalSnapOffsetIndex))
156         m_scrollController.setActiveScrollSnapIndexForAxis(ScrollEventAxis::Horizontal, scrollingStateNode.currentHorizontalSnapPointIndex());
157     
158     if (scrollingStateNode.hasChangedProperty(ScrollingStateScrollingNode::CurrentVerticalSnapOffsetIndex))
159         m_scrollController.setActiveScrollSnapIndexForAxis(ScrollEventAxis::Vertical, scrollingStateNode.currentVerticalSnapPointIndex());
160 #endif
161
162     if (scrollingStateNode.hasChangedProperty(ScrollingStateScrollingNode::ExpectsWheelEventTestTrigger))
163         m_expectsWheelEventTestTrigger = scrollingStateNode.expectsWheelEventTestTrigger();
164
165     m_hadFirstUpdate = true;
166 }
167
168 void ScrollingTreeFrameScrollingNodeMac::commitStateAfterChildren(const ScrollingStateNode& stateNode)
169 {
170     ScrollingTreeFrameScrollingNode::commitStateAfterChildren(stateNode);
171
172     const auto& scrollingStateNode = downcast<ScrollingStateScrollingNode>(stateNode);
173
174     // Update the scroll position after child nodes have been updated, because they need to have updated their constraints before any scrolling happens.
175     if (scrollingStateNode.hasChangedProperty(ScrollingStateScrollingNode::RequestedScrollPosition))
176         setScrollPosition(scrollingStateNode.requestedScrollPosition());
177
178     if (scrollingStateNode.hasChangedProperty(ScrollingStateNode::ScrollLayer)
179         || scrollingStateNode.hasChangedProperty(ScrollingStateScrollingNode::TotalContentsSize)
180         || scrollingStateNode.hasChangedProperty(ScrollingStateScrollingNode::ScrollableAreaSize))
181         updateMainFramePinState(scrollPosition());
182 }
183
184 void ScrollingTreeFrameScrollingNodeMac::handleWheelEvent(const PlatformWheelEvent& wheelEvent)
185 {
186     if (!canHaveScrollbars())
187         return;
188
189     if (wheelEvent.momentumPhase() == PlatformWheelEventPhaseBegan) {
190         [m_verticalScrollerImp setUsePresentationValue:YES];
191         [m_horizontalScrollerImp setUsePresentationValue:YES];
192     }
193     if (wheelEvent.momentumPhase() == PlatformWheelEventPhaseEnded || wheelEvent.momentumPhase() == PlatformWheelEventPhaseCancelled) {
194         [m_verticalScrollerImp setUsePresentationValue:NO];
195         [m_horizontalScrollerImp setUsePresentationValue:NO];
196     }
197
198 #if ENABLE(CSS_SCROLL_SNAP) || ENABLE(RUBBER_BANDING)
199     if (m_expectsWheelEventTestTrigger) {
200         if (scrollingTree().shouldHandleWheelEventSynchronously(wheelEvent))
201             removeTestDeferralForReason(reinterpret_cast<WheelEventTestTrigger::ScrollableAreaIdentifier>(scrollingNodeID()), WheelEventTestTrigger::ScrollingThreadSyncNeeded);
202         else
203             deferTestsForReason(reinterpret_cast<WheelEventTestTrigger::ScrollableAreaIdentifier>(scrollingNodeID()), WheelEventTestTrigger::ScrollingThreadSyncNeeded);
204     }
205 #endif
206
207     m_scrollController.handleWheelEvent(wheelEvent);
208 #if ENABLE(CSS_SCROLL_SNAP)
209     scrollingTree().setMainFrameIsScrollSnapping(m_scrollController.isScrollSnapInProgress());
210     if (m_scrollController.activeScrollSnapIndexDidChange())
211         scrollingTree().setActiveScrollSnapIndices(scrollingNodeID(), m_scrollController.activeScrollSnapIndexForAxis(ScrollEventAxis::Horizontal), m_scrollController.activeScrollSnapIndexForAxis(ScrollEventAxis::Vertical));
212 #endif
213     scrollingTree().setOrClearLatchedNode(wheelEvent, scrollingNodeID());
214     scrollingTree().handleWheelEventPhase(wheelEvent.phase());
215 }
216
217 // FIXME: We should find a way to share some of the code from newGestureIsStarting(), isAlreadyPinnedInDirectionOfGesture(),
218 // allowsVerticalStretching(), and allowsHorizontalStretching() with the implementation in ScrollAnimatorMac.
219 static bool newGestureIsStarting(const PlatformWheelEvent& wheelEvent)
220 {
221     return wheelEvent.phase() == PlatformWheelEventPhaseMayBegin || wheelEvent.phase() == PlatformWheelEventPhaseBegan;
222 }
223
224 bool ScrollingTreeFrameScrollingNodeMac::isAlreadyPinnedInDirectionOfGesture(const PlatformWheelEvent& wheelEvent, ScrollEventAxis axis)
225 {
226     switch (axis) {
227     case ScrollEventAxis::Vertical:
228         return (wheelEvent.deltaY() > 0 && scrollPosition().y() <= minimumScrollPosition().y()) || (wheelEvent.deltaY() < 0 && scrollPosition().y() >= maximumScrollPosition().y());
229     case ScrollEventAxis::Horizontal:
230         return (wheelEvent.deltaX() > 0 && scrollPosition().x() <= minimumScrollPosition().x()) || (wheelEvent.deltaX() < 0 && scrollPosition().x() >= maximumScrollPosition().x());
231     }
232
233     ASSERT_NOT_REACHED();
234     return false;
235 }
236
237 bool ScrollingTreeFrameScrollingNodeMac::allowsHorizontalStretching(const PlatformWheelEvent& wheelEvent)
238 {
239     switch (horizontalScrollElasticity()) {
240     case ScrollElasticityAutomatic: {
241         bool scrollbarsAllowStretching = hasEnabledHorizontalScrollbar() || !hasEnabledVerticalScrollbar();
242         bool eventPreventsStretching = newGestureIsStarting(wheelEvent) && isAlreadyPinnedInDirectionOfGesture(wheelEvent, ScrollEventAxis::Horizontal);
243         return scrollbarsAllowStretching && !eventPreventsStretching;
244     }
245     case ScrollElasticityNone:
246         return false;
247     case ScrollElasticityAllowed:
248         return true;
249     }
250
251     ASSERT_NOT_REACHED();
252     return false;
253 }
254
255 bool ScrollingTreeFrameScrollingNodeMac::allowsVerticalStretching(const PlatformWheelEvent& wheelEvent)
256 {
257     switch (verticalScrollElasticity()) {
258     case ScrollElasticityAutomatic: {
259         bool scrollbarsAllowStretching = hasEnabledVerticalScrollbar() || !hasEnabledHorizontalScrollbar();
260         bool eventPreventsStretching = newGestureIsStarting(wheelEvent) && isAlreadyPinnedInDirectionOfGesture(wheelEvent, ScrollEventAxis::Vertical);
261         return scrollbarsAllowStretching && !eventPreventsStretching;
262     }
263     case ScrollElasticityNone:
264         return false;
265     case ScrollElasticityAllowed:
266         return true;
267     }
268
269     ASSERT_NOT_REACHED();
270     return false;
271 }
272
273 IntSize ScrollingTreeFrameScrollingNodeMac::stretchAmount()
274 {
275     IntSize stretch;
276
277     if (scrollPosition().y() < minimumScrollPosition().y())
278         stretch.setHeight(scrollPosition().y() - minimumScrollPosition().y());
279     else if (scrollPosition().y() > maximumScrollPosition().y())
280         stretch.setHeight(scrollPosition().y() - maximumScrollPosition().y());
281
282     if (scrollPosition().x() < minimumScrollPosition().x())
283         stretch.setWidth(scrollPosition().x() - minimumScrollPosition().x());
284     else if (scrollPosition().x() > maximumScrollPosition().x())
285         stretch.setWidth(scrollPosition().x() - maximumScrollPosition().x());
286
287     if (scrollingTree().rootNode() == this) {
288         if (stretch.isZero())
289             scrollingTree().setMainFrameIsRubberBanding(false);
290         else
291             scrollingTree().setMainFrameIsRubberBanding(true);
292     }
293
294     return stretch;
295 }
296
297 bool ScrollingTreeFrameScrollingNodeMac::pinnedInDirection(const FloatSize& delta)
298 {
299     FloatSize limitDelta;
300
301     if (fabsf(delta.height()) >= fabsf(delta.width())) {
302         if (delta.height() < 0) {
303             // We are trying to scroll up. Make sure we are not pinned to the top.
304             limitDelta.setHeight(scrollPosition().y() - minimumScrollPosition().y());
305         } else {
306             // We are trying to scroll down. Make sure we are not pinned to the bottom.
307             limitDelta.setHeight(maximumScrollPosition().y() - scrollPosition().y());
308         }
309     } else if (delta.width()) {
310         if (delta.width() < 0) {
311             // We are trying to scroll left. Make sure we are not pinned to the left.
312             limitDelta.setWidth(scrollPosition().x() - minimumScrollPosition().x());
313         } else {
314             // We are trying to scroll right. Make sure we are not pinned to the right.
315             limitDelta.setWidth(maximumScrollPosition().x() - scrollPosition().x());
316         }
317     }
318
319     if ((delta.width() || delta.height()) && (limitDelta.width() < 1 && limitDelta.height() < 1))
320         return true;
321
322     return false;
323 }
324
325 bool ScrollingTreeFrameScrollingNodeMac::canScrollHorizontally()
326 {
327     return hasEnabledHorizontalScrollbar();
328 }
329
330 bool ScrollingTreeFrameScrollingNodeMac::canScrollVertically()
331 {
332     return hasEnabledVerticalScrollbar();
333 }
334
335 bool ScrollingTreeFrameScrollingNodeMac::shouldRubberBandInDirection(ScrollDirection)
336 {
337     return true;
338 }
339
340 void ScrollingTreeFrameScrollingNodeMac::immediateScrollBy(const FloatSize& delta)
341 {
342     scrollBy(delta);
343 }
344
345 void ScrollingTreeFrameScrollingNodeMac::immediateScrollByWithoutContentEdgeConstraints(const FloatSize& offset)
346 {
347     scrollByWithoutContentEdgeConstraints(offset);
348 }
349
350 void ScrollingTreeFrameScrollingNodeMac::stopSnapRubberbandTimer()
351 {
352     scrollingTree().setMainFrameIsRubberBanding(false);
353
354     // Since the rubberband timer has stopped, totalContentsSizeForRubberBand can be synchronized with totalContentsSize.
355     setTotalContentsSizeForRubberBand(totalContentsSize());
356 }
357
358 void ScrollingTreeFrameScrollingNodeMac::adjustScrollPositionToBoundsIfNecessary()
359 {
360     FloatPoint currentScrollPosition = scrollPosition();
361     FloatPoint constainedPosition = currentScrollPosition.constrainedBetween(minimumScrollPosition(), maximumScrollPosition());
362     immediateScrollBy(constainedPosition - currentScrollPosition);
363 }
364
365 FloatPoint ScrollingTreeFrameScrollingNodeMac::scrollPosition() const
366 {
367     if (shouldUpdateScrollLayerPositionSynchronously())
368         return m_probableMainThreadScrollPosition;
369
370     return -m_scrollLayer.get().position;
371 }
372
373 void ScrollingTreeFrameScrollingNodeMac::setScrollPosition(const FloatPoint& scrollPosition)
374 {
375     LOG_WITH_STREAM(Scrolling, stream << "ScrollingTreeFrameScrollingNodeMac::setScrollPosition " << scrollPosition << " scrollPosition(): " << this->scrollPosition() << " min: " << minimumScrollPosition() << " max: " << maximumScrollPosition());
376
377     // Scroll deltas can be non-integral with some input devices, so scrollPosition may not be integral.
378     // FIXME: when we support half-pixel scroll positions on Retina displays, this will need to round to half pixels.
379     FloatPoint roundedPosition(roundf(scrollPosition.x()), roundf(scrollPosition.y()));
380
381     ScrollingTreeFrameScrollingNode::setScrollPosition(roundedPosition);
382
383     if (scrollingTree().scrollingPerformanceLoggingEnabled())
384         logExposedUnfilledArea();
385 }
386
387 void ScrollingTreeFrameScrollingNodeMac::setScrollPositionWithoutContentEdgeConstraints(const FloatPoint& scrollPosition)
388 {
389     updateMainFramePinState(scrollPosition);
390
391     std::optional<FloatPoint> layoutViewportOrigin;
392     if (scrollingTree().visualViewportEnabled()) {
393         FloatPoint visibleContentOrigin = scrollPosition;
394         float counterScale = 1 / frameScaleFactor();
395         visibleContentOrigin.scale(counterScale, counterScale);
396         FloatRect newLayoutViewport = layoutViewportForScrollPosition(visibleContentOrigin, frameScaleFactor());
397         setLayoutViewport(newLayoutViewport);
398         layoutViewportOrigin = newLayoutViewport.location();
399     }
400
401     if (shouldUpdateScrollLayerPositionSynchronously()) {
402         m_probableMainThreadScrollPosition = scrollPosition;
403         scrollingTree().scrollingTreeNodeDidScroll(scrollingNodeID(), scrollPosition, layoutViewportOrigin, ScrollingLayerPositionAction::Set);
404         return;
405     }
406
407     setScrollLayerPosition(scrollPosition, layoutViewport());
408     scrollingTree().scrollingTreeNodeDidScroll(scrollingNodeID(), scrollPosition, layoutViewportOrigin);
409 }
410
411 void ScrollingTreeFrameScrollingNodeMac::setScrollLayerPosition(const FloatPoint& position, const FloatRect& layoutViewport)
412 {
413     ASSERT(!shouldUpdateScrollLayerPositionSynchronously());
414
415     m_scrollLayer.get().position = -position;
416
417     FloatRect visibleContentRect(position, scrollableAreaSize());
418     FloatRect fixedPositionRect;
419     ScrollBehaviorForFixedElements behaviorForFixed = StickToViewportBounds;
420
421     if (scrollingTree().visualViewportEnabled())
422         fixedPositionRect = layoutViewport;
423     else {
424         behaviorForFixed = scrollBehaviorForFixedElements();
425         
426         FloatPoint scrollPositionForFixedChildren = FrameView::scrollPositionForFixedPosition(enclosingLayoutRect(visibleContentRect), LayoutSize(totalContentsSize()),
427             LayoutPoint(position), scrollOrigin(), frameScaleFactor(), fixedElementsLayoutRelativeToFrame(), behaviorForFixed, headerHeight(), footerHeight());
428
429         fixedPositionRect = { scrollPositionForFixedChildren, visibleContentRect.size() };
430     }
431     
432     if (m_counterScrollingLayer)
433         m_counterScrollingLayer.get().position = fixedPositionRect.location();
434
435     float topContentInset = this->topContentInset();
436     if (m_insetClipLayer && m_scrolledContentsLayer && topContentInset) {
437         m_insetClipLayer.get().position = FloatPoint(m_insetClipLayer.get().position.x, FrameView::yPositionForInsetClipLayer(position, topContentInset));
438         m_scrolledContentsLayer.get().position = FrameView::positionForRootContentLayer(position, scrollOrigin(), topContentInset, headerHeight());
439         if (m_contentShadowLayer)
440             m_contentShadowLayer.get().position = m_scrolledContentsLayer.get().position;
441     }
442
443     if (m_headerLayer || m_footerLayer) {
444         // Generally the banners should have the same horizontal-position computation as a fixed element. However,
445         // the banners are not affected by the frameScaleFactor(), so if there is currently a non-1 frameScaleFactor()
446         // then we should recompute fixedPositionRect.x() for the banner with a scale factor of 1.
447         float horizontalScrollOffsetForBanner = fixedPositionRect.x();
448         if (!scrollingTree().visualViewportEnabled() && frameScaleFactor() != 1) {
449             horizontalScrollOffsetForBanner = FrameView::scrollPositionForFixedPosition(enclosingLayoutRect(visibleContentRect), LayoutSize(totalContentsSize()),
450                 LayoutPoint(position), scrollOrigin(), 1, fixedElementsLayoutRelativeToFrame(), behaviorForFixed, headerHeight(), footerHeight()).x();
451         }
452
453         if (m_headerLayer)
454             m_headerLayer.get().position = FloatPoint(horizontalScrollOffsetForBanner, FrameView::yPositionForHeaderLayer(position, topContentInset));
455
456         if (m_footerLayer)
457             m_footerLayer.get().position = FloatPoint(horizontalScrollOffsetForBanner, FrameView::yPositionForFooterLayer(position, topContentInset, totalContentsSize().height(), footerHeight()));
458     }
459
460     if (m_verticalScrollerImp || m_horizontalScrollerImp) {
461         [CATransaction begin];
462         [CATransaction lock];
463
464         if ([m_verticalScrollerImp shouldUsePresentationValue]) {
465             float presentationValue;
466             float overhangAmount;
467             ScrollableArea::computeScrollbarValueAndOverhang(position.y(), totalContentsSize().height(), visibleContentRect.height(), presentationValue, overhangAmount);
468             [m_verticalScrollerImp setPresentationValue:presentationValue];
469         }
470
471         if ([m_horizontalScrollerImp shouldUsePresentationValue]) {
472             float presentationValue;
473             float overhangAmount;
474             ScrollableArea::computeScrollbarValueAndOverhang(position.x(), totalContentsSize().width(), visibleContentRect.width(), presentationValue, overhangAmount);
475             [m_horizontalScrollerImp setPresentationValue:presentationValue];
476         }
477
478         [CATransaction unlock];
479         [CATransaction commit];
480     }
481
482     if (!m_children)
483         return;
484
485     for (auto& child : *m_children)
486         child->updateLayersAfterAncestorChange(*this, fixedPositionRect, FloatSize());
487 }
488
489 void ScrollingTreeFrameScrollingNodeMac::updateLayersAfterViewportChange(const FloatRect&, double)
490 {
491     ASSERT_NOT_REACHED();
492 }
493
494 FloatPoint ScrollingTreeFrameScrollingNodeMac::minimumScrollPosition() const
495 {
496     FloatPoint position = ScrollableArea::scrollPositionFromOffset(FloatPoint(), toFloatSize(scrollOrigin()));
497     
498     if (scrollingTree().rootNode() == this && scrollingTree().scrollPinningBehavior() == PinToBottom)
499         position.setY(maximumScrollPosition().y());
500
501     return position;
502 }
503
504 FloatPoint ScrollingTreeFrameScrollingNodeMac::maximumScrollPosition() const
505 {
506     FloatPoint position = ScrollableArea::scrollPositionFromOffset(FloatPoint(totalContentsSizeForRubberBand() - scrollableAreaSize()), toFloatSize(scrollOrigin()));
507     position = position.expandedTo(FloatPoint());
508
509     if (scrollingTree().rootNode() == this && scrollingTree().scrollPinningBehavior() == PinToTop)
510         position.setY(minimumScrollPosition().y());
511
512     return position;
513 }
514
515 void ScrollingTreeFrameScrollingNodeMac::updateMainFramePinState(const FloatPoint& scrollPosition)
516 {
517     bool pinnedToTheLeft = scrollPosition.x() <= minimumScrollPosition().x();
518     bool pinnedToTheRight = scrollPosition.x() >= maximumScrollPosition().x();
519     bool pinnedToTheTop = scrollPosition.y() <= minimumScrollPosition().y();
520     bool pinnedToTheBottom = scrollPosition.y() >= maximumScrollPosition().y();
521
522     scrollingTree().setMainFramePinState(pinnedToTheLeft, pinnedToTheRight, pinnedToTheTop, pinnedToTheBottom);
523 }
524
525 void ScrollingTreeFrameScrollingNodeMac::logExposedUnfilledArea()
526 {
527     Region paintedVisibleTiles;
528
529     Deque<CALayer*> layerQueue;
530     layerQueue.append(m_scrollLayer.get());
531     PlatformLayerList tiles;
532
533     while (!layerQueue.isEmpty() && tiles.isEmpty()) {
534         CALayer* layer = layerQueue.takeFirst();
535         NSArray* sublayers = [[layer sublayers] copy];
536
537         // If this layer is the parent of a tile, it is the parent of all of the tiles and nothing else.
538         if ([[[sublayers objectAtIndex:0] valueForKey:@"isTile"] boolValue]) {
539             for (CALayer* sublayer in sublayers)
540                 tiles.append(sublayer);
541         } else {
542             for (CALayer* sublayer in sublayers)
543                 layerQueue.append(sublayer);
544         }
545
546         [sublayers release];
547     }
548
549     FloatPoint scrollPosition = this->scrollPosition();
550     FloatRect viewPortRect(FloatPoint(), scrollableAreaSize());
551     unsigned unfilledArea = TileController::blankPixelCountForTiles(tiles, viewPortRect, IntPoint(-scrollPosition.x(), -scrollPosition.y()));
552
553     if (unfilledArea || m_lastScrollHadUnfilledPixels)
554         WTFLogAlways("SCROLLING: Exposed tileless area. Time: %f Unfilled Pixels: %u\n", WTF::monotonicallyIncreasingTime(), unfilledArea);
555
556     m_lastScrollHadUnfilledPixels = unfilledArea;
557 }
558
559 static void logThreadedScrollingMode(unsigned synchronousScrollingReasons)
560 {
561     if (synchronousScrollingReasons) {
562         StringBuilder reasonsDescription;
563
564         if (synchronousScrollingReasons & ScrollingCoordinator::ForcedOnMainThread)
565             reasonsDescription.appendLiteral("forced,");
566         if (synchronousScrollingReasons & ScrollingCoordinator::HasSlowRepaintObjects)
567             reasonsDescription.appendLiteral("slow-repaint objects,");
568         if (synchronousScrollingReasons & ScrollingCoordinator::HasViewportConstrainedObjectsWithoutSupportingFixedLayers)
569             reasonsDescription.appendLiteral("viewport-constrained objects,");
570         if (synchronousScrollingReasons & ScrollingCoordinator::HasNonLayerViewportConstrainedObjects)
571             reasonsDescription.appendLiteral("non-layer viewport-constrained objects,");
572         if (synchronousScrollingReasons & ScrollingCoordinator::IsImageDocument)
573             reasonsDescription.appendLiteral("image document,");
574
575         // Strip the trailing comma.
576         String reasonsDescriptionTrimmed = reasonsDescription.toString().left(reasonsDescription.length() - 1);
577
578         WTFLogAlways("SCROLLING: Switching to main-thread scrolling mode. Time: %f Reason(s): %s\n", WTF::monotonicallyIncreasingTime(), reasonsDescriptionTrimmed.ascii().data());
579     } else
580         WTFLogAlways("SCROLLING: Switching to threaded scrolling mode. Time: %f\n", WTF::monotonicallyIncreasingTime());
581 }
582
583 #if ENABLE(CSS_SCROLL_SNAP)
584 FloatPoint ScrollingTreeFrameScrollingNodeMac::scrollOffset() const
585 {
586     return scrollPosition();
587 }
588
589 void ScrollingTreeFrameScrollingNodeMac::immediateScrollOnAxis(ScrollEventAxis axis, float delta)
590 {
591     const FloatPoint& currentPosition = scrollPosition();
592     FloatPoint change;
593     if (axis == ScrollEventAxis::Horizontal)
594         change = FloatPoint(currentPosition.x() + delta, currentPosition.y());
595     else
596         change = FloatPoint(currentPosition.x(), currentPosition.y() + delta);
597
598     immediateScrollBy(change - currentPosition);
599 }
600
601 float ScrollingTreeFrameScrollingNodeMac::pageScaleFactor() const
602 {
603     return frameScaleFactor();
604 }
605
606 void ScrollingTreeFrameScrollingNodeMac::startScrollSnapTimer()
607 {
608     scrollingTree().setMainFrameIsScrollSnapping(true);
609 }
610
611 void ScrollingTreeFrameScrollingNodeMac::stopScrollSnapTimer()
612 {
613     scrollingTree().setMainFrameIsScrollSnapping(false);
614 }
615     
616 LayoutSize ScrollingTreeFrameScrollingNodeMac::scrollExtent() const
617 {
618     return LayoutSize(totalContentsSize());
619 }
620
621 FloatSize ScrollingTreeFrameScrollingNodeMac::viewportSize() const
622 {
623     return scrollableAreaSize();
624 }
625
626 #endif
627
628 void ScrollingTreeFrameScrollingNodeMac::deferTestsForReason(WheelEventTestTrigger::ScrollableAreaIdentifier identifier, WheelEventTestTrigger::DeferTestTriggerReason reason) const
629 {
630     if (!m_expectsWheelEventTestTrigger)
631         return;
632
633     LOG(WheelEventTestTriggers, "  ScrollingTreeFrameScrollingNodeMac::deferTestsForReason: STARTING deferral for %p because of %d", identifier, reason);
634     scrollingTree().deferTestsForReason(identifier, reason);
635 }
636     
637 void ScrollingTreeFrameScrollingNodeMac::removeTestDeferralForReason(WheelEventTestTrigger::ScrollableAreaIdentifier identifier, WheelEventTestTrigger::DeferTestTriggerReason reason) const
638 {
639     if (!m_expectsWheelEventTestTrigger)
640         return;
641     
642     LOG(WheelEventTestTriggers, "   ScrollingTreeFrameScrollingNodeMac::deferTestsForReason: ENDING deferral for %p because of %d", identifier, reason);
643     scrollingTree().removeTestDeferralForReason(identifier, reason);
644 }
645
646 } // namespace WebCore
647
648 #endif // ENABLE(ASYNC_SCROLLING) && PLATFORM(MAC)