4a580200aebde5322ffe62fb59b27fea3b9ac8da
[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     if (shouldUpdateScrollLayerPositionSynchronously()) {
392         m_probableMainThreadScrollPosition = scrollPosition;
393         scrollingTree().scrollingTreeNodeDidScroll(scrollingNodeID(), scrollPosition, SetScrollingLayerPosition);
394         return;
395     }
396
397     setScrollLayerPosition(scrollPosition);
398     scrollingTree().scrollingTreeNodeDidScroll(scrollingNodeID(), scrollPosition);
399 }
400
401 void ScrollingTreeFrameScrollingNodeMac::setScrollLayerPosition(const FloatPoint& position)
402 {
403     ASSERT(!shouldUpdateScrollLayerPositionSynchronously());
404
405     m_scrollLayer.get().position = -position;
406
407     ScrollBehaviorForFixedElements behaviorForFixed = scrollBehaviorForFixedElements();
408     FloatRect viewportRect(position, scrollableAreaSize());
409     
410     FloatPoint scrollPositionForFixedChildren = FrameView::scrollPositionForFixedPosition(enclosingLayoutRect(viewportRect), LayoutSize(totalContentsSize()), LayoutPoint(position), scrollOrigin(), frameScaleFactor(),
411         fixedElementsLayoutRelativeToFrame(), behaviorForFixed, headerHeight(), footerHeight());
412     
413     if (m_counterScrollingLayer)
414         m_counterScrollingLayer.get().position = scrollPositionForFixedChildren;
415
416     float topContentInset = this->topContentInset();
417     if (m_insetClipLayer && m_scrolledContentsLayer && topContentInset) {
418         m_insetClipLayer.get().position = FloatPoint(m_insetClipLayer.get().position.x, FrameView::yPositionForInsetClipLayer(position, topContentInset));
419         m_scrolledContentsLayer.get().position = FrameView::positionForRootContentLayer(position, scrollOrigin(), topContentInset, headerHeight());
420         if (m_contentShadowLayer)
421             m_contentShadowLayer.get().position = m_scrolledContentsLayer.get().position;
422     }
423
424     if (m_headerLayer || m_footerLayer) {
425         // Generally the banners should have the same horizontal-position computation as a fixed element. However,
426         // the banners are not affected by the frameScaleFactor(), so if there is currently a non-1 frameScaleFactor()
427         // then we should recompute scrollOffsetForFixedChildren for the banner with a scale factor of 1.
428         float horizontalScrollOffsetForBanner = scrollPositionForFixedChildren.x();
429         if (frameScaleFactor() != 1)
430             horizontalScrollOffsetForBanner = FrameView::scrollPositionForFixedPosition(enclosingLayoutRect(viewportRect), LayoutSize(totalContentsSize()), LayoutPoint(position), scrollOrigin(), 1, fixedElementsLayoutRelativeToFrame(), behaviorForFixed, headerHeight(), footerHeight()).x();
431
432         if (m_headerLayer)
433             m_headerLayer.get().position = FloatPoint(horizontalScrollOffsetForBanner, FrameView::yPositionForHeaderLayer(position, topContentInset));
434
435         if (m_footerLayer) {
436             m_footerLayer.get().position = FloatPoint(horizontalScrollOffsetForBanner,
437                 FrameView::yPositionForFooterLayer(position, topContentInset, totalContentsSize().height(), footerHeight()));
438         }
439     }
440
441     if (m_verticalScrollerImp || m_horizontalScrollerImp) {
442         [CATransaction begin];
443         [CATransaction lock];
444
445         if ([m_verticalScrollerImp shouldUsePresentationValue]) {
446             float presentationValue;
447             float overhangAmount;
448             ScrollableArea::computeScrollbarValueAndOverhang(position.y(), totalContentsSize().height(), viewportRect.height(), presentationValue, overhangAmount);
449             [m_verticalScrollerImp setPresentationValue:presentationValue];
450         }
451
452         if ([m_horizontalScrollerImp shouldUsePresentationValue]) {
453             float presentationValue;
454             float overhangAmount;
455             ScrollableArea::computeScrollbarValueAndOverhang(position.x(), totalContentsSize().width(), viewportRect.width(), presentationValue, overhangAmount);
456             [m_horizontalScrollerImp setPresentationValue:presentationValue];
457         }
458         [CATransaction unlock];
459         [CATransaction commit];
460     }
461
462     if (!m_children)
463         return;
464
465     viewportRect.setLocation(scrollPositionForFixedChildren);
466
467     for (auto& child : *m_children)
468         child->updateLayersAfterAncestorChange(*this, viewportRect, FloatSize());
469 }
470
471 void ScrollingTreeFrameScrollingNodeMac::updateLayersAfterViewportChange(const FloatRect&, double)
472 {
473     ASSERT_NOT_REACHED();
474 }
475
476 FloatPoint ScrollingTreeFrameScrollingNodeMac::minimumScrollPosition() const
477 {
478     FloatPoint position = ScrollableArea::scrollPositionFromOffset(FloatPoint(), toFloatSize(scrollOrigin()));
479     
480     if (scrollingTree().rootNode() == this && scrollingTree().scrollPinningBehavior() == PinToBottom)
481         position.setY(maximumScrollPosition().y());
482
483     return position;
484 }
485
486 FloatPoint ScrollingTreeFrameScrollingNodeMac::maximumScrollPosition() const
487 {
488     FloatPoint position = ScrollableArea::scrollPositionFromOffset(FloatPoint(totalContentsSizeForRubberBand() - scrollableAreaSize()), toFloatSize(scrollOrigin()));
489     position = position.expandedTo(FloatPoint());
490
491     if (scrollingTree().rootNode() == this && scrollingTree().scrollPinningBehavior() == PinToTop)
492         position.setY(minimumScrollPosition().y());
493
494     return position;
495 }
496
497 void ScrollingTreeFrameScrollingNodeMac::updateMainFramePinState(const FloatPoint& scrollPosition)
498 {
499     bool pinnedToTheLeft = scrollPosition.x() <= minimumScrollPosition().x();
500     bool pinnedToTheRight = scrollPosition.x() >= maximumScrollPosition().x();
501     bool pinnedToTheTop = scrollPosition.y() <= minimumScrollPosition().y();
502     bool pinnedToTheBottom = scrollPosition.y() >= maximumScrollPosition().y();
503
504     scrollingTree().setMainFramePinState(pinnedToTheLeft, pinnedToTheRight, pinnedToTheTop, pinnedToTheBottom);
505 }
506
507 void ScrollingTreeFrameScrollingNodeMac::logExposedUnfilledArea()
508 {
509     Region paintedVisibleTiles;
510
511     Deque<CALayer*> layerQueue;
512     layerQueue.append(m_scrollLayer.get());
513     PlatformLayerList tiles;
514
515     while (!layerQueue.isEmpty() && tiles.isEmpty()) {
516         CALayer* layer = layerQueue.takeFirst();
517         NSArray* sublayers = [[layer sublayers] copy];
518
519         // If this layer is the parent of a tile, it is the parent of all of the tiles and nothing else.
520         if ([[[sublayers objectAtIndex:0] valueForKey:@"isTile"] boolValue]) {
521             for (CALayer* sublayer in sublayers)
522                 tiles.append(sublayer);
523         } else {
524             for (CALayer* sublayer in sublayers)
525                 layerQueue.append(sublayer);
526         }
527
528         [sublayers release];
529     }
530
531     FloatPoint scrollPosition = this->scrollPosition();
532     FloatRect viewPortRect(FloatPoint(), scrollableAreaSize());
533     unsigned unfilledArea = TileController::blankPixelCountForTiles(tiles, viewPortRect, IntPoint(-scrollPosition.x(), -scrollPosition.y()));
534
535     if (unfilledArea || m_lastScrollHadUnfilledPixels)
536         WTFLogAlways("SCROLLING: Exposed tileless area. Time: %f Unfilled Pixels: %u\n", WTF::monotonicallyIncreasingTime(), unfilledArea);
537
538     m_lastScrollHadUnfilledPixels = unfilledArea;
539 }
540
541 static void logThreadedScrollingMode(unsigned synchronousScrollingReasons)
542 {
543     if (synchronousScrollingReasons) {
544         StringBuilder reasonsDescription;
545
546         if (synchronousScrollingReasons & ScrollingCoordinator::ForcedOnMainThread)
547             reasonsDescription.appendLiteral("forced,");
548         if (synchronousScrollingReasons & ScrollingCoordinator::HasSlowRepaintObjects)
549             reasonsDescription.appendLiteral("slow-repaint objects,");
550         if (synchronousScrollingReasons & ScrollingCoordinator::HasViewportConstrainedObjectsWithoutSupportingFixedLayers)
551             reasonsDescription.appendLiteral("viewport-constrained objects,");
552         if (synchronousScrollingReasons & ScrollingCoordinator::HasNonLayerViewportConstrainedObjects)
553             reasonsDescription.appendLiteral("non-layer viewport-constrained objects,");
554         if (synchronousScrollingReasons & ScrollingCoordinator::IsImageDocument)
555             reasonsDescription.appendLiteral("image document,");
556
557         // Strip the trailing comma.
558         String reasonsDescriptionTrimmed = reasonsDescription.toString().left(reasonsDescription.length() - 1);
559
560         WTFLogAlways("SCROLLING: Switching to main-thread scrolling mode. Time: %f Reason(s): %s\n", WTF::monotonicallyIncreasingTime(), reasonsDescriptionTrimmed.ascii().data());
561     } else
562         WTFLogAlways("SCROLLING: Switching to threaded scrolling mode. Time: %f\n", WTF::monotonicallyIncreasingTime());
563 }
564
565 #if ENABLE(CSS_SCROLL_SNAP)
566 LayoutUnit ScrollingTreeFrameScrollingNodeMac::scrollOffsetOnAxis(ScrollEventAxis axis) const
567 {
568     const FloatPoint& currentPosition = scrollPosition();
569     return axis == ScrollEventAxis::Horizontal ? currentPosition.x() : currentPosition.y();
570 }
571
572 void ScrollingTreeFrameScrollingNodeMac::immediateScrollOnAxis(ScrollEventAxis axis, float delta)
573 {
574     const FloatPoint& currentPosition = scrollPosition();
575     FloatPoint change;
576     if (axis == ScrollEventAxis::Horizontal)
577         change = FloatPoint(currentPosition.x() + delta, currentPosition.y());
578     else
579         change = FloatPoint(currentPosition.x(), currentPosition.y() + delta);
580
581     immediateScrollBy(change - currentPosition);
582 }
583
584 float ScrollingTreeFrameScrollingNodeMac::pageScaleFactor() const
585 {
586     return frameScaleFactor();
587 }
588
589 void ScrollingTreeFrameScrollingNodeMac::startScrollSnapTimer()
590 {
591     scrollingTree().setMainFrameIsScrollSnapping(true);
592 }
593
594 void ScrollingTreeFrameScrollingNodeMac::stopScrollSnapTimer()
595 {
596     scrollingTree().setMainFrameIsScrollSnapping(false);
597 }
598     
599 LayoutSize ScrollingTreeFrameScrollingNodeMac::scrollExtent() const
600 {
601     return LayoutSize(totalContentsSize());
602 }
603 #endif
604
605 void ScrollingTreeFrameScrollingNodeMac::deferTestsForReason(WheelEventTestTrigger::ScrollableAreaIdentifier identifier, WheelEventTestTrigger::DeferTestTriggerReason reason) const
606 {
607     if (!m_expectsWheelEventTestTrigger)
608         return;
609
610     LOG(WheelEventTestTriggers, "  ScrollingTreeFrameScrollingNodeMac::deferTestsForReason: STARTING deferral for %p because of %d", identifier, reason);
611     scrollingTree().deferTestsForReason(identifier, reason);
612 }
613     
614 void ScrollingTreeFrameScrollingNodeMac::removeTestDeferralForReason(WheelEventTestTrigger::ScrollableAreaIdentifier identifier, WheelEventTestTrigger::DeferTestTriggerReason reason) const
615 {
616     if (!m_expectsWheelEventTestTrigger)
617         return;
618     
619     LOG(WheelEventTestTriggers, "   ScrollingTreeFrameScrollingNodeMac::deferTestsForReason: ENDING deferral for %p because of %d", identifier, reason);
620     scrollingTree().removeTestDeferralForReason(identifier, reason);
621 }
622
623 } // namespace WebCore
624
625 #endif // ENABLE(ASYNC_SCROLLING) && PLATFORM(MAC)