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 "FrameView.h"
32 #include "PlatformWheelEvent.h"
33 #include "ScrollingCoordinator.h"
34 #include "ScrollingTree.h"
35 #include "ScrollingStateTree.h"
37 #include "TileCache.h"
38 #include "WebTileLayer.h"
40 #include <wtf/CurrentTime.h>
41 #include <wtf/Deque.h>
42 #include <wtf/text/StringBuilder.h>
43 #include <wtf/text/CString.h>
47 static void logThreadedScrollingMode(unsigned mainThreadScrollingReasons);
48 static void logWheelEventHandlerCountChanged(unsigned);
51 PassOwnPtr<ScrollingTreeScrollingNode> ScrollingTreeScrollingNode::create(ScrollingTree* scrollingTree, ScrollingNodeID nodeID)
53 return adoptPtr(new ScrollingTreeScrollingNodeMac(scrollingTree, nodeID));
56 ScrollingTreeScrollingNodeMac::ScrollingTreeScrollingNodeMac(ScrollingTree* scrollingTree, ScrollingNodeID nodeID)
57 : ScrollingTreeScrollingNode(scrollingTree, nodeID)
58 , m_scrollElasticityController(this)
59 , m_lastScrollHadUnfilledPixels(false)
63 ScrollingTreeScrollingNodeMac::~ScrollingTreeScrollingNodeMac()
65 if (m_snapRubberbandTimer)
66 CFRunLoopTimerInvalidate(m_snapRubberbandTimer.get());
69 void ScrollingTreeScrollingNodeMac::updateBeforeChildren(ScrollingStateNode* stateNode)
71 ScrollingTreeScrollingNode::updateBeforeChildren(stateNode);
72 ScrollingStateScrollingNode* scrollingStateNode = toScrollingStateScrollingNode(stateNode);
74 if (scrollingStateNode->hasChangedProperty(ScrollingStateNode::ScrollLayer))
75 m_scrollLayer = scrollingStateNode->platformScrollLayer();
77 if (scrollingStateNode->hasChangedProperty(ScrollingStateScrollingNode::CounterScrollingLayer))
78 m_counterScrollingLayer = scrollingStateNode->counterScrollingPlatformLayer();
80 if (scrollingStateNode->hasChangedProperty(ScrollingStateScrollingNode::ShouldUpdateScrollLayerPositionOnMainThread)) {
81 unsigned mainThreadScrollingReasons = this->shouldUpdateScrollLayerPositionOnMainThread();
83 if (mainThreadScrollingReasons) {
84 // We're transitioning to the slow "update scroll layer position on the main thread" mode.
85 // Initialize the probable main thread scroll position with the current scroll layer position.
86 if (scrollingStateNode->hasChangedProperty(ScrollingStateScrollingNode::RequestedScrollPosition))
87 m_probableMainThreadScrollPosition = scrollingStateNode->requestedScrollPosition();
89 CGPoint scrollLayerPosition = m_scrollLayer.get().position;
90 m_probableMainThreadScrollPosition = IntPoint(-scrollLayerPosition.x, -scrollLayerPosition.y);
94 if (scrollingTree()->scrollingPerformanceLoggingEnabled())
95 logThreadedScrollingMode(mainThreadScrollingReasons);
98 if (scrollingStateNode->hasChangedProperty(ScrollingStateScrollingNode::WheelEventHandlerCount)) {
99 if (scrollingTree()->scrollingPerformanceLoggingEnabled())
100 logWheelEventHandlerCountChanged(scrollingStateNode->wheelEventHandlerCount());
104 void ScrollingTreeScrollingNodeMac::updateAfterChildren(ScrollingStateNode* stateNode)
106 ScrollingTreeScrollingNode::updateAfterChildren(stateNode);
108 ScrollingStateScrollingNode* scrollingStateNode = toScrollingStateScrollingNode(stateNode);
110 // Update the scroll position after child nodes have been updated, because they need to have updated their constraints before any scrolling happens.
111 if (scrollingStateNode->hasChangedProperty(ScrollingStateScrollingNode::RequestedScrollPosition))
112 setScrollPosition(scrollingStateNode->requestedScrollPosition());
114 if (scrollingStateNode->hasChangedProperty(ScrollingStateNode::ScrollLayer) || scrollingStateNode->hasChangedProperty(ScrollingStateScrollingNode::ContentsSize) || scrollingStateNode->hasChangedProperty(ScrollingStateScrollingNode::ViewportRect))
115 updateMainFramePinState(scrollPosition());
118 void ScrollingTreeScrollingNodeMac::handleWheelEvent(const PlatformWheelEvent& wheelEvent)
120 if (!canHaveScrollbars())
123 m_scrollElasticityController.handleWheelEvent(wheelEvent);
124 scrollingTree()->handleWheelEventPhase(wheelEvent.phase());
127 bool ScrollingTreeScrollingNodeMac::allowsHorizontalStretching()
129 switch (horizontalScrollElasticity()) {
130 case ScrollElasticityAutomatic:
131 return hasEnabledHorizontalScrollbar() || !hasEnabledVerticalScrollbar();
132 case ScrollElasticityNone:
134 case ScrollElasticityAllowed:
138 ASSERT_NOT_REACHED();
142 bool ScrollingTreeScrollingNodeMac::allowsVerticalStretching()
144 switch (verticalScrollElasticity()) {
145 case ScrollElasticityAutomatic:
146 return hasEnabledVerticalScrollbar() || !hasEnabledHorizontalScrollbar();
147 case ScrollElasticityNone:
149 case ScrollElasticityAllowed:
153 ASSERT_NOT_REACHED();
157 IntSize ScrollingTreeScrollingNodeMac::stretchAmount()
161 if (scrollPosition().y() < minimumScrollPosition().y())
162 stretch.setHeight(scrollPosition().y() - minimumScrollPosition().y());
163 else if (scrollPosition().y() > maximumScrollPosition().y())
164 stretch.setHeight(scrollPosition().y() - maximumScrollPosition().y());
166 if (scrollPosition().x() < minimumScrollPosition().x())
167 stretch.setWidth(scrollPosition().x() - minimumScrollPosition().x());
168 else if (scrollPosition().x() > maximumScrollPosition().x())
169 stretch.setWidth(scrollPosition().x() - maximumScrollPosition().x());
171 if (scrollingTree()->rootNode() == this) {
172 if (stretch.isZero())
173 scrollingTree()->setMainFrameIsRubberBanding(false);
175 scrollingTree()->setMainFrameIsRubberBanding(true);
181 bool ScrollingTreeScrollingNodeMac::pinnedInDirection(const FloatSize& delta)
183 FloatSize limitDelta;
185 if (fabsf(delta.height()) >= fabsf(delta.width())) {
186 if (delta.height() < 0) {
187 // We are trying to scroll up. Make sure we are not pinned to the top
188 limitDelta.setHeight(scrollPosition().y() - minimumScrollPosition().y());
190 // We are trying to scroll down. Make sure we are not pinned to the bottom
191 limitDelta.setHeight(maximumScrollPosition().y() - scrollPosition().y());
193 } else if (delta.width()) {
194 if (delta.width() < 0) {
195 // We are trying to scroll left. Make sure we are not pinned to the left
196 limitDelta.setHeight(scrollPosition().x() - minimumScrollPosition().x());
198 // We are trying to scroll right. Make sure we are not pinned to the right
199 limitDelta.setHeight(maximumScrollPosition().x() - scrollPosition().x());
203 if ((delta.width() || delta.height()) && (limitDelta.width() < 1 && limitDelta.height() < 1))
209 bool ScrollingTreeScrollingNodeMac::canScrollHorizontally()
211 return hasEnabledHorizontalScrollbar();
214 bool ScrollingTreeScrollingNodeMac::canScrollVertically()
216 return hasEnabledVerticalScrollbar();
219 bool ScrollingTreeScrollingNodeMac::shouldRubberBandInDirection(ScrollDirection direction)
221 if (direction == ScrollLeft)
222 return !scrollingTree()->canGoBack();
223 if (direction == ScrollRight)
224 return !scrollingTree()->canGoForward();
226 ASSERT_NOT_REACHED();
230 IntPoint ScrollingTreeScrollingNodeMac::absoluteScrollPosition()
232 return scrollPosition();
235 void ScrollingTreeScrollingNodeMac::immediateScrollBy(const FloatSize& offset)
237 scrollBy(roundedIntSize(offset));
240 void ScrollingTreeScrollingNodeMac::immediateScrollByWithoutContentEdgeConstraints(const FloatSize& offset)
242 scrollByWithoutContentEdgeConstraints(roundedIntSize(offset));
245 void ScrollingTreeScrollingNodeMac::startSnapRubberbandTimer()
247 ASSERT(!m_snapRubberbandTimer);
249 CFTimeInterval timerInterval = 1.0 / 60.0;
251 m_snapRubberbandTimer = adoptCF(CFRunLoopTimerCreateWithHandler(kCFAllocatorDefault, CFAbsoluteTimeGetCurrent() + timerInterval, timerInterval, 0, 0, ^(CFRunLoopTimerRef) {
252 m_scrollElasticityController.snapRubberBandTimerFired();
254 CFRunLoopAddTimer(CFRunLoopGetCurrent(), m_snapRubberbandTimer.get(), kCFRunLoopDefaultMode);
257 void ScrollingTreeScrollingNodeMac::stopSnapRubberbandTimer()
259 if (!m_snapRubberbandTimer)
262 scrollingTree()->setMainFrameIsRubberBanding(false);
264 CFRunLoopTimerInvalidate(m_snapRubberbandTimer.get());
265 m_snapRubberbandTimer = nullptr;
268 IntPoint ScrollingTreeScrollingNodeMac::scrollPosition() const
270 if (shouldUpdateScrollLayerPositionOnMainThread())
271 return m_probableMainThreadScrollPosition;
273 CGPoint scrollLayerPosition = m_scrollLayer.get().position;
274 return IntPoint(-scrollLayerPosition.x + scrollOrigin().x(), -scrollLayerPosition.y + scrollOrigin().y());
277 void ScrollingTreeScrollingNodeMac::setScrollPosition(const IntPoint& scrollPosition)
279 IntPoint newScrollPosition = scrollPosition;
280 newScrollPosition = newScrollPosition.shrunkTo(maximumScrollPosition());
281 newScrollPosition = newScrollPosition.expandedTo(minimumScrollPosition());
283 setScrollPositionWithoutContentEdgeConstraints(newScrollPosition);
285 if (scrollingTree()->scrollingPerformanceLoggingEnabled())
286 logExposedUnfilledArea();
289 void ScrollingTreeScrollingNodeMac::setScrollPositionWithoutContentEdgeConstraints(const IntPoint& scrollPosition)
291 updateMainFramePinState(scrollPosition);
293 if (shouldUpdateScrollLayerPositionOnMainThread()) {
294 m_probableMainThreadScrollPosition = scrollPosition;
295 scrollingTree()->updateMainFrameScrollPosition(scrollPosition, SetScrollingLayerPosition);
299 setScrollLayerPosition(scrollPosition);
300 scrollingTree()->updateMainFrameScrollPosition(scrollPosition);
303 void ScrollingTreeScrollingNodeMac::setScrollLayerPosition(const IntPoint& position)
305 ASSERT(!shouldUpdateScrollLayerPositionOnMainThread());
306 m_scrollLayer.get().position = CGPointMake(-position.x() + scrollOrigin().x(), -position.y() + scrollOrigin().y());
308 IntSize scrollOffsetForFixedChildren = FrameView::scrollOffsetForFixedPosition(viewportRect(), contentsSize(), position, scrollOrigin(), frameScaleFactor(), false);
309 if (m_counterScrollingLayer)
310 m_counterScrollingLayer.get().position = FloatPoint(scrollOffsetForFixedChildren);
315 IntRect viewportRect = this->viewportRect();
316 viewportRect.setLocation(IntPoint(scrollOffsetForFixedChildren));
318 size_t size = m_children->size();
319 for (size_t i = 0; i < size; ++i)
320 m_children->at(i)->parentScrollPositionDidChange(viewportRect, FloatSize());
323 IntPoint ScrollingTreeScrollingNodeMac::minimumScrollPosition() const
325 return IntPoint(0, 0);
328 IntPoint ScrollingTreeScrollingNodeMac::maximumScrollPosition() const
330 IntPoint position(contentsSize().width() - viewportRect().width(),
331 contentsSize().height() - viewportRect().height());
333 position.clampNegativeToZero();
338 void ScrollingTreeScrollingNodeMac::scrollBy(const IntSize& offset)
340 setScrollPosition(scrollPosition() + offset);
343 void ScrollingTreeScrollingNodeMac::scrollByWithoutContentEdgeConstraints(const IntSize& offset)
345 setScrollPositionWithoutContentEdgeConstraints(scrollPosition() + offset);
348 void ScrollingTreeScrollingNodeMac::updateMainFramePinState(const IntPoint& scrollPosition)
350 bool pinnedToTheLeft = scrollPosition.x() <= minimumScrollPosition().x();
351 bool pinnedToTheRight = scrollPosition.x() >= maximumScrollPosition().x();
353 scrollingTree()->setMainFramePinState(pinnedToTheLeft, pinnedToTheRight);
356 void ScrollingTreeScrollingNodeMac::logExposedUnfilledArea()
358 Region paintedVisibleTiles;
360 Deque<CALayer*> layerQueue;
361 layerQueue.append(m_scrollLayer.get());
362 WebTileLayerList tiles;
364 while (!layerQueue.isEmpty() && tiles.isEmpty()) {
365 CALayer* layer = layerQueue.takeFirst();
366 NSArray* sublayers = [[layer sublayers] copy];
368 // If this layer is the parent of a tile, it is the parent of all of the tiles and nothing else.
369 if ([[sublayers objectAtIndex:0] isKindOfClass:[WebTileLayer class]]) {
370 for (CALayer* sublayer in sublayers) {
371 ASSERT([sublayer isKindOfClass:[WebTileLayer class]]);
372 tiles.append(static_cast<WebTileLayer*>(sublayer));
375 for (CALayer* sublayer in sublayers)
376 layerQueue.append(sublayer);
382 IntPoint scrollPosition = this->scrollPosition();
383 unsigned unfilledArea = TileCache::blankPixelCountForTiles(tiles, viewportRect(), IntPoint(-scrollPosition.x(), -scrollPosition.y()));
385 if (unfilledArea || m_lastScrollHadUnfilledPixels)
386 WTFLogAlways("SCROLLING: Exposed tileless area. Time: %f Unfilled Pixels: %u\n", WTF::monotonicallyIncreasingTime(), unfilledArea);
388 m_lastScrollHadUnfilledPixels = unfilledArea;
391 static void logThreadedScrollingMode(unsigned mainThreadScrollingReasons)
393 if (mainThreadScrollingReasons) {
394 StringBuilder reasonsDescription;
396 if (mainThreadScrollingReasons & ScrollingCoordinator::ForcedOnMainThread)
397 reasonsDescription.append("forced,");
398 if (mainThreadScrollingReasons & ScrollingCoordinator::HasSlowRepaintObjects)
399 reasonsDescription.append("slow-repaint objects,");
400 if (mainThreadScrollingReasons & ScrollingCoordinator::HasViewportConstrainedObjectsWithoutSupportingFixedLayers)
401 reasonsDescription.append("viewport-constrained objects,");
402 if (mainThreadScrollingReasons & ScrollingCoordinator::HasNonLayerViewportConstrainedObjects)
403 reasonsDescription.append("non-layer viewport-constrained objects,");
404 if (mainThreadScrollingReasons & ScrollingCoordinator::IsImageDocument)
405 reasonsDescription.append("image document,");
407 // Strip the trailing comma.
408 String reasonsDescriptionTrimmed = reasonsDescription.toString().left(reasonsDescription.length() - 1);
410 WTFLogAlways("SCROLLING: Switching to main-thread scrolling mode. Time: %f Reason(s): %s\n", WTF::monotonicallyIncreasingTime(), reasonsDescriptionTrimmed.ascii().data());
412 WTFLogAlways("SCROLLING: Switching to threaded scrolling mode. Time: %f\n", WTF::monotonicallyIncreasingTime());
415 void logWheelEventHandlerCountChanged(unsigned count)
417 WTFLogAlways("SCROLLING: Wheel event handler count changed. Time: %f Count: %u\n", WTF::monotonicallyIncreasingTime(), count);
420 } // namespace WebCore
422 #endif // ENABLE(THREADED_SCROLLING)