7c722cca33fe9fb7587dc86f315e3bd218ad1b18
[WebKit-https.git] / Source / WebCore / page / scrolling / mac / ScrollingTreeScrollingNodeMac.mm
1 /*
2  * Copyright (C) 2012 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 #include "config.h"
27 #include "ScrollingTreeScrollingNodeMac.h"
28
29 #if ENABLE(THREADED_SCROLLING)
30
31 #include "PlatformWheelEvent.h"
32 #include "ScrollingCoordinator.h"
33 #include "ScrollingTree.h"
34 #include "ScrollingStateTree.h"
35 #include "Settings.h"
36 #include "TileCache.h"
37 #include "WebTileLayer.h"
38
39 #include <wtf/CurrentTime.h>
40 #include <wtf/Deque.h>
41 #include <wtf/text/StringBuilder.h>
42 #include <wtf/text/CString.h>
43
44 namespace WebCore {
45
46 static void logThreadedScrollingMode(unsigned mainThreadScrollingReasons);
47 static void logWheelEventHandlerCountChanged(unsigned);
48
49
50 PassOwnPtr<ScrollingTreeScrollingNode> ScrollingTreeScrollingNode::create(ScrollingTree* scrollingTree, ScrollingNodeID nodeID)
51 {
52     return adoptPtr(new ScrollingTreeScrollingNodeMac(scrollingTree, nodeID));
53 }
54
55 ScrollingTreeScrollingNodeMac::ScrollingTreeScrollingNodeMac(ScrollingTree* scrollingTree, ScrollingNodeID nodeID)
56     : ScrollingTreeScrollingNode(scrollingTree, nodeID)
57     , m_scrollElasticityController(this)
58     , m_lastScrollHadUnfilledPixels(false)
59 {
60 }
61
62 ScrollingTreeScrollingNodeMac::~ScrollingTreeScrollingNodeMac()
63 {
64     if (m_snapRubberbandTimer)
65         CFRunLoopTimerInvalidate(m_snapRubberbandTimer.get());
66 }
67
68 void ScrollingTreeScrollingNodeMac::updateBeforeChildren(ScrollingStateNode* stateNode)
69 {
70     ScrollingTreeScrollingNode::updateBeforeChildren(stateNode);
71     ScrollingStateScrollingNode* scrollingStateNode = toScrollingStateScrollingNode(stateNode);
72
73     if (scrollingStateNode->hasChangedProperty(ScrollingStateNode::ScrollLayer))
74         m_scrollLayer = scrollingStateNode->platformScrollLayer();
75
76     if (scrollingStateNode->hasChangedProperty(ScrollingStateScrollingNode::CounterScrollingLayer))
77         m_counterScrollingLayer = scrollingStateNode->counterScrollingPlatformLayer();
78
79     if (scrollingStateNode->hasChangedProperty(ScrollingStateScrollingNode::ShouldUpdateScrollLayerPositionOnMainThread)) {
80         unsigned mainThreadScrollingReasons = this->shouldUpdateScrollLayerPositionOnMainThread();
81
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();
87             else {
88                 CGPoint scrollLayerPosition = m_scrollLayer.get().position;
89                 m_probableMainThreadScrollPosition = IntPoint(-scrollLayerPosition.x, -scrollLayerPosition.y);
90             }
91         }
92
93         if (scrollingTree()->scrollingPerformanceLoggingEnabled())
94             logThreadedScrollingMode(mainThreadScrollingReasons);
95     }
96
97     if (scrollingStateNode->hasChangedProperty(ScrollingStateScrollingNode::WheelEventHandlerCount)) {
98         if (scrollingTree()->scrollingPerformanceLoggingEnabled())
99             logWheelEventHandlerCountChanged(scrollingStateNode->wheelEventHandlerCount());
100     }
101 }
102
103 void ScrollingTreeScrollingNodeMac::updateAfterChildren(ScrollingStateNode* stateNode)
104 {
105     ScrollingTreeScrollingNode::updateAfterChildren(stateNode);
106
107     ScrollingStateScrollingNode* scrollingStateNode = toScrollingStateScrollingNode(stateNode);
108
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());
112
113     if (scrollingStateNode->hasChangedProperty(ScrollingStateNode::ScrollLayer) || scrollingStateNode->hasChangedProperty(ScrollingStateScrollingNode::ContentsSize) || scrollingStateNode->hasChangedProperty(ScrollingStateScrollingNode::ViewportRect))
114         updateMainFramePinState(scrollPosition());
115 }
116
117 void ScrollingTreeScrollingNodeMac::handleWheelEvent(const PlatformWheelEvent& wheelEvent)
118 {
119     if (!canHaveScrollbars())
120         return;
121
122     m_scrollElasticityController.handleWheelEvent(wheelEvent);
123     scrollingTree()->handleWheelEventPhase(wheelEvent.phase());
124 }
125
126 bool ScrollingTreeScrollingNodeMac::allowsHorizontalStretching()
127 {
128     switch (horizontalScrollElasticity()) {
129     case ScrollElasticityAutomatic:
130         return hasEnabledHorizontalScrollbar() || !hasEnabledVerticalScrollbar();
131     case ScrollElasticityNone:
132         return false;
133     case ScrollElasticityAllowed:
134         return true;
135     }
136
137     ASSERT_NOT_REACHED();
138     return false;
139 }
140
141 bool ScrollingTreeScrollingNodeMac::allowsVerticalStretching()
142 {
143     switch (verticalScrollElasticity()) {
144     case ScrollElasticityAutomatic:
145         return hasEnabledVerticalScrollbar() || !hasEnabledHorizontalScrollbar();
146     case ScrollElasticityNone:
147         return false;
148     case ScrollElasticityAllowed:
149         return true;
150     }
151
152     ASSERT_NOT_REACHED();
153     return false;
154 }
155
156 IntSize ScrollingTreeScrollingNodeMac::stretchAmount()
157 {
158     IntSize stretch;
159
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());
164
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());
169
170     if (scrollingTree()->rootNode() == this) {
171         if (stretch.isZero())
172             scrollingTree()->setMainFrameIsRubberBanding(false);
173         else
174             scrollingTree()->setMainFrameIsRubberBanding(true);
175     }
176
177     return stretch;
178 }
179
180 bool ScrollingTreeScrollingNodeMac::pinnedInDirection(const FloatSize& delta)
181 {
182     FloatSize limitDelta;
183
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());
188         } else {
189             // We are trying to scroll down.  Make sure we are not pinned to the bottom
190             limitDelta.setHeight(maximumScrollPosition().y() - scrollPosition().y());
191         }
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());
196         } else {
197             // We are trying to scroll right.  Make sure we are not pinned to the right
198             limitDelta.setHeight(maximumScrollPosition().x() - scrollPosition().x());
199         }
200     }
201
202     if ((delta.width() || delta.height()) && (limitDelta.width() < 1 && limitDelta.height() < 1))        
203         return true;
204
205     return false;
206 }
207
208 bool ScrollingTreeScrollingNodeMac::canScrollHorizontally()
209 {
210     return hasEnabledHorizontalScrollbar();
211 }
212
213 bool ScrollingTreeScrollingNodeMac::canScrollVertically()
214 {
215     return hasEnabledVerticalScrollbar();
216 }
217
218 bool ScrollingTreeScrollingNodeMac::shouldRubberBandInDirection(ScrollDirection direction)
219 {
220     if (direction == ScrollLeft)
221         return !scrollingTree()->canGoBack();
222     if (direction == ScrollRight)
223         return !scrollingTree()->canGoForward();
224
225     ASSERT_NOT_REACHED();
226     return false;
227 }
228
229 IntPoint ScrollingTreeScrollingNodeMac::absoluteScrollPosition()
230 {
231     return scrollPosition();
232 }
233
234 void ScrollingTreeScrollingNodeMac::immediateScrollBy(const FloatSize& offset)
235 {
236     scrollBy(roundedIntSize(offset));
237 }
238
239 void ScrollingTreeScrollingNodeMac::immediateScrollByWithoutContentEdgeConstraints(const FloatSize& offset)
240 {
241     scrollByWithoutContentEdgeConstraints(roundedIntSize(offset));
242 }
243
244 void ScrollingTreeScrollingNodeMac::startSnapRubberbandTimer()
245 {
246     ASSERT(!m_snapRubberbandTimer);
247
248     CFTimeInterval timerInterval = 1.0 / 60.0;
249
250     m_snapRubberbandTimer = adoptCF(CFRunLoopTimerCreateWithHandler(kCFAllocatorDefault, CFAbsoluteTimeGetCurrent() + timerInterval, timerInterval, 0, 0, ^(CFRunLoopTimerRef) {
251         m_scrollElasticityController.snapRubberBandTimerFired();
252     }));
253     CFRunLoopAddTimer(CFRunLoopGetCurrent(), m_snapRubberbandTimer.get(), kCFRunLoopDefaultMode);
254 }
255
256 void ScrollingTreeScrollingNodeMac::stopSnapRubberbandTimer()
257 {
258     if (!m_snapRubberbandTimer)
259         return;
260
261     scrollingTree()->setMainFrameIsRubberBanding(false);
262
263     CFRunLoopTimerInvalidate(m_snapRubberbandTimer.get());
264     m_snapRubberbandTimer = nullptr;
265 }
266
267 IntPoint ScrollingTreeScrollingNodeMac::scrollPosition() const
268 {
269     if (shouldUpdateScrollLayerPositionOnMainThread())
270         return m_probableMainThreadScrollPosition;
271
272     CGPoint scrollLayerPosition = m_scrollLayer.get().position;
273     return IntPoint(-scrollLayerPosition.x + scrollOrigin().x(), -scrollLayerPosition.y + scrollOrigin().y());
274 }
275
276 void ScrollingTreeScrollingNodeMac::setScrollPosition(const IntPoint& scrollPosition)
277 {
278     IntPoint newScrollPosition = scrollPosition;
279     newScrollPosition = newScrollPosition.shrunkTo(maximumScrollPosition());
280     newScrollPosition = newScrollPosition.expandedTo(minimumScrollPosition());
281
282     setScrollPositionWithoutContentEdgeConstraints(newScrollPosition);
283
284     if (scrollingTree()->scrollingPerformanceLoggingEnabled())
285         logExposedUnfilledArea();
286 }
287
288 void ScrollingTreeScrollingNodeMac::setScrollPositionWithoutContentEdgeConstraints(const IntPoint& scrollPosition)
289 {
290     updateMainFramePinState(scrollPosition);
291
292     if (shouldUpdateScrollLayerPositionOnMainThread()) {
293         m_probableMainThreadScrollPosition = scrollPosition;
294         scrollingTree()->updateMainFrameScrollPosition(scrollPosition, SetScrollingLayerPosition);
295         return;
296     }
297
298     setScrollLayerPosition(scrollPosition);
299     scrollingTree()->updateMainFrameScrollPosition(scrollPosition);
300 }
301
302 void ScrollingTreeScrollingNodeMac::setScrollLayerPosition(const IntPoint& position)
303 {
304     ASSERT(!shouldUpdateScrollLayerPositionOnMainThread());
305     m_scrollLayer.get().position = CGPointMake(-position.x() + scrollOrigin().x(), -position.y() + scrollOrigin().y());
306
307     IntSize scrollOffsetForFixedChildren = WebCore::scrollOffsetForFixedPosition(viewportRect(), contentsSize(), position, scrollOrigin(), frameScaleFactor(), false);
308     if (m_counterScrollingLayer)
309         m_counterScrollingLayer.get().position = FloatPoint(scrollOffsetForFixedChildren);
310
311     if (!m_children)
312         return;
313
314     IntRect viewportRect = this->viewportRect();
315     viewportRect.setLocation(IntPoint(scrollOffsetForFixedChildren));
316
317     size_t size = m_children->size();
318     for (size_t i = 0; i < size; ++i)
319         m_children->at(i)->parentScrollPositionDidChange(viewportRect, FloatSize());
320 }
321
322 IntPoint ScrollingTreeScrollingNodeMac::minimumScrollPosition() const
323 {
324     return IntPoint(0, 0);
325 }
326
327 IntPoint ScrollingTreeScrollingNodeMac::maximumScrollPosition() const
328 {
329     IntPoint position(contentsSize().width() - viewportRect().width(),
330                       contentsSize().height() - viewportRect().height());
331
332     position.clampNegativeToZero();
333
334     return position;
335 }
336
337 void ScrollingTreeScrollingNodeMac::scrollBy(const IntSize& offset)
338 {
339     setScrollPosition(scrollPosition() + offset);
340 }
341
342 void ScrollingTreeScrollingNodeMac::scrollByWithoutContentEdgeConstraints(const IntSize& offset)
343 {
344     setScrollPositionWithoutContentEdgeConstraints(scrollPosition() + offset);
345 }
346
347 void ScrollingTreeScrollingNodeMac::updateMainFramePinState(const IntPoint& scrollPosition)
348 {
349     bool pinnedToTheLeft = scrollPosition.x() <= minimumScrollPosition().x();
350     bool pinnedToTheRight = scrollPosition.x() >= maximumScrollPosition().x();
351
352     scrollingTree()->setMainFramePinState(pinnedToTheLeft, pinnedToTheRight);
353 }
354
355 void ScrollingTreeScrollingNodeMac::logExposedUnfilledArea()
356 {
357     Region paintedVisibleTiles;
358
359     Deque<CALayer*> layerQueue;
360     layerQueue.append(m_scrollLayer.get());
361     WebTileLayerList tiles;
362
363     while (!layerQueue.isEmpty() && tiles.isEmpty()) {
364         CALayer* layer = layerQueue.takeFirst();
365         NSArray* sublayers = [[layer sublayers] copy];
366
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));
372             }
373         } else {
374             for (CALayer* sublayer in sublayers)
375                 layerQueue.append(sublayer);
376         }
377
378         [sublayers release];
379     }
380
381     IntPoint scrollPosition = this->scrollPosition();
382     unsigned unfilledArea = TileCache::blankPixelCountForTiles(tiles, viewportRect(), IntPoint(-scrollPosition.x(), -scrollPosition.y()));
383
384     if (unfilledArea || m_lastScrollHadUnfilledPixels)
385         WTFLogAlways("SCROLLING: Exposed tileless area. Time: %f Unfilled Pixels: %u\n", WTF::monotonicallyIncreasingTime(), unfilledArea);
386
387     m_lastScrollHadUnfilledPixels = unfilledArea;
388 }
389
390 static void logThreadedScrollingMode(unsigned mainThreadScrollingReasons)
391 {
392     if (mainThreadScrollingReasons) {
393         StringBuilder reasonsDescription;
394
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,");
405
406         // Strip the trailing comma.
407         String reasonsDescriptionTrimmed = reasonsDescription.toString().left(reasonsDescription.length() - 1);
408
409         WTFLogAlways("SCROLLING: Switching to main-thread scrolling mode. Time: %f Reason(s): %s\n", WTF::monotonicallyIncreasingTime(), reasonsDescriptionTrimmed.ascii().data());
410     } else
411         WTFLogAlways("SCROLLING: Switching to threaded scrolling mode. Time: %f\n", WTF::monotonicallyIncreasingTime());
412 }
413
414 void logWheelEventHandlerCountChanged(unsigned count)
415 {
416     WTFLogAlways("SCROLLING: Wheel event handler count changed. Time: %f Count: %u\n", WTF::monotonicallyIncreasingTime(), count);
417 }
418
419 } // namespace WebCore
420
421 #endif // ENABLE(THREADED_SCROLLING)