548381bad52846c1897b9d353f9042105e9b585a
[WebKit-https.git] / Source / WebCore / page / scrolling / mac / ScrollingTreeFrameScrollingNodeMac.mm
1 /*
2  * Copyright (C) 2012, 2014 Apple Inc. All rights reserved.
3  *
4  * Redistribution and use in source and binary forms, with or without
5  * modification, are permitted provided that the following conditions
6  * are met:
7  * 1. Redistributions of source code must retain the above copyright
8  *    notice, this list of conditions and the following disclaimer.
9  * 2. Redistributions in binary form must reproduce the above copyright
10  *    notice, this list of conditions and the following disclaimer in the
11  *    documentation and/or other materials provided with the distribution.
12  *
13  * THIS SOFTWARE IS PROVIDED BY APPLE INC. AND ITS CONTRIBUTORS ``AS IS''
14  * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
15  * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
16  * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL APPLE INC. OR ITS CONTRIBUTORS
17  * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
18  * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
19  * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
20  * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
21  * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
22  * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF
23  * THE POSSIBILITY OF SUCH DAMAGE.
24  */
25
26 #import "config.h"
27 #import "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
47 namespace WebCore {
48
49 static void logThreadedScrollingMode(unsigned synchronousScrollingReasons);
50 static void logWheelEventHandlerCountChanged(unsigned);
51
52
53 PassRefPtr<ScrollingTreeFrameScrollingNode> ScrollingTreeFrameScrollingNodeMac::create(ScrollingTree& scrollingTree, ScrollingNodeID nodeID)
54 {
55     return adoptRef(new ScrollingTreeFrameScrollingNodeMac(scrollingTree, nodeID));
56 }
57
58 ScrollingTreeFrameScrollingNodeMac::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
67 ScrollingTreeFrameScrollingNodeMac::~ScrollingTreeFrameScrollingNodeMac()
68 {
69     if (m_snapRubberbandTimer)
70         CFRunLoopTimerInvalidate(m_snapRubberbandTimer.get());
71 }
72
73 void 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
126 void 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
142 void 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
161 // FIXME: We should find a way to share some of the code from newGestureIsStarting(), isAlreadyPinnedInDirectionOfGesture(),
162 // allowsVerticalStretching(), and allowsHorizontalStretching() with the implementation in ScrollAnimatorMac.
163 static bool newGestureIsStarting(const PlatformWheelEvent& wheelEvent)
164 {
165     return wheelEvent.phase() == PlatformWheelEventPhaseMayBegin || wheelEvent.phase() == PlatformWheelEventPhaseBegan;
166 }
167
168 bool ScrollingTreeFrameScrollingNodeMac::isAlreadyPinnedInDirectionOfGesture(const PlatformWheelEvent& wheelEvent, ScrollEventAxis axis)
169 {
170     switch (axis) {
171     case ScrollEventAxis::Vertical:
172         return (wheelEvent.deltaY() > 0 && scrollPosition().y() <= minimumScrollPosition().y()) || (wheelEvent.deltaY() < 0 && scrollPosition().y() >= maximumScrollPosition().y());
173     case ScrollEventAxis::Horizontal:
174         return (wheelEvent.deltaX() > 0 && scrollPosition().x() <= minimumScrollPosition().x()) || (wheelEvent.deltaX() < 0 && scrollPosition().x() >= maximumScrollPosition().x());
175     }
176
177     ASSERT_NOT_REACHED();
178     return false;
179 }
180
181 bool ScrollingTreeFrameScrollingNodeMac::allowsHorizontalStretching(const PlatformWheelEvent& wheelEvent)
182 {
183     switch (horizontalScrollElasticity()) {
184     case ScrollElasticityAutomatic: {
185         bool scrollbarsAllowStretching = hasEnabledHorizontalScrollbar() || !hasEnabledVerticalScrollbar();
186         bool eventPreventsStretching = newGestureIsStarting(wheelEvent) && isAlreadyPinnedInDirectionOfGesture(wheelEvent, ScrollEventAxis::Horizontal);
187         return scrollbarsAllowStretching && !eventPreventsStretching;
188     }
189     case ScrollElasticityNone:
190         return false;
191     case ScrollElasticityAllowed:
192         return true;
193     }
194
195     ASSERT_NOT_REACHED();
196     return false;
197 }
198
199 bool ScrollingTreeFrameScrollingNodeMac::allowsVerticalStretching(const PlatformWheelEvent& wheelEvent)
200 {
201     switch (verticalScrollElasticity()) {
202     case ScrollElasticityAutomatic: {
203         bool scrollbarsAllowStretching = hasEnabledVerticalScrollbar() || !hasEnabledHorizontalScrollbar();
204         bool eventPreventsStretching = newGestureIsStarting(wheelEvent) && isAlreadyPinnedInDirectionOfGesture(wheelEvent, ScrollEventAxis::Vertical);
205         return scrollbarsAllowStretching && !eventPreventsStretching;
206     }
207     case ScrollElasticityNone:
208         return false;
209     case ScrollElasticityAllowed:
210         return true;
211     }
212
213     ASSERT_NOT_REACHED();
214     return false;
215 }
216
217 IntSize ScrollingTreeFrameScrollingNodeMac::stretchAmount()
218 {
219     IntSize stretch;
220
221     if (scrollPosition().y() < minimumScrollPosition().y())
222         stretch.setHeight(scrollPosition().y() - minimumScrollPosition().y());
223     else if (scrollPosition().y() > maximumScrollPosition().y())
224         stretch.setHeight(scrollPosition().y() - maximumScrollPosition().y());
225
226     if (scrollPosition().x() < minimumScrollPosition().x())
227         stretch.setWidth(scrollPosition().x() - minimumScrollPosition().x());
228     else if (scrollPosition().x() > maximumScrollPosition().x())
229         stretch.setWidth(scrollPosition().x() - maximumScrollPosition().x());
230
231     if (scrollingTree().rootNode() == this) {
232         if (stretch.isZero())
233             scrollingTree().setMainFrameIsRubberBanding(false);
234         else
235             scrollingTree().setMainFrameIsRubberBanding(true);
236     }
237
238     return stretch;
239 }
240
241 bool ScrollingTreeFrameScrollingNodeMac::pinnedInDirection(const FloatSize& delta)
242 {
243     FloatSize limitDelta;
244
245     if (fabsf(delta.height()) >= fabsf(delta.width())) {
246         if (delta.height() < 0) {
247             // We are trying to scroll up. Make sure we are not pinned to the top.
248             limitDelta.setHeight(scrollPosition().y() - minimumScrollPosition().y());
249         } else {
250             // We are trying to scroll down. Make sure we are not pinned to the bottom.
251             limitDelta.setHeight(maximumScrollPosition().y() - scrollPosition().y());
252         }
253     } else if (delta.width()) {
254         if (delta.width() < 0) {
255             // We are trying to scroll left. Make sure we are not pinned to the left.
256             limitDelta.setWidth(scrollPosition().x() - minimumScrollPosition().x());
257         } else {
258             // We are trying to scroll right. Make sure we are not pinned to the right.
259             limitDelta.setWidth(maximumScrollPosition().x() - scrollPosition().x());
260         }
261     }
262
263     if ((delta.width() || delta.height()) && (limitDelta.width() < 1 && limitDelta.height() < 1))
264         return true;
265
266     return false;
267 }
268
269 bool ScrollingTreeFrameScrollingNodeMac::canScrollHorizontally()
270 {
271     return hasEnabledHorizontalScrollbar();
272 }
273
274 bool ScrollingTreeFrameScrollingNodeMac::canScrollVertically()
275 {
276     return hasEnabledVerticalScrollbar();
277 }
278
279 bool ScrollingTreeFrameScrollingNodeMac::shouldRubberBandInDirection(ScrollDirection)
280 {
281     return true;
282 }
283
284 IntPoint ScrollingTreeFrameScrollingNodeMac::absoluteScrollPosition()
285 {
286     return roundedIntPoint(scrollPosition());
287 }
288
289 void ScrollingTreeFrameScrollingNodeMac::immediateScrollBy(const FloatSize& offset)
290 {
291     scrollBy(offset);
292 }
293
294 void ScrollingTreeFrameScrollingNodeMac::immediateScrollByWithoutContentEdgeConstraints(const FloatSize& offset)
295 {
296     scrollByWithoutContentEdgeConstraints(offset);
297 }
298
299 void ScrollingTreeFrameScrollingNodeMac::startSnapRubberbandTimer()
300 {
301     ASSERT(!m_snapRubberbandTimer);
302
303     CFTimeInterval timerInterval = 1.0 / 60.0;
304
305     m_snapRubberbandTimer = adoptCF(CFRunLoopTimerCreateWithHandler(kCFAllocatorDefault, CFAbsoluteTimeGetCurrent() + timerInterval, timerInterval, 0, 0, ^(CFRunLoopTimerRef) {
306         m_scrollElasticityController.snapRubberBandTimerFired();
307     }));
308     CFRunLoopAddTimer(CFRunLoopGetCurrent(), m_snapRubberbandTimer.get(), kCFRunLoopDefaultMode);
309 }
310
311 void ScrollingTreeFrameScrollingNodeMac::stopSnapRubberbandTimer()
312 {
313     if (!m_snapRubberbandTimer)
314         return;
315
316     scrollingTree().setMainFrameIsRubberBanding(false);
317
318     // Since the rubberband timer has stopped, totalContentsSizeForRubberBand can be synchronized with totalContentsSize.
319     setTotalContentsSizeForRubberBand(totalContentsSize());
320
321     CFRunLoopTimerInvalidate(m_snapRubberbandTimer.get());
322     m_snapRubberbandTimer = nullptr;
323 }
324
325 void ScrollingTreeFrameScrollingNodeMac::adjustScrollPositionToBoundsIfNecessary()
326 {
327     FloatPoint currentScrollPosition = absoluteScrollPosition();
328     FloatPoint minPosition = minimumScrollPosition();
329     FloatPoint maxPosition = maximumScrollPosition();
330
331     float nearestXWithinBounds = std::max(std::min(currentScrollPosition.x(), maxPosition.x()), minPosition.x());
332     float nearestYWithinBounds = std::max(std::min(currentScrollPosition.y(), maxPosition.y()), minPosition.y());
333
334     FloatPoint nearestPointWithinBounds(nearestXWithinBounds, nearestYWithinBounds);
335     immediateScrollBy(nearestPointWithinBounds - currentScrollPosition);
336 }
337
338 FloatPoint ScrollingTreeFrameScrollingNodeMac::scrollPosition() const
339 {
340     if (shouldUpdateScrollLayerPositionSynchronously())
341         return m_probableMainThreadScrollPosition;
342
343     CGPoint scrollLayerPosition = m_scrollLayer.get().position;
344     return FloatPoint(-scrollLayerPosition.x + scrollOrigin().x(), -scrollLayerPosition.y + scrollOrigin().y());
345 }
346
347 void ScrollingTreeFrameScrollingNodeMac::setScrollPosition(const FloatPoint& scrollPosition)
348 {
349     // Scroll deltas can be non-integral with some input devices, so scrollPosition may not be integral.
350     // FIXME: when we support half-pixel scroll positions on Retina displays, this will need to round to half pixels.
351     FloatPoint roundedPosition(roundf(scrollPosition.x()), roundf(scrollPosition.y()));
352
353     ScrollingTreeFrameScrollingNode::setScrollPosition(roundedPosition);
354
355     if (scrollingTree().scrollingPerformanceLoggingEnabled())
356         logExposedUnfilledArea();
357 }
358
359 void ScrollingTreeFrameScrollingNodeMac::setScrollPositionWithoutContentEdgeConstraints(const FloatPoint& scrollPosition)
360 {
361     updateMainFramePinState(scrollPosition);
362
363     if (shouldUpdateScrollLayerPositionSynchronously()) {
364         m_probableMainThreadScrollPosition = scrollPosition;
365         scrollingTree().scrollingTreeNodeDidScroll(scrollingNodeID(), scrollPosition, SetScrollingLayerPosition);
366         return;
367     }
368
369     setScrollLayerPosition(scrollPosition);
370     scrollingTree().scrollingTreeNodeDidScroll(scrollingNodeID(), scrollPosition);
371 }
372
373 void ScrollingTreeFrameScrollingNodeMac::setScrollLayerPosition(const FloatPoint& position)
374 {
375     ASSERT(!shouldUpdateScrollLayerPositionSynchronously());
376     m_scrollLayer.get().position = CGPointMake(-position.x() + scrollOrigin().x(), -position.y() + scrollOrigin().y());
377
378     ScrollBehaviorForFixedElements behaviorForFixed = scrollBehaviorForFixedElements();
379     FloatPoint scrollOffset = position - toFloatSize(scrollOrigin());
380     FloatRect viewportRect(FloatPoint(), scrollableAreaSize());
381     
382     FloatSize scrollOffsetForFixedChildren = FrameView::scrollOffsetForFixedPosition(enclosingLayoutRect(viewportRect),
383         roundedLayoutSize(totalContentsSize()), roundedLayoutPoint(scrollOffset), scrollOrigin(), frameScaleFactor(), false, behaviorForFixed, headerHeight(), footerHeight());
384     
385     if (m_counterScrollingLayer)
386         m_counterScrollingLayer.get().position = FloatPoint(scrollOffsetForFixedChildren);
387
388     float topContentInset = this->topContentInset();
389     if (m_insetClipLayer && m_scrolledContentsLayer && topContentInset) {
390         m_insetClipLayer.get().position = FloatPoint(0, FrameView::yPositionForInsetClipLayer(position, topContentInset));
391         m_scrolledContentsLayer.get().position = FloatPoint(m_scrolledContentsLayer.get().position.x,
392             FrameView::yPositionForRootContentLayer(position, topContentInset, headerHeight()));
393         if (m_contentShadowLayer)
394             m_contentShadowLayer.get().position = m_scrolledContentsLayer.get().position;
395     }
396
397     if (m_headerLayer || m_footerLayer) {
398         // Generally the banners should have the same horizontal-position computation as a fixed element. However,
399         // the banners are not affected by the frameScaleFactor(), so if there is currently a non-1 frameScaleFactor()
400         // then we should recompute scrollOffsetForFixedChildren for the banner with a scale factor of 1.
401         float horizontalScrollOffsetForBanner = scrollOffsetForFixedChildren.width();
402         if (frameScaleFactor() != 1)
403             horizontalScrollOffsetForBanner = FrameView::scrollOffsetForFixedPosition(enclosingLayoutRect(viewportRect), roundedLayoutSize(totalContentsSize()), roundedLayoutPoint(scrollOffset), scrollOrigin(), 1, false, behaviorForFixed, headerHeight(), footerHeight()).width();
404
405         if (m_headerLayer)
406             m_headerLayer.get().position = FloatPoint(horizontalScrollOffsetForBanner, FrameView::yPositionForHeaderLayer(position, topContentInset));
407
408         if (m_footerLayer) {
409             m_footerLayer.get().position = FloatPoint(horizontalScrollOffsetForBanner,
410                 FrameView::yPositionForFooterLayer(position, topContentInset, totalContentsSize().height(), footerHeight()));
411         }
412     }
413
414     if (m_verticalScrollbarPainter || m_horizontalScrollbarPainter) {
415         [CATransaction begin];
416         [CATransaction lock];
417
418         if ([m_verticalScrollbarPainter shouldUsePresentationValue]) {
419             float presentationValue;
420             float overhangAmount;
421             ScrollableArea::computeScrollbarValueAndOverhang(position.y(), totalContentsSize().height(), viewportRect.height(), presentationValue, overhangAmount);
422             [m_verticalScrollbarPainter setPresentationValue:presentationValue];
423         }
424
425         if ([m_horizontalScrollbarPainter shouldUsePresentationValue]) {
426             float presentationValue;
427             float overhangAmount;
428             ScrollableArea::computeScrollbarValueAndOverhang(position.x(), totalContentsSize().width(), viewportRect.width(), presentationValue, overhangAmount);
429             [m_horizontalScrollbarPainter setPresentationValue:presentationValue];
430         }
431         [CATransaction unlock];
432         [CATransaction commit];
433     }
434
435     if (!m_children)
436         return;
437
438     viewportRect.setLocation(FloatPoint() + scrollOffsetForFixedChildren);
439
440     for (auto& child : *m_children)
441         child->updateLayersAfterAncestorChange(*this, viewportRect, FloatSize());
442 }
443
444 void ScrollingTreeFrameScrollingNodeMac::updateLayersAfterViewportChange(const FloatRect&, double)
445 {
446     ASSERT_NOT_REACHED();
447 }
448
449 FloatPoint ScrollingTreeFrameScrollingNodeMac::minimumScrollPosition() const
450 {
451     FloatPoint position;
452     
453     if (scrollingTree().rootNode() == this && scrollingTree().scrollPinningBehavior() == PinToBottom)
454         position.setY(maximumScrollPosition().y());
455
456     return position;
457 }
458
459 FloatPoint ScrollingTreeFrameScrollingNodeMac::maximumScrollPosition() const
460 {
461     FloatPoint position(totalContentsSizeForRubberBand() - scrollableAreaSize());
462     position = position.expandedTo(FloatPoint());
463
464     if (scrollingTree().rootNode() == this && scrollingTree().scrollPinningBehavior() == PinToTop)
465         position.setY(minimumScrollPosition().y());
466
467     return position;
468 }
469
470 void ScrollingTreeFrameScrollingNodeMac::updateMainFramePinState(const FloatPoint& scrollPosition)
471 {
472     bool pinnedToTheLeft = scrollPosition.x() <= minimumScrollPosition().x();
473     bool pinnedToTheRight = scrollPosition.x() >= maximumScrollPosition().x();
474     bool pinnedToTheTop = scrollPosition.y() <= minimumScrollPosition().y();
475     bool pinnedToTheBottom = scrollPosition.y() >= maximumScrollPosition().y();
476
477     scrollingTree().setMainFramePinState(pinnedToTheLeft, pinnedToTheRight, pinnedToTheTop, pinnedToTheBottom);
478 }
479
480 void ScrollingTreeFrameScrollingNodeMac::logExposedUnfilledArea()
481 {
482     Region paintedVisibleTiles;
483
484     Deque<CALayer*> layerQueue;
485     layerQueue.append(m_scrollLayer.get());
486     PlatformLayerList tiles;
487
488     while (!layerQueue.isEmpty() && tiles.isEmpty()) {
489         CALayer* layer = layerQueue.takeFirst();
490         NSArray* sublayers = [[layer sublayers] copy];
491
492         // If this layer is the parent of a tile, it is the parent of all of the tiles and nothing else.
493         if ([[[sublayers objectAtIndex:0] valueForKey:@"isTile"] boolValue]) {
494             for (CALayer* sublayer in sublayers)
495                 tiles.append(sublayer);
496         } else {
497             for (CALayer* sublayer in sublayers)
498                 layerQueue.append(sublayer);
499         }
500
501         [sublayers release];
502     }
503
504     FloatPoint scrollPosition = this->scrollPosition();
505     FloatRect viewPortRect(FloatPoint(), scrollableAreaSize());
506     unsigned unfilledArea = TileController::blankPixelCountForTiles(tiles, viewPortRect, IntPoint(-scrollPosition.x(), -scrollPosition.y()));
507
508     if (unfilledArea || m_lastScrollHadUnfilledPixels)
509         WTFLogAlways("SCROLLING: Exposed tileless area. Time: %f Unfilled Pixels: %u\n", WTF::monotonicallyIncreasingTime(), unfilledArea);
510
511     m_lastScrollHadUnfilledPixels = unfilledArea;
512 }
513
514 static void logThreadedScrollingMode(unsigned synchronousScrollingReasons)
515 {
516     if (synchronousScrollingReasons) {
517         StringBuilder reasonsDescription;
518
519         if (synchronousScrollingReasons & ScrollingCoordinator::ForcedOnMainThread)
520             reasonsDescription.append("forced,");
521         if (synchronousScrollingReasons & ScrollingCoordinator::HasSlowRepaintObjects)
522             reasonsDescription.append("slow-repaint objects,");
523         if (synchronousScrollingReasons & ScrollingCoordinator::HasViewportConstrainedObjectsWithoutSupportingFixedLayers)
524             reasonsDescription.append("viewport-constrained objects,");
525         if (synchronousScrollingReasons & ScrollingCoordinator::HasNonLayerViewportConstrainedObjects)
526             reasonsDescription.append("non-layer viewport-constrained objects,");
527         if (synchronousScrollingReasons & ScrollingCoordinator::IsImageDocument)
528             reasonsDescription.append("image document,");
529
530         // Strip the trailing comma.
531         String reasonsDescriptionTrimmed = reasonsDescription.toString().left(reasonsDescription.length() - 1);
532
533         WTFLogAlways("SCROLLING: Switching to main-thread scrolling mode. Time: %f Reason(s): %s\n", WTF::monotonicallyIncreasingTime(), reasonsDescriptionTrimmed.ascii().data());
534     } else
535         WTFLogAlways("SCROLLING: Switching to threaded scrolling mode. Time: %f\n", WTF::monotonicallyIncreasingTime());
536 }
537
538 void logWheelEventHandlerCountChanged(unsigned count)
539 {
540     WTFLogAlways("SCROLLING: Wheel event handler count changed. Time: %f Count: %u\n", WTF::monotonicallyIncreasingTime(), count);
541 }
542
543 } // namespace WebCore
544
545 #endif // ENABLE(ASYNC_SCROLLING) && PLATFORM(MAC)