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);
48
PassOwnPtr<ScrollingTreeScrollingNode> ScrollingTreeScrollingNode::create(ScrollingTree* scrollingTree)
50
return adoptPtr(new ScrollingTreeScrollingNodeMac(scrollingTree));
53
ScrollingTreeScrollingNodeMac::ScrollingTreeScrollingNodeMac(ScrollingTree* scrollingTree)
54
: ScrollingTreeScrollingNode(scrollingTree)
55
, m_scrollElasticityController(this)
59
ScrollingTreeScrollingNodeMac::~ScrollingTreeScrollingNodeMac()
61
if (m_snapRubberbandTimer)
62
CFRunLoopTimerInvalidate(m_snapRubberbandTimer.get());
65
void ScrollingTreeScrollingNodeMac::update(ScrollingStateNode* stateNode)
67
ScrollingTreeScrollingNode::update(stateNode);
68
ScrollingStateScrollingNode* state = toScrollingStateScrollingNode(stateNode);
70
if (state->scrollLayerDidChange())
71
m_scrollLayer = state->platformScrollLayer();
73
if (state->changedProperties() & ScrollingStateScrollingNode::RequestedScrollPosition)
74
setScrollPosition(state->requestedScrollPosition());
76
if (state->scrollLayerDidChange() || state->changedProperties() & (ScrollingStateScrollingNode::ContentsSize | ScrollingStateScrollingNode::ViewportRect))
77
updateMainFramePinState(scrollPosition());
79
if ((state->changedProperties() & 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 (state->changedProperties() & ScrollingStateScrollingNode::RequestedScrollPosition)
86
m_probableMainThreadScrollPosition = state->requestedScrollPosition();
88
CGPoint scrollLayerPosition = m_scrollLayer.get().position;
89
m_probableMainThreadScrollPosition = IntPoint(-scrollLayerPosition.x, -scrollLayerPosition.y);
93
if (scrollingTree()->scrollingPerformanceLoggingEnabled())
94
logThreadedScrollingMode(mainThreadScrollingReasons);
98
void ScrollingTreeScrollingNodeMac::handleWheelEvent(const PlatformWheelEvent& wheelEvent)
100
if (!canHaveScrollbars())
103
m_scrollElasticityController.handleWheelEvent(wheelEvent);
104
scrollingTree()->handleWheelEventPhase(wheelEvent.phase());
107
bool ScrollingTreeScrollingNodeMac::allowsHorizontalStretching()
109
switch (horizontalScrollElasticity()) {
110
case ScrollElasticityAutomatic:
111
return hasEnabledHorizontalScrollbar() || !hasEnabledVerticalScrollbar();
112
case ScrollElasticityNone:
114
case ScrollElasticityAllowed:
118
ASSERT_NOT_REACHED();
122
bool ScrollingTreeScrollingNodeMac::allowsVerticalStretching()
124
switch (verticalScrollElasticity()) {
125
case ScrollElasticityAutomatic:
126
return hasEnabledVerticalScrollbar() || !hasEnabledHorizontalScrollbar();
127
case ScrollElasticityNone:
129
case ScrollElasticityAllowed:
133
ASSERT_NOT_REACHED();
137
IntSize ScrollingTreeScrollingNodeMac::stretchAmount()
141
if (scrollPosition().y() < minimumScrollPosition().y())
142
stretch.setHeight(scrollPosition().y() - minimumScrollPosition().y());
143
else if (scrollPosition().y() > maximumScrollPosition().y())
144
stretch.setHeight(scrollPosition().y() - maximumScrollPosition().y());
146
if (scrollPosition().x() < minimumScrollPosition().x())
147
stretch.setWidth(scrollPosition().x() - minimumScrollPosition().x());
148
else if (scrollPosition().x() > maximumScrollPosition().x())
149
stretch.setWidth(scrollPosition().x() - maximumScrollPosition().x());
154
bool ScrollingTreeScrollingNodeMac::pinnedInDirection(const FloatSize& delta)
156
FloatSize limitDelta;
158
if (fabsf(delta.height()) >= fabsf(delta.width())) {
159
if (delta.height() < 0) {
160
// We are trying to scroll up. Make sure we are not pinned to the top
161
limitDelta.setHeight(scrollPosition().y() - minimumScrollPosition().y());
163
// We are trying to scroll down. Make sure we are not pinned to the bottom
164
limitDelta.setHeight(maximumScrollPosition().y() - scrollPosition().y());
166
} else if (delta.width()) {
167
if (delta.width() < 0) {
168
// We are trying to scroll left. Make sure we are not pinned to the left
169
limitDelta.setHeight(scrollPosition().x() - minimumScrollPosition().x());
171
// We are trying to scroll right. Make sure we are not pinned to the right
172
limitDelta.setHeight(maximumScrollPosition().x() - scrollPosition().x());
176
if ((delta.width() || delta.height()) && (limitDelta.width() < 1 && limitDelta.height() < 1))
182
bool ScrollingTreeScrollingNodeMac::canScrollHorizontally()
184
return hasEnabledHorizontalScrollbar();
187
bool ScrollingTreeScrollingNodeMac::canScrollVertically()
189
return hasEnabledVerticalScrollbar();
192
bool ScrollingTreeScrollingNodeMac::shouldRubberBandInDirection(ScrollDirection direction)
194
if (direction == ScrollLeft)
195
return !scrollingTree()->canGoBack();
196
if (direction == ScrollRight)
197
return !scrollingTree()->canGoForward();
199
ASSERT_NOT_REACHED();
203
IntPoint ScrollingTreeScrollingNodeMac::absoluteScrollPosition()
205
return scrollPosition();
208
void ScrollingTreeScrollingNodeMac::immediateScrollBy(const FloatSize& offset)
210
scrollBy(roundedIntSize(offset));
213
void ScrollingTreeScrollingNodeMac::immediateScrollByWithoutContentEdgeConstraints(const FloatSize& offset)
215
scrollByWithoutContentEdgeConstraints(roundedIntSize(offset));
218
void ScrollingTreeScrollingNodeMac::startSnapRubberbandTimer()
220
ASSERT(!m_snapRubberbandTimer);
222
CFTimeInterval timerInterval = 1.0 / 60.0;
224
m_snapRubberbandTimer = adoptCF(CFRunLoopTimerCreateWithHandler(kCFAllocatorDefault, CFAbsoluteTimeGetCurrent() + timerInterval, timerInterval, 0, 0, ^(CFRunLoopTimerRef) {
225
m_scrollElasticityController.snapRubberBandTimerFired();
227
CFRunLoopAddTimer(CFRunLoopGetCurrent(), m_snapRubberbandTimer.get(), kCFRunLoopDefaultMode);
230
void ScrollingTreeScrollingNodeMac::stopSnapRubberbandTimer()
232
if (!m_snapRubberbandTimer)
235
CFRunLoopTimerInvalidate(m_snapRubberbandTimer.get());
236
m_snapRubberbandTimer = nullptr;
239
IntPoint ScrollingTreeScrollingNodeMac::scrollPosition() const
241
if (shouldUpdateScrollLayerPositionOnMainThread())
242
return m_probableMainThreadScrollPosition;
244
CGPoint scrollLayerPosition = m_scrollLayer.get().position;
245
return IntPoint(-scrollLayerPosition.x + scrollOrigin().x(), -scrollLayerPosition.y + scrollOrigin().y());
248
void ScrollingTreeScrollingNodeMac::setScrollPosition(const IntPoint& scrollPosition)
250
IntPoint newScrollPosition = scrollPosition;
251
newScrollPosition = newScrollPosition.shrunkTo(maximumScrollPosition());
252
newScrollPosition = newScrollPosition.expandedTo(minimumScrollPosition());
254
setScrollPositionWithoutContentEdgeConstraints(newScrollPosition);
256
if (scrollingTree()->scrollingPerformanceLoggingEnabled())
257
logExposedUnfilledArea();
260
void ScrollingTreeScrollingNodeMac::setScrollPositionWithoutContentEdgeConstraints(const IntPoint& scrollPosition)
262
updateMainFramePinState(scrollPosition);
264
if (shouldUpdateScrollLayerPositionOnMainThread()) {
265
m_probableMainThreadScrollPosition = scrollPosition;
266
scrollingTree()->updateMainFrameScrollPosition(scrollPosition, SetScrollingLayerPosition);
270
setScrollLayerPosition(scrollPosition);
271
scrollingTree()->updateMainFrameScrollPosition(scrollPosition);
274
void ScrollingTreeScrollingNodeMac::setScrollLayerPosition(const IntPoint& position)
276
ASSERT(!shouldUpdateScrollLayerPositionOnMainThread());
277
m_scrollLayer.get().position = CGPointMake(-position.x() + scrollOrigin().x(), -position.y() + scrollOrigin().y());
282
IntSize scrollOffsetForFixedChildren = WebCore::scrollOffsetForFixedPosition(viewportRect(), contentsSize(), position,
283
scrollOrigin(), 1, false);
284
IntRect viewportRect = this->viewportRect();
285
viewportRect.setLocation(toPoint(scrollOffsetForFixedChildren));
287
size_t size = m_children->size();
288
for (size_t i = 0; i < size; ++i)
289
m_children->at(i)->parentScrollPositionDidChange(viewportRect);
292
IntPoint ScrollingTreeScrollingNodeMac::minimumScrollPosition() const
294
return IntPoint(0, 0);
297
IntPoint ScrollingTreeScrollingNodeMac::maximumScrollPosition() const
299
IntPoint position(contentsSize().width() - viewportRect().width(),
300
contentsSize().height() - viewportRect().height());
302
position.clampNegativeToZero();
307
void ScrollingTreeScrollingNodeMac::scrollBy(const IntSize& offset)
309
setScrollPosition(scrollPosition() + offset);
312
void ScrollingTreeScrollingNodeMac::scrollByWithoutContentEdgeConstraints(const IntSize& offset)
314
setScrollPositionWithoutContentEdgeConstraints(scrollPosition() + offset);
317
void ScrollingTreeScrollingNodeMac::updateMainFramePinState(const IntPoint& scrollPosition)
319
bool pinnedToTheLeft = scrollPosition.x() <= minimumScrollPosition().x();
320
bool pinnedToTheRight = scrollPosition.x() >= maximumScrollPosition().x();
322
scrollingTree()->setMainFramePinState(pinnedToTheLeft, pinnedToTheRight);
325
void ScrollingTreeScrollingNodeMac::logExposedUnfilledArea()
327
Region paintedVisibleTiles;
329
Deque<CALayer*> layerQueue;
330
layerQueue.append(m_scrollLayer.get());
331
WebTileLayerList tiles;
333
while(!layerQueue.isEmpty() && tiles.isEmpty()) {
334
CALayer* layer = layerQueue.takeFirst();
335
NSArray* sublayers = [[layer sublayers] copy];
337
// If this layer is the parent of a tile, it is the parent of all of the tiles and nothing else.
338
if ([[sublayers objectAtIndex:0] isKindOfClass:[WebTileLayer class]]) {
339
for (CALayer* sublayer in sublayers) {
340
ASSERT([sublayer isKindOfClass:[WebTileLayer class]]);
341
tiles.append(static_cast<WebTileLayer*>(sublayer));
344
for (CALayer* sublayer in sublayers)
345
layerQueue.append(sublayer);
351
IntPoint scrollPosition = this->scrollPosition();
352
unsigned unfilledArea = TileCache::blankPixelCountForTiles(tiles, viewportRect(), IntPoint(-scrollPosition.x(), -scrollPosition.y()));
355
WTFLogAlways("SCROLLING: Exposed tileless area. Time: %f Unfilled Pixels: %u\n", WTF::monotonicallyIncreasingTime(), unfilledArea);
358
static void logThreadedScrollingMode(unsigned mainThreadScrollingReasons)
360
if (mainThreadScrollingReasons) {
361
StringBuilder reasonsDescription;
363
if (mainThreadScrollingReasons & ScrollingCoordinator::ForcedOnMainThread)
364
reasonsDescription.append("forced,");
365
if (mainThreadScrollingReasons & ScrollingCoordinator::HasSlowRepaintObjects)
366
reasonsDescription.append("slow-repaint objects,");
367
if (mainThreadScrollingReasons & ScrollingCoordinator::HasViewportConstrainedObjectsWithoutSupportingFixedLayers)
368
reasonsDescription.append("viewport-constrained objects,");
369
if (mainThreadScrollingReasons & ScrollingCoordinator::HasNonLayerFixedObjects)
370
reasonsDescription.append("non-layer viewport-constrained objects,");
371
if (mainThreadScrollingReasons & ScrollingCoordinator::IsImageDocument)
372
reasonsDescription.append("image document,");
374
// Strip the trailing comma.
375
String reasonsDescriptionTrimmed = reasonsDescription.toString().left(reasonsDescription.length() - 1);
377
WTFLogAlways("SCROLLING: Switching to main-thread scrolling mode. Time: %f Reason(s): %s\n", WTF::monotonicallyIncreasingTime(), reasonsDescriptionTrimmed.ascii().data());
379
WTFLogAlways("SCROLLING: Switching to threaded scrolling mode. Time: %f\n", WTF::monotonicallyIncreasingTime());
382
} // namespace WebCore
384
#endif // ENABLE(THREADED_SCROLLING)