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