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