2 * Copyright (C) 2012 Apple Inc. All rights reserved.
4 * Redistribution and use in source and binary forms, with or without
5 * modification, are permitted provided that the following conditions
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.
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.
27 #include "ScrollingTreeScrollingNodeMac.h"
29 #if ENABLE(THREADED_SCROLLING)
31 #include "PlatformWheelEvent.h"
32 #include "ScrollingCoordinator.h"
33 #include "ScrollingTree.h"
34 #include "ScrollingStateTree.h"
36 #include "TileCache.h"
37 #include "WebTileLayer.h"
39 #include <wtf/CurrentTime.h>
40 #include <wtf/Deque.h>
41 #include <wtf/text/StringBuilder.h>
42 #include <wtf/text/CString.h>
46 static void logThreadedScrollingMode(unsigned mainThreadScrollingReasons);
47 static void logWheelEventHandlerCountChanged(unsigned);
50 PassOwnPtr<ScrollingTreeScrollingNode> ScrollingTreeScrollingNode::create(ScrollingTree* scrollingTree, ScrollingNodeID nodeID)
52 return adoptPtr(new ScrollingTreeScrollingNodeMac(scrollingTree, nodeID));
55 ScrollingTreeScrollingNodeMac::ScrollingTreeScrollingNodeMac(ScrollingTree* scrollingTree, ScrollingNodeID nodeID)
56 : ScrollingTreeScrollingNode(scrollingTree, nodeID)
57 , m_scrollElasticityController(this)
58 , m_lastScrollHadUnfilledPixels(false)
62 ScrollingTreeScrollingNodeMac::~ScrollingTreeScrollingNodeMac()
64 if (m_snapRubberbandTimer)
65 CFRunLoopTimerInvalidate(m_snapRubberbandTimer.get());
68 void ScrollingTreeScrollingNodeMac::updateBeforeChildren(ScrollingStateNode* stateNode)
70 ScrollingTreeScrollingNode::updateBeforeChildren(stateNode);
71 ScrollingStateScrollingNode* scrollingStateNode = toScrollingStateScrollingNode(stateNode);
73 if (scrollingStateNode->hasChangedProperty(ScrollingStateNode::ScrollLayer))
74 m_scrollLayer = scrollingStateNode->platformScrollLayer();
76 if (scrollingStateNode->hasChangedProperty(ScrollingStateScrollingNode::CounterScrollingLayer))
77 m_counterScrollingLayer = scrollingStateNode->counterScrollingPlatformLayer();
79 if (scrollingStateNode->hasChangedProperty(ScrollingStateScrollingNode::ShouldUpdateScrollLayerPositionOnMainThread)) {
80 unsigned mainThreadScrollingReasons = this->shouldUpdateScrollLayerPositionOnMainThread();
82 if (mainThreadScrollingReasons) {
83 // We're transitioning to the slow "update scroll layer position on the main thread" mode.
84 // Initialize the probable main thread scroll position with the current scroll layer position.
85 if (scrollingStateNode->hasChangedProperty(ScrollingStateScrollingNode::RequestedScrollPosition))
86 m_probableMainThreadScrollPosition = scrollingStateNode->requestedScrollPosition();
88 CGPoint scrollLayerPosition = m_scrollLayer.get().position;
89 m_probableMainThreadScrollPosition = IntPoint(-scrollLayerPosition.x, -scrollLayerPosition.y);
93 if (scrollingTree()->scrollingPerformanceLoggingEnabled())
94 logThreadedScrollingMode(mainThreadScrollingReasons);
97 if (scrollingStateNode->hasChangedProperty(ScrollingStateScrollingNode::WheelEventHandlerCount)) {
98 if (scrollingTree()->scrollingPerformanceLoggingEnabled())
99 logWheelEventHandlerCountChanged(scrollingStateNode->wheelEventHandlerCount());
103 void ScrollingTreeScrollingNodeMac::updateAfterChildren(ScrollingStateNode* stateNode)
105 ScrollingTreeScrollingNode::updateAfterChildren(stateNode);
107 ScrollingStateScrollingNode* scrollingStateNode = toScrollingStateScrollingNode(stateNode);
109 // Update the scroll position after child nodes have been updated, because they need to have updated their constraints before any scrolling happens.
110 if (scrollingStateNode->hasChangedProperty(ScrollingStateScrollingNode::RequestedScrollPosition))
111 setScrollPosition(scrollingStateNode->requestedScrollPosition());
113 if (scrollingStateNode->hasChangedProperty(ScrollingStateNode::ScrollLayer) || scrollingStateNode->hasChangedProperty(ScrollingStateScrollingNode::ContentsSize) || scrollingStateNode->hasChangedProperty(ScrollingStateScrollingNode::ViewportRect))
114 updateMainFramePinState(scrollPosition());
117 void ScrollingTreeScrollingNodeMac::handleWheelEvent(const PlatformWheelEvent& wheelEvent)
119 if (!canHaveScrollbars())
122 m_scrollElasticityController.handleWheelEvent(wheelEvent);
123 scrollingTree()->handleWheelEventPhase(wheelEvent.phase());
126 bool ScrollingTreeScrollingNodeMac::allowsHorizontalStretching()
128 switch (horizontalScrollElasticity()) {
129 case ScrollElasticityAutomatic:
130 return hasEnabledHorizontalScrollbar() || !hasEnabledVerticalScrollbar();
131 case ScrollElasticityNone:
133 case ScrollElasticityAllowed:
137 ASSERT_NOT_REACHED();
141 bool ScrollingTreeScrollingNodeMac::allowsVerticalStretching()
143 switch (verticalScrollElasticity()) {
144 case ScrollElasticityAutomatic:
145 return hasEnabledVerticalScrollbar() || !hasEnabledHorizontalScrollbar();
146 case ScrollElasticityNone:
148 case ScrollElasticityAllowed:
152 ASSERT_NOT_REACHED();
156 IntSize ScrollingTreeScrollingNodeMac::stretchAmount()
160 if (scrollPosition().y() < minimumScrollPosition().y())
161 stretch.setHeight(scrollPosition().y() - minimumScrollPosition().y());
162 else if (scrollPosition().y() > maximumScrollPosition().y())
163 stretch.setHeight(scrollPosition().y() - maximumScrollPosition().y());
165 if (scrollPosition().x() < minimumScrollPosition().x())
166 stretch.setWidth(scrollPosition().x() - minimumScrollPosition().x());
167 else if (scrollPosition().x() > maximumScrollPosition().x())
168 stretch.setWidth(scrollPosition().x() - maximumScrollPosition().x());
170 if (scrollingTree()->rootNode() == this) {
171 if (stretch.isZero())
172 scrollingTree()->setMainFrameIsRubberBanding(false);
174 scrollingTree()->setMainFrameIsRubberBanding(true);
180 bool ScrollingTreeScrollingNodeMac::pinnedInDirection(const FloatSize& delta)
182 FloatSize limitDelta;
184 if (fabsf(delta.height()) >= fabsf(delta.width())) {
185 if (delta.height() < 0) {
186 // We are trying to scroll up. Make sure we are not pinned to the top
187 limitDelta.setHeight(scrollPosition().y() - minimumScrollPosition().y());
189 // We are trying to scroll down. Make sure we are not pinned to the bottom
190 limitDelta.setHeight(maximumScrollPosition().y() - scrollPosition().y());
192 } else if (delta.width()) {
193 if (delta.width() < 0) {
194 // We are trying to scroll left. Make sure we are not pinned to the left
195 limitDelta.setHeight(scrollPosition().x() - minimumScrollPosition().x());
197 // We are trying to scroll right. Make sure we are not pinned to the right
198 limitDelta.setHeight(maximumScrollPosition().x() - scrollPosition().x());
202 if ((delta.width() || delta.height()) && (limitDelta.width() < 1 && limitDelta.height() < 1))
208 bool ScrollingTreeScrollingNodeMac::canScrollHorizontally()
210 return hasEnabledHorizontalScrollbar();
213 bool ScrollingTreeScrollingNodeMac::canScrollVertically()
215 return hasEnabledVerticalScrollbar();
218 bool ScrollingTreeScrollingNodeMac::shouldRubberBandInDirection(ScrollDirection direction)
220 if (direction == ScrollLeft)
221 return !scrollingTree()->canGoBack();
222 if (direction == ScrollRight)
223 return !scrollingTree()->canGoForward();
225 ASSERT_NOT_REACHED();
229 IntPoint ScrollingTreeScrollingNodeMac::absoluteScrollPosition()
231 return scrollPosition();
234 void ScrollingTreeScrollingNodeMac::immediateScrollBy(const FloatSize& offset)
236 scrollBy(roundedIntSize(offset));
239 void ScrollingTreeScrollingNodeMac::immediateScrollByWithoutContentEdgeConstraints(const FloatSize& offset)
241 scrollByWithoutContentEdgeConstraints(roundedIntSize(offset));
244 void ScrollingTreeScrollingNodeMac::startSnapRubberbandTimer()
246 ASSERT(!m_snapRubberbandTimer);
248 CFTimeInterval timerInterval = 1.0 / 60.0;
250 m_snapRubberbandTimer = adoptCF(CFRunLoopTimerCreateWithHandler(kCFAllocatorDefault, CFAbsoluteTimeGetCurrent() + timerInterval, timerInterval, 0, 0, ^(CFRunLoopTimerRef) {
251 m_scrollElasticityController.snapRubberBandTimerFired();
253 CFRunLoopAddTimer(CFRunLoopGetCurrent(), m_snapRubberbandTimer.get(), kCFRunLoopDefaultMode);
256 void ScrollingTreeScrollingNodeMac::stopSnapRubberbandTimer()
258 if (!m_snapRubberbandTimer)
261 scrollingTree()->setMainFrameIsRubberBanding(false);
263 CFRunLoopTimerInvalidate(m_snapRubberbandTimer.get());
264 m_snapRubberbandTimer = nullptr;
267 IntPoint ScrollingTreeScrollingNodeMac::scrollPosition() const
269 if (shouldUpdateScrollLayerPositionOnMainThread())
270 return m_probableMainThreadScrollPosition;
272 CGPoint scrollLayerPosition = m_scrollLayer.get().position;
273 return IntPoint(-scrollLayerPosition.x + scrollOrigin().x(), -scrollLayerPosition.y + scrollOrigin().y());
276 void ScrollingTreeScrollingNodeMac::setScrollPosition(const IntPoint& scrollPosition)
278 IntPoint newScrollPosition = scrollPosition;
279 newScrollPosition = newScrollPosition.shrunkTo(maximumScrollPosition());
280 newScrollPosition = newScrollPosition.expandedTo(minimumScrollPosition());
282 setScrollPositionWithoutContentEdgeConstraints(newScrollPosition);
284 if (scrollingTree()->scrollingPerformanceLoggingEnabled())
285 logExposedUnfilledArea();
288 void ScrollingTreeScrollingNodeMac::setScrollPositionWithoutContentEdgeConstraints(const IntPoint& scrollPosition)
290 updateMainFramePinState(scrollPosition);
292 if (shouldUpdateScrollLayerPositionOnMainThread()) {
293 m_probableMainThreadScrollPosition = scrollPosition;
294 scrollingTree()->updateMainFrameScrollPosition(scrollPosition, SetScrollingLayerPosition);
298 setScrollLayerPosition(scrollPosition);
299 scrollingTree()->updateMainFrameScrollPosition(scrollPosition);
302 void ScrollingTreeScrollingNodeMac::setScrollLayerPosition(const IntPoint& position)
304 ASSERT(!shouldUpdateScrollLayerPositionOnMainThread());
305 m_scrollLayer.get().position = CGPointMake(-position.x() + scrollOrigin().x(), -position.y() + scrollOrigin().y());
307 IntSize scrollOffsetForFixedChildren = WebCore::scrollOffsetForFixedPosition(viewportRect(), contentsSize(), position, scrollOrigin(), frameScaleFactor(), false);
308 if (m_counterScrollingLayer)
309 m_counterScrollingLayer.get().position = FloatPoint(scrollOffsetForFixedChildren);
314 IntRect viewportRect = this->viewportRect();
315 viewportRect.setLocation(IntPoint(scrollOffsetForFixedChildren));
317 size_t size = m_children->size();
318 for (size_t i = 0; i < size; ++i)
319 m_children->at(i)->parentScrollPositionDidChange(viewportRect, FloatSize());
322 IntPoint ScrollingTreeScrollingNodeMac::minimumScrollPosition() const
324 return IntPoint(0, 0);
327 IntPoint ScrollingTreeScrollingNodeMac::maximumScrollPosition() const
329 IntPoint position(contentsSize().width() - viewportRect().width(),
330 contentsSize().height() - viewportRect().height());
332 position.clampNegativeToZero();
337 void ScrollingTreeScrollingNodeMac::scrollBy(const IntSize& offset)
339 setScrollPosition(scrollPosition() + offset);
342 void ScrollingTreeScrollingNodeMac::scrollByWithoutContentEdgeConstraints(const IntSize& offset)
344 setScrollPositionWithoutContentEdgeConstraints(scrollPosition() + offset);
347 void ScrollingTreeScrollingNodeMac::updateMainFramePinState(const IntPoint& scrollPosition)
349 bool pinnedToTheLeft = scrollPosition.x() <= minimumScrollPosition().x();
350 bool pinnedToTheRight = scrollPosition.x() >= maximumScrollPosition().x();
352 scrollingTree()->setMainFramePinState(pinnedToTheLeft, pinnedToTheRight);
355 void ScrollingTreeScrollingNodeMac::logExposedUnfilledArea()
357 Region paintedVisibleTiles;
359 Deque<CALayer*> layerQueue;
360 layerQueue.append(m_scrollLayer.get());
361 WebTileLayerList tiles;
363 while (!layerQueue.isEmpty() && tiles.isEmpty()) {
364 CALayer* layer = layerQueue.takeFirst();
365 NSArray* sublayers = [[layer sublayers] copy];
367 // If this layer is the parent of a tile, it is the parent of all of the tiles and nothing else.
368 if ([[sublayers objectAtIndex:0] isKindOfClass:[WebTileLayer class]]) {
369 for (CALayer* sublayer in sublayers) {
370 ASSERT([sublayer isKindOfClass:[WebTileLayer class]]);
371 tiles.append(static_cast<WebTileLayer*>(sublayer));
374 for (CALayer* sublayer in sublayers)
375 layerQueue.append(sublayer);
381 IntPoint scrollPosition = this->scrollPosition();
382 unsigned unfilledArea = TileCache::blankPixelCountForTiles(tiles, viewportRect(), IntPoint(-scrollPosition.x(), -scrollPosition.y()));
384 if (unfilledArea || m_lastScrollHadUnfilledPixels)
385 WTFLogAlways("SCROLLING: Exposed tileless area. Time: %f Unfilled Pixels: %u\n", WTF::monotonicallyIncreasingTime(), unfilledArea);
387 m_lastScrollHadUnfilledPixels = unfilledArea;
390 static void logThreadedScrollingMode(unsigned mainThreadScrollingReasons)
392 if (mainThreadScrollingReasons) {
393 StringBuilder reasonsDescription;
395 if (mainThreadScrollingReasons & ScrollingCoordinator::ForcedOnMainThread)
396 reasonsDescription.append("forced,");
397 if (mainThreadScrollingReasons & ScrollingCoordinator::HasSlowRepaintObjects)
398 reasonsDescription.append("slow-repaint objects,");
399 if (mainThreadScrollingReasons & ScrollingCoordinator::HasViewportConstrainedObjectsWithoutSupportingFixedLayers)
400 reasonsDescription.append("viewport-constrained objects,");
401 if (mainThreadScrollingReasons & ScrollingCoordinator::HasNonLayerViewportConstrainedObjects)
402 reasonsDescription.append("non-layer viewport-constrained objects,");
403 if (mainThreadScrollingReasons & ScrollingCoordinator::IsImageDocument)
404 reasonsDescription.append("image document,");
406 // Strip the trailing comma.
407 String reasonsDescriptionTrimmed = reasonsDescription.toString().left(reasonsDescription.length() - 1);
409 WTFLogAlways("SCROLLING: Switching to main-thread scrolling mode. Time: %f Reason(s): %s\n", WTF::monotonicallyIncreasingTime(), reasonsDescriptionTrimmed.ascii().data());
411 WTFLogAlways("SCROLLING: Switching to threaded scrolling mode. Time: %f\n", WTF::monotonicallyIncreasingTime());
414 void logWheelEventHandlerCountChanged(unsigned count)
416 WTFLogAlways("SCROLLING: Wheel event handler count changed. Time: %f Count: %u\n", WTF::monotonicallyIncreasingTime(), count);
419 } // namespace WebCore
421 #endif // ENABLE(THREADED_SCROLLING)