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 "ScrollingTreeFrameScrollingNodeMac.h"
28
29#if ENABLE(ASYNC_SCROLLING) && PLATFORM(MAC)
30
31#import "FrameView.h"
32#import "NSScrollerImpDetails.h"
33#import "PlatformWheelEvent.h"
34#import "ScrollingCoordinator.h"
35#import "ScrollingTree.h"
36#import "ScrollingStateTree.h"
37#import "Settings.h"
38#import "TileController.h"
39#import "WebLayer.h"
40
41#import <QuartzCore/QuartzCore.h>
42#import <wtf/CurrentTime.h>
43#import <wtf/Deque.h>
44#import <wtf/text/StringBuilder.h>
45#import <wtf/text/CString.h>
46
47namespace WebCore {
48
49static void logThreadedScrollingMode(unsigned synchronousScrollingReasons);
50static void logWheelEventHandlerCountChanged(unsigned);
51
52
53PassRefPtr<ScrollingTreeFrameScrollingNode> ScrollingTreeFrameScrollingNodeMac::create(ScrollingTree& scrollingTree, ScrollingNodeID nodeID)
54{
55    return adoptRef(new ScrollingTreeFrameScrollingNodeMac(scrollingTree, nodeID));
56}
57
58ScrollingTreeFrameScrollingNodeMac::ScrollingTreeFrameScrollingNodeMac(ScrollingTree& scrollingTree, ScrollingNodeID nodeID)
59    : ScrollingTreeFrameScrollingNode(scrollingTree, nodeID)
60    , m_scrollElasticityController(this)
61    , m_verticalScrollbarPainter(0)
62    , m_horizontalScrollbarPainter(0)
63    , m_lastScrollHadUnfilledPixels(false)
64{
65}
66
67ScrollingTreeFrameScrollingNodeMac::~ScrollingTreeFrameScrollingNodeMac()
68{
69    if (m_snapRubberbandTimer)
70        CFRunLoopTimerInvalidate(m_snapRubberbandTimer.get());
71}
72
73void ScrollingTreeFrameScrollingNodeMac::updateBeforeChildren(const ScrollingStateNode& stateNode)
74{
75    ScrollingTreeFrameScrollingNode::updateBeforeChildren(stateNode);
76    const auto& scrollingStateNode = toScrollingStateFrameScrollingNode(stateNode);
77
78    if (scrollingStateNode.hasChangedProperty(ScrollingStateNode::ScrollLayer))
79        m_scrollLayer = scrollingStateNode.layer();
80
81    if (scrollingStateNode.hasChangedProperty(ScrollingStateFrameScrollingNode::ScrolledContentsLayer))
82        m_scrolledContentsLayer = scrollingStateNode.scrolledContentsLayer();
83
84    if (scrollingStateNode.hasChangedProperty(ScrollingStateFrameScrollingNode::CounterScrollingLayer))
85        m_counterScrollingLayer = scrollingStateNode.counterScrollingLayer();
86
87    if (scrollingStateNode.hasChangedProperty(ScrollingStateFrameScrollingNode::InsetClipLayer))
88        m_insetClipLayer = scrollingStateNode.insetClipLayer();
89
90    if (scrollingStateNode.hasChangedProperty(ScrollingStateFrameScrollingNode::ContentShadowLayer))
91        m_contentShadowLayer = scrollingStateNode.contentShadowLayer();
92
93    if (scrollingStateNode.hasChangedProperty(ScrollingStateFrameScrollingNode::HeaderLayer))
94        m_headerLayer = scrollingStateNode.headerLayer();
95
96    if (scrollingStateNode.hasChangedProperty(ScrollingStateFrameScrollingNode::FooterLayer))
97        m_footerLayer = scrollingStateNode.footerLayer();
98
99    if (scrollingStateNode.hasChangedProperty(ScrollingStateFrameScrollingNode::PainterForScrollbar)) {
100        m_verticalScrollbarPainter = scrollingStateNode.verticalScrollbarPainter();
101        m_horizontalScrollbarPainter = scrollingStateNode.horizontalScrollbarPainter();
102    }
103
104    if (scrollingStateNode.hasChangedProperty(ScrollingStateFrameScrollingNode::ReasonsForSynchronousScrolling)) {
105        if (shouldUpdateScrollLayerPositionSynchronously()) {
106            // We're transitioning to the slow "update scroll layer position on the main thread" mode.
107            // Initialize the probable main thread scroll position with the current scroll layer position.
108            if (scrollingStateNode.hasChangedProperty(ScrollingStateScrollingNode::RequestedScrollPosition))
109                m_probableMainThreadScrollPosition = scrollingStateNode.requestedScrollPosition();
110            else {
111                CGPoint scrollLayerPosition = m_scrollLayer.get().position;
112                m_probableMainThreadScrollPosition = FloatPoint(-scrollLayerPosition.x, -scrollLayerPosition.y);
113            }
114        }
115
116        if (scrollingTree().scrollingPerformanceLoggingEnabled())
117            logThreadedScrollingMode(synchronousScrollingReasons());
118    }
119
120    if (scrollingStateNode.hasChangedProperty(ScrollingStateFrameScrollingNode::WheelEventHandlerCount)) {
121        if (scrollingTree().scrollingPerformanceLoggingEnabled())
122            logWheelEventHandlerCountChanged(scrollingStateNode.wheelEventHandlerCount());
123    }
124}
125
126void ScrollingTreeFrameScrollingNodeMac::updateAfterChildren(const ScrollingStateNode& stateNode)
127{
128    ScrollingTreeFrameScrollingNode::updateAfterChildren(stateNode);
129
130    const auto& scrollingStateNode = toScrollingStateScrollingNode(stateNode);
131
132    // Update the scroll position after child nodes have been updated, because they need to have updated their constraints before any scrolling happens.
133    if (scrollingStateNode.hasChangedProperty(ScrollingStateScrollingNode::RequestedScrollPosition))
134        setScrollPosition(scrollingStateNode.requestedScrollPosition());
135
136    if (scrollingStateNode.hasChangedProperty(ScrollingStateNode::ScrollLayer)
137        || scrollingStateNode.hasChangedProperty(ScrollingStateScrollingNode::TotalContentsSize)
138        || scrollingStateNode.hasChangedProperty(ScrollingStateScrollingNode::ScrollableAreaSize))
139        updateMainFramePinState(scrollPosition());
140}
141
142void ScrollingTreeFrameScrollingNodeMac::handleWheelEvent(const PlatformWheelEvent& wheelEvent)
143{
144    if (!canHaveScrollbars())
145        return;
146
147    if (wheelEvent.momentumPhase() == PlatformWheelEventPhaseBegan) {
148        [m_verticalScrollbarPainter setUsePresentationValue:YES];
149        [m_horizontalScrollbarPainter setUsePresentationValue:YES];
150    }
151    if (wheelEvent.momentumPhase() == PlatformWheelEventPhaseEnded || wheelEvent.momentumPhase() == PlatformWheelEventPhaseCancelled) {
152        [m_verticalScrollbarPainter setUsePresentationValue:NO];
153        [m_horizontalScrollbarPainter setUsePresentationValue:NO];
154    }
155
156    m_scrollElasticityController.handleWheelEvent(wheelEvent);
157    scrollingTree().setOrClearLatchedNode(wheelEvent, scrollingNodeID());
158    scrollingTree().handleWheelEventPhase(wheelEvent.phase());
159}
160
161bool ScrollingTreeFrameScrollingNodeMac::allowsHorizontalStretching()
162{
163    switch (horizontalScrollElasticity()) {
164    case ScrollElasticityAutomatic:
165        return hasEnabledHorizontalScrollbar() || !hasEnabledVerticalScrollbar();
166    case ScrollElasticityNone:
167        return false;
168    case ScrollElasticityAllowed:
169        return true;
170    }
171
172    ASSERT_NOT_REACHED();
173    return false;
174}
175
176bool ScrollingTreeFrameScrollingNodeMac::allowsVerticalStretching()
177{
178    switch (verticalScrollElasticity()) {
179    case ScrollElasticityAutomatic:
180        return hasEnabledVerticalScrollbar() || !hasEnabledHorizontalScrollbar();
181    case ScrollElasticityNone:
182        return false;
183    case ScrollElasticityAllowed:
184        return true;
185    }
186
187    ASSERT_NOT_REACHED();
188    return false;
189}
190
191IntSize ScrollingTreeFrameScrollingNodeMac::stretchAmount()
192{
193    IntSize stretch;
194
195    if (scrollPosition().y() < minimumScrollPosition().y())
196        stretch.setHeight(scrollPosition().y() - minimumScrollPosition().y());
197    else if (scrollPosition().y() > maximumScrollPosition().y())
198        stretch.setHeight(scrollPosition().y() - maximumScrollPosition().y());
199
200    if (scrollPosition().x() < minimumScrollPosition().x())
201        stretch.setWidth(scrollPosition().x() - minimumScrollPosition().x());
202    else if (scrollPosition().x() > maximumScrollPosition().x())
203        stretch.setWidth(scrollPosition().x() - maximumScrollPosition().x());
204
205    if (scrollingTree().rootNode() == this) {
206        if (stretch.isZero())
207            scrollingTree().setMainFrameIsRubberBanding(false);
208        else
209            scrollingTree().setMainFrameIsRubberBanding(true);
210    }
211
212    return stretch;
213}
214
215bool ScrollingTreeFrameScrollingNodeMac::pinnedInDirection(const FloatSize& delta)
216{
217    FloatSize limitDelta;
218
219    if (fabsf(delta.height()) >= fabsf(delta.width())) {
220        if (delta.height() < 0) {
221            // We are trying to scroll up. Make sure we are not pinned to the top.
222            limitDelta.setHeight(scrollPosition().y() - minimumScrollPosition().y());
223        } else {
224            // We are trying to scroll down. Make sure we are not pinned to the bottom.
225            limitDelta.setHeight(maximumScrollPosition().y() - scrollPosition().y());
226        }
227    } else if (delta.width()) {
228        if (delta.width() < 0) {
229            // We are trying to scroll left. Make sure we are not pinned to the left.
230            limitDelta.setWidth(scrollPosition().x() - minimumScrollPosition().x());
231        } else {
232            // We are trying to scroll right. Make sure we are not pinned to the right.
233            limitDelta.setWidth(maximumScrollPosition().x() - scrollPosition().x());
234        }
235    }
236
237    if ((delta.width() || delta.height()) && (limitDelta.width() < 1 && limitDelta.height() < 1))
238        return true;
239
240    return false;
241}
242
243bool ScrollingTreeFrameScrollingNodeMac::canScrollHorizontally()
244{
245    return hasEnabledHorizontalScrollbar();
246}
247
248bool ScrollingTreeFrameScrollingNodeMac::canScrollVertically()
249{
250    return hasEnabledVerticalScrollbar();
251}
252
253bool ScrollingTreeFrameScrollingNodeMac::shouldRubberBandInDirection(ScrollDirection)
254{
255    return true;
256}
257
258IntPoint ScrollingTreeFrameScrollingNodeMac::absoluteScrollPosition()
259{
260    return roundedIntPoint(scrollPosition());
261}
262
263void ScrollingTreeFrameScrollingNodeMac::immediateScrollBy(const FloatSize& offset)
264{
265    scrollBy(offset);
266}
267
268void ScrollingTreeFrameScrollingNodeMac::immediateScrollByWithoutContentEdgeConstraints(const FloatSize& offset)
269{
270    scrollByWithoutContentEdgeConstraints(offset);
271}
272
273void ScrollingTreeFrameScrollingNodeMac::startSnapRubberbandTimer()
274{
275    ASSERT(!m_snapRubberbandTimer);
276
277    CFTimeInterval timerInterval = 1.0 / 60.0;
278
279    m_snapRubberbandTimer = adoptCF(CFRunLoopTimerCreateWithHandler(kCFAllocatorDefault, CFAbsoluteTimeGetCurrent() + timerInterval, timerInterval, 0, 0, ^(CFRunLoopTimerRef) {
280        m_scrollElasticityController.snapRubberBandTimerFired();
281    }));
282    CFRunLoopAddTimer(CFRunLoopGetCurrent(), m_snapRubberbandTimer.get(), kCFRunLoopDefaultMode);
283}
284
285void ScrollingTreeFrameScrollingNodeMac::stopSnapRubberbandTimer()
286{
287    if (!m_snapRubberbandTimer)
288        return;
289
290    scrollingTree().setMainFrameIsRubberBanding(false);
291
292    // Since the rubberband timer has stopped, totalContentsSizeForRubberBand can be synchronized with totalContentsSize.
293    setTotalContentsSizeForRubberBand(totalContentsSize());
294
295    CFRunLoopTimerInvalidate(m_snapRubberbandTimer.get());
296    m_snapRubberbandTimer = nullptr;
297}
298
299void ScrollingTreeFrameScrollingNodeMac::adjustScrollPositionToBoundsIfNecessary()
300{
301    FloatPoint currentScrollPosition = absoluteScrollPosition();
302    FloatPoint minPosition = minimumScrollPosition();
303    FloatPoint maxPosition = maximumScrollPosition();
304
305    float nearestXWithinBounds = std::max(std::min(currentScrollPosition.x(), maxPosition.x()), minPosition.x());
306    float nearestYWithinBounds = std::max(std::min(currentScrollPosition.y(), maxPosition.y()), minPosition.y());
307
308    FloatPoint nearestPointWithinBounds(nearestXWithinBounds, nearestYWithinBounds);
309    immediateScrollBy(nearestPointWithinBounds - currentScrollPosition);
310}
311
312FloatPoint ScrollingTreeFrameScrollingNodeMac::scrollPosition() const
313{
314    if (shouldUpdateScrollLayerPositionSynchronously())
315        return m_probableMainThreadScrollPosition;
316
317    CGPoint scrollLayerPosition = m_scrollLayer.get().position;
318    return FloatPoint(-scrollLayerPosition.x + scrollOrigin().x(), -scrollLayerPosition.y + scrollOrigin().y());
319}
320
321void ScrollingTreeFrameScrollingNodeMac::setScrollPosition(const FloatPoint& scrollPosition)
322{
323    // Scroll deltas can be non-integral with some input devices, so scrollPosition may not be integral.
324    // FIXME: when we support half-pixel scroll positions on Retina displays, this will need to round to half pixels.
325    FloatPoint roundedPosition(roundf(scrollPosition.x()), roundf(scrollPosition.y()));
326
327    ScrollingTreeFrameScrollingNode::setScrollPosition(roundedPosition);
328
329    if (scrollingTree().scrollingPerformanceLoggingEnabled())
330        logExposedUnfilledArea();
331}
332
333void ScrollingTreeFrameScrollingNodeMac::setScrollPositionWithoutContentEdgeConstraints(const FloatPoint& scrollPosition)
334{
335    updateMainFramePinState(scrollPosition);
336
337    if (shouldUpdateScrollLayerPositionSynchronously()) {
338        m_probableMainThreadScrollPosition = scrollPosition;
339        scrollingTree().scrollingTreeNodeDidScroll(scrollingNodeID(), scrollPosition, SetScrollingLayerPosition);
340        return;
341    }
342
343    setScrollLayerPosition(scrollPosition);
344    scrollingTree().scrollingTreeNodeDidScroll(scrollingNodeID(), scrollPosition);
345}
346
347void ScrollingTreeFrameScrollingNodeMac::setScrollLayerPosition(const FloatPoint& position)
348{
349    ASSERT(!shouldUpdateScrollLayerPositionSynchronously());
350    m_scrollLayer.get().position = CGPointMake(-position.x() + scrollOrigin().x(), -position.y() + scrollOrigin().y());
351
352    ScrollBehaviorForFixedElements behaviorForFixed = scrollBehaviorForFixedElements();
353    FloatPoint scrollOffset = position - toFloatSize(scrollOrigin());
354    FloatRect viewportRect(FloatPoint(), scrollableAreaSize());
355
356    FloatSize scrollOffsetForFixedChildren = FrameView::scrollOffsetForFixedPosition(enclosingLayoutRect(viewportRect),
357        roundedLayoutSize(totalContentsSize()), roundedLayoutPoint(scrollOffset), scrollOrigin(), frameScaleFactor(), false, behaviorForFixed, headerHeight(), footerHeight());
358
359    if (m_counterScrollingLayer)
360        m_counterScrollingLayer.get().position = FloatPoint(scrollOffsetForFixedChildren);
361
362    float topContentInset = this->topContentInset();
363    if (m_insetClipLayer && m_scrolledContentsLayer && topContentInset) {
364        m_insetClipLayer.get().position = FloatPoint(0, FrameView::yPositionForInsetClipLayer(position, topContentInset));
365        m_scrolledContentsLayer.get().position = FloatPoint(m_scrolledContentsLayer.get().position.x,
366            FrameView::yPositionForRootContentLayer(position, topContentInset, headerHeight()));
367        if (m_contentShadowLayer)
368            m_contentShadowLayer.get().position = m_scrolledContentsLayer.get().position;
369    }
370
371    if (m_headerLayer || m_footerLayer) {
372        // Generally the banners should have the same horizontal-position computation as a fixed element. However,
373        // the banners are not affected by the frameScaleFactor(), so if there is currently a non-1 frameScaleFactor()
374        // then we should recompute scrollOffsetForFixedChildren for the banner with a scale factor of 1.
375        float horizontalScrollOffsetForBanner = scrollOffsetForFixedChildren.width();
376        if (frameScaleFactor() != 1)
377            horizontalScrollOffsetForBanner = FrameView::scrollOffsetForFixedPosition(enclosingLayoutRect(viewportRect), roundedLayoutSize(totalContentsSize()), roundedLayoutPoint(scrollOffset), scrollOrigin(), 1, false, behaviorForFixed, headerHeight(), footerHeight()).width();
378
379        if (m_headerLayer)
380            m_headerLayer.get().position = FloatPoint(horizontalScrollOffsetForBanner, FrameView::yPositionForHeaderLayer(position, topContentInset));
381
382        if (m_footerLayer) {
383            m_footerLayer.get().position = FloatPoint(horizontalScrollOffsetForBanner,
384                FrameView::yPositionForFooterLayer(position, topContentInset, totalContentsSize().height(), footerHeight()));
385        }
386    }
387
388    if (m_verticalScrollbarPainter || m_horizontalScrollbarPainter) {
389        [CATransaction begin];
390        [CATransaction lock];
391
392        if ([m_verticalScrollbarPainter shouldUsePresentationValue]) {
393            float presentationValue;
394            float overhangAmount;
395            ScrollableArea::computeScrollbarValueAndOverhang(position.y(), totalContentsSize().height(), viewportRect.height(), presentationValue, overhangAmount);
396            [m_verticalScrollbarPainter setPresentationValue:presentationValue];
397        }
398
399        if ([m_horizontalScrollbarPainter shouldUsePresentationValue]) {
400            float presentationValue;
401            float overhangAmount;
402            ScrollableArea::computeScrollbarValueAndOverhang(position.x(), totalContentsSize().width(), viewportRect.width(), presentationValue, overhangAmount);
403            [m_horizontalScrollbarPainter setPresentationValue:presentationValue];
404        }
405        [CATransaction unlock];
406        [CATransaction commit];
407    }
408
409    if (!m_children)
410        return;
411
412    viewportRect.setLocation(FloatPoint() + scrollOffsetForFixedChildren);
413
414    for (auto& child : *m_children)
415        child->updateLayersAfterAncestorChange(*this, viewportRect, FloatSize());
416}
417
418void ScrollingTreeFrameScrollingNodeMac::updateLayersAfterViewportChange(const FloatRect&, double)
419{
420    ASSERT_NOT_REACHED();
421}
422
423FloatPoint ScrollingTreeFrameScrollingNodeMac::minimumScrollPosition() const
424{
425    FloatPoint position;
426
427    if (scrollingTree().rootNode() == this && scrollingTree().scrollPinningBehavior() == PinToBottom)
428        position.setY(maximumScrollPosition().y());
429
430    return position;
431}
432
433FloatPoint ScrollingTreeFrameScrollingNodeMac::maximumScrollPosition() const
434{
435    FloatPoint position(totalContentsSizeForRubberBand() - scrollableAreaSize());
436    position = position.expandedTo(FloatPoint());
437
438    if (scrollingTree().rootNode() == this && scrollingTree().scrollPinningBehavior() == PinToTop)
439        position.setY(minimumScrollPosition().y());
440
441    return position;
442}
443
444void ScrollingTreeFrameScrollingNodeMac::updateMainFramePinState(const FloatPoint& scrollPosition)
445{
446    bool pinnedToTheLeft = scrollPosition.x() <= minimumScrollPosition().x();
447    bool pinnedToTheRight = scrollPosition.x() >= maximumScrollPosition().x();
448    bool pinnedToTheTop = scrollPosition.y() <= minimumScrollPosition().y();
449    bool pinnedToTheBottom = scrollPosition.y() >= maximumScrollPosition().y();
450
451    scrollingTree().setMainFramePinState(pinnedToTheLeft, pinnedToTheRight, pinnedToTheTop, pinnedToTheBottom);
452}
453
454void ScrollingTreeFrameScrollingNodeMac::logExposedUnfilledArea()
455{
456    Region paintedVisibleTiles;
457
458    Deque<CALayer*> layerQueue;
459    layerQueue.append(m_scrollLayer.get());
460    PlatformLayerList tiles;
461
462    while (!layerQueue.isEmpty() && tiles.isEmpty()) {
463        CALayer* layer = layerQueue.takeFirst();
464        NSArray* sublayers = [[layer sublayers] copy];
465
466        // If this layer is the parent of a tile, it is the parent of all of the tiles and nothing else.
467        if ([[[sublayers objectAtIndex:0] valueForKey:@"isTile"] boolValue]) {
468            for (CALayer* sublayer in sublayers)
469                tiles.append(sublayer);
470        } else {
471            for (CALayer* sublayer in sublayers)
472                layerQueue.append(sublayer);
473        }
474
475        [sublayers release];
476    }
477
478    FloatPoint scrollPosition = this->scrollPosition();
479    FloatRect viewPortRect(FloatPoint(), scrollableAreaSize());
480    unsigned unfilledArea = TileController::blankPixelCountForTiles(tiles, viewPortRect, IntPoint(-scrollPosition.x(), -scrollPosition.y()));
481
482    if (unfilledArea || m_lastScrollHadUnfilledPixels)
483        WTFLogAlways("SCROLLING: Exposed tileless area. Time: %f Unfilled Pixels: %u\n", WTF::monotonicallyIncreasingTime(), unfilledArea);
484
485    m_lastScrollHadUnfilledPixels = unfilledArea;
486}
487
488static void logThreadedScrollingMode(unsigned synchronousScrollingReasons)
489{
490    if (synchronousScrollingReasons) {
491        StringBuilder reasonsDescription;
492
493        if (synchronousScrollingReasons & ScrollingCoordinator::ForcedOnMainThread)
494            reasonsDescription.append("forced,");
495        if (synchronousScrollingReasons & ScrollingCoordinator::HasSlowRepaintObjects)
496            reasonsDescription.append("slow-repaint objects,");
497        if (synchronousScrollingReasons & ScrollingCoordinator::HasViewportConstrainedObjectsWithoutSupportingFixedLayers)
498            reasonsDescription.append("viewport-constrained objects,");
499        if (synchronousScrollingReasons & ScrollingCoordinator::HasNonLayerViewportConstrainedObjects)
500            reasonsDescription.append("non-layer viewport-constrained objects,");
501        if (synchronousScrollingReasons & ScrollingCoordinator::IsImageDocument)
502            reasonsDescription.append("image document,");
503
504        // Strip the trailing comma.
505        String reasonsDescriptionTrimmed = reasonsDescription.toString().left(reasonsDescription.length() - 1);
506
507        WTFLogAlways("SCROLLING: Switching to main-thread scrolling mode. Time: %f Reason(s): %s\n", WTF::monotonicallyIncreasingTime(), reasonsDescriptionTrimmed.ascii().data());
508    } else
509        WTFLogAlways("SCROLLING: Switching to threaded scrolling mode. Time: %f\n", WTF::monotonicallyIncreasingTime());
510}
511
512void logWheelEventHandlerCountChanged(unsigned count)
513{
514    WTFLogAlways("SCROLLING: Wheel event handler count changed. Time: %f Count: %u\n", WTF::monotonicallyIncreasingTime(), count);
515}
516
517} // namespace WebCore
518
519#endif // ENABLE(ASYNC_SCROLLING) && PLATFORM(MAC)
520