1 /*
2 * Copyright (c) 2010, Google Inc. All rights reserved.
3 * Copyright (C) 2008, 2011 Apple Inc. All Rights Reserved.
4 *
5 * Redistribution and use in source and binary forms, with or without
6 * modification, are permitted provided that the following conditions are
7 * met:
8 *
9 * * Redistributions of source code must retain the above copyright
10 * notice, this list of conditions and the following disclaimer.
11 * * Redistributions in binary form must reproduce the above
12 * copyright notice, this list of conditions and the following disclaimer
13 * in the documentation and/or other materials provided with the
14 * distribution.
15 * * Neither the name of Google Inc. nor the names of its
16 * contributors may be used to endorse or promote products derived from
17 * this software without specific prior written permission.
18 *
19 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
20 * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
21 * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
22 * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
23 * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
24 * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
25 * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
26 * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
27 * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
28 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
29 * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
30 */
31
32 #include "config.h"
33 #include "platform/scroll/ScrollableArea.h"
34
35 #include "platform/graphics/GraphicsLayer.h"
36 #include "platform/geometry/FloatPoint.h"
37 #include "platform/scroll/ScrollbarTheme.h"
38 #include "wtf/PassOwnPtr.h"
39
40 #include "platform/TraceEvent.h"
41
42 static const int kPixelsPerLineStep = 40;
43 static const float kMinFractionToStepWhenPaging = 0.875f;
44
45 namespace WebCore {
46
47 struct SameSizeAsScrollableArea {
48 virtual ~SameSizeAsScrollableArea();
49 unsigned damageBits : 2;
50 IntRect scrollbarDamage[2];
51 void* pointer;
52 unsigned bitfields : 16;
53 IntPoint origin;
54 };
55
56 COMPILE_ASSERT(sizeof(ScrollableArea) == sizeof(SameSizeAsScrollableArea), ScrollableArea_should_stay_small);
57
pixelsPerLineStep()58 int ScrollableArea::pixelsPerLineStep()
59 {
60 return kPixelsPerLineStep;
61 }
62
minFractionToStepWhenPaging()63 float ScrollableArea::minFractionToStepWhenPaging()
64 {
65 return kMinFractionToStepWhenPaging;
66 }
67
maxOverlapBetweenPages()68 int ScrollableArea::maxOverlapBetweenPages()
69 {
70 static int maxOverlapBetweenPages = ScrollbarTheme::theme()->maxOverlapBetweenPages();
71 return maxOverlapBetweenPages;
72 }
73
ScrollableArea()74 ScrollableArea::ScrollableArea()
75 : m_hasHorizontalBarDamage(false)
76 , m_hasVerticalBarDamage(false)
77 , m_constrainsScrollingToContentEdge(true)
78 , m_inLiveResize(false)
79 , m_verticalScrollElasticity(ScrollElasticityNone)
80 , m_horizontalScrollElasticity(ScrollElasticityNone)
81 , m_scrollbarOverlayStyle(ScrollbarOverlayStyleDefault)
82 , m_scrollOriginChanged(false)
83 {
84 }
85
~ScrollableArea()86 ScrollableArea::~ScrollableArea()
87 {
88 }
89
scrollAnimator() const90 ScrollAnimator* ScrollableArea::scrollAnimator() const
91 {
92 if (!m_scrollAnimator)
93 m_scrollAnimator = ScrollAnimator::create(const_cast<ScrollableArea*>(this));
94
95 return m_scrollAnimator.get();
96 }
97
setScrollOrigin(const IntPoint & origin)98 void ScrollableArea::setScrollOrigin(const IntPoint& origin)
99 {
100 if (m_scrollOrigin != origin) {
101 m_scrollOrigin = origin;
102 m_scrollOriginChanged = true;
103 }
104 }
105
layerForContainer() const106 GraphicsLayer* ScrollableArea::layerForContainer() const
107 {
108 return layerForScrolling() ? layerForScrolling()->parent() : 0;
109 }
110
scroll(ScrollDirection direction,ScrollGranularity granularity,float delta)111 bool ScrollableArea::scroll(ScrollDirection direction, ScrollGranularity granularity, float delta)
112 {
113 ScrollbarOrientation orientation;
114
115 if (direction == ScrollUp || direction == ScrollDown)
116 orientation = VerticalScrollbar;
117 else
118 orientation = HorizontalScrollbar;
119
120 if (!userInputScrollable(orientation))
121 return false;
122
123 float step = 0;
124 switch (granularity) {
125 case ScrollByLine:
126 step = lineStep(orientation);
127 break;
128 case ScrollByPage:
129 step = pageStep(orientation);
130 break;
131 case ScrollByDocument:
132 step = documentStep(orientation);
133 break;
134 case ScrollByPixel:
135 case ScrollByPrecisePixel:
136 step = pixelStep(orientation);
137 break;
138 }
139
140 if (direction == ScrollUp || direction == ScrollLeft)
141 delta = -delta;
142
143 return scrollAnimator()->scroll(orientation, granularity, step, delta);
144 }
145
scrollToOffsetWithoutAnimation(const FloatPoint & offset)146 void ScrollableArea::scrollToOffsetWithoutAnimation(const FloatPoint& offset)
147 {
148 scrollAnimator()->scrollToOffsetWithoutAnimation(offset);
149 }
150
scrollToOffsetWithoutAnimation(ScrollbarOrientation orientation,float offset)151 void ScrollableArea::scrollToOffsetWithoutAnimation(ScrollbarOrientation orientation, float offset)
152 {
153 if (orientation == HorizontalScrollbar)
154 scrollToOffsetWithoutAnimation(FloatPoint(offset, scrollAnimator()->currentPosition().y()));
155 else
156 scrollToOffsetWithoutAnimation(FloatPoint(scrollAnimator()->currentPosition().x(), offset));
157 }
158
notifyScrollPositionChanged(const IntPoint & position)159 void ScrollableArea::notifyScrollPositionChanged(const IntPoint& position)
160 {
161 scrollPositionChanged(position);
162 scrollAnimator()->setCurrentPosition(position);
163 }
164
scrollPositionChanged(const IntPoint & position)165 void ScrollableArea::scrollPositionChanged(const IntPoint& position)
166 {
167 TRACE_EVENT0("webkit", "ScrollableArea::scrollPositionChanged");
168
169 IntPoint oldPosition = scrollPosition();
170 // Tell the derived class to scroll its contents.
171 setScrollOffset(position);
172
173 Scrollbar* verticalScrollbar = this->verticalScrollbar();
174
175 // Tell the scrollbars to update their thumb postions.
176 if (Scrollbar* horizontalScrollbar = this->horizontalScrollbar()) {
177 horizontalScrollbar->offsetDidChange();
178 if (horizontalScrollbar->isOverlayScrollbar() && !hasLayerForHorizontalScrollbar()) {
179 if (!verticalScrollbar)
180 horizontalScrollbar->invalidate();
181 else {
182 // If there is both a horizontalScrollbar and a verticalScrollbar,
183 // then we must also invalidate the corner between them.
184 IntRect boundsAndCorner = horizontalScrollbar->boundsRect();
185 boundsAndCorner.setWidth(boundsAndCorner.width() + verticalScrollbar->width());
186 horizontalScrollbar->invalidateRect(boundsAndCorner);
187 }
188 }
189 }
190 if (verticalScrollbar) {
191 verticalScrollbar->offsetDidChange();
192 if (verticalScrollbar->isOverlayScrollbar() && !hasLayerForVerticalScrollbar())
193 verticalScrollbar->invalidate();
194 }
195
196 if (scrollPosition() != oldPosition)
197 scrollAnimator()->notifyContentAreaScrolled(scrollPosition() - oldPosition);
198 }
199
scrollBehaviorFromString(const String & behaviorString,ScrollBehavior & behavior)200 bool ScrollableArea::scrollBehaviorFromString(const String& behaviorString, ScrollBehavior& behavior)
201 {
202 if (behaviorString == "auto")
203 behavior = ScrollBehaviorAuto;
204 else if (behaviorString == "instant")
205 behavior = ScrollBehaviorInstant;
206 else if (behaviorString == "smooth")
207 behavior = ScrollBehaviorSmooth;
208 else
209 return false;
210
211 return true;
212 }
213
handleWheelEvent(const PlatformWheelEvent & wheelEvent)214 bool ScrollableArea::handleWheelEvent(const PlatformWheelEvent& wheelEvent)
215 {
216 // ctrl+wheel events are used to trigger zooming, not scrolling.
217 if (wheelEvent.modifiers() & PlatformEvent::CtrlKey)
218 return false;
219
220 return scrollAnimator()->handleWheelEvent(wheelEvent);
221 }
222
223 // NOTE: Only called from Internals for testing.
setScrollOffsetFromInternals(const IntPoint & offset)224 void ScrollableArea::setScrollOffsetFromInternals(const IntPoint& offset)
225 {
226 setScrollOffsetFromAnimation(offset);
227 }
228
setScrollOffsetFromAnimation(const IntPoint & offset)229 void ScrollableArea::setScrollOffsetFromAnimation(const IntPoint& offset)
230 {
231 scrollPositionChanged(offset);
232 }
233
willStartLiveResize()234 void ScrollableArea::willStartLiveResize()
235 {
236 if (m_inLiveResize)
237 return;
238 m_inLiveResize = true;
239 if (ScrollAnimator* scrollAnimator = existingScrollAnimator())
240 scrollAnimator->willStartLiveResize();
241 }
242
willEndLiveResize()243 void ScrollableArea::willEndLiveResize()
244 {
245 if (!m_inLiveResize)
246 return;
247 m_inLiveResize = false;
248 if (ScrollAnimator* scrollAnimator = existingScrollAnimator())
249 scrollAnimator->willEndLiveResize();
250 }
251
contentAreaWillPaint() const252 void ScrollableArea::contentAreaWillPaint() const
253 {
254 if (ScrollAnimator* scrollAnimator = existingScrollAnimator())
255 scrollAnimator->contentAreaWillPaint();
256 }
257
mouseEnteredContentArea() const258 void ScrollableArea::mouseEnteredContentArea() const
259 {
260 if (ScrollAnimator* scrollAnimator = existingScrollAnimator())
261 scrollAnimator->mouseEnteredContentArea();
262 }
263
mouseExitedContentArea() const264 void ScrollableArea::mouseExitedContentArea() const
265 {
266 if (ScrollAnimator* scrollAnimator = existingScrollAnimator())
267 scrollAnimator->mouseEnteredContentArea();
268 }
269
mouseMovedInContentArea() const270 void ScrollableArea::mouseMovedInContentArea() const
271 {
272 if (ScrollAnimator* scrollAnimator = existingScrollAnimator())
273 scrollAnimator->mouseMovedInContentArea();
274 }
275
mouseEnteredScrollbar(Scrollbar * scrollbar) const276 void ScrollableArea::mouseEnteredScrollbar(Scrollbar* scrollbar) const
277 {
278 scrollAnimator()->mouseEnteredScrollbar(scrollbar);
279 }
280
mouseExitedScrollbar(Scrollbar * scrollbar) const281 void ScrollableArea::mouseExitedScrollbar(Scrollbar* scrollbar) const
282 {
283 scrollAnimator()->mouseExitedScrollbar(scrollbar);
284 }
285
contentAreaDidShow() const286 void ScrollableArea::contentAreaDidShow() const
287 {
288 if (ScrollAnimator* scrollAnimator = existingScrollAnimator())
289 scrollAnimator->contentAreaDidShow();
290 }
291
contentAreaDidHide() const292 void ScrollableArea::contentAreaDidHide() const
293 {
294 if (ScrollAnimator* scrollAnimator = existingScrollAnimator())
295 scrollAnimator->contentAreaDidHide();
296 }
297
finishCurrentScrollAnimations() const298 void ScrollableArea::finishCurrentScrollAnimations() const
299 {
300 if (ScrollAnimator* scrollAnimator = existingScrollAnimator())
301 scrollAnimator->finishCurrentScrollAnimations();
302 }
303
didAddScrollbar(Scrollbar * scrollbar,ScrollbarOrientation orientation)304 void ScrollableArea::didAddScrollbar(Scrollbar* scrollbar, ScrollbarOrientation orientation)
305 {
306 if (orientation == VerticalScrollbar)
307 scrollAnimator()->didAddVerticalScrollbar(scrollbar);
308 else
309 scrollAnimator()->didAddHorizontalScrollbar(scrollbar);
310
311 // <rdar://problem/9797253> AppKit resets the scrollbar's style when you attach a scrollbar
312 setScrollbarOverlayStyle(scrollbarOverlayStyle());
313 }
314
willRemoveScrollbar(Scrollbar * scrollbar,ScrollbarOrientation orientation)315 void ScrollableArea::willRemoveScrollbar(Scrollbar* scrollbar, ScrollbarOrientation orientation)
316 {
317 if (orientation == VerticalScrollbar)
318 scrollAnimator()->willRemoveVerticalScrollbar(scrollbar);
319 else
320 scrollAnimator()->willRemoveHorizontalScrollbar(scrollbar);
321 }
322
contentsResized()323 void ScrollableArea::contentsResized()
324 {
325 if (ScrollAnimator* scrollAnimator = existingScrollAnimator())
326 scrollAnimator->contentsResized();
327 }
328
hasOverlayScrollbars() const329 bool ScrollableArea::hasOverlayScrollbars() const
330 {
331 Scrollbar* vScrollbar = verticalScrollbar();
332 if (vScrollbar && vScrollbar->isOverlayScrollbar())
333 return true;
334 Scrollbar* hScrollbar = horizontalScrollbar();
335 return hScrollbar && hScrollbar->isOverlayScrollbar();
336 }
337
setScrollbarOverlayStyle(ScrollbarOverlayStyle overlayStyle)338 void ScrollableArea::setScrollbarOverlayStyle(ScrollbarOverlayStyle overlayStyle)
339 {
340 m_scrollbarOverlayStyle = overlayStyle;
341
342 if (Scrollbar* scrollbar = horizontalScrollbar()) {
343 ScrollbarTheme::theme()->updateScrollbarOverlayStyle(scrollbar);
344 scrollbar->invalidate();
345 }
346
347 if (Scrollbar* scrollbar = verticalScrollbar()) {
348 ScrollbarTheme::theme()->updateScrollbarOverlayStyle(scrollbar);
349 scrollbar->invalidate();
350 }
351 }
352
invalidateScrollbar(Scrollbar * scrollbar,const IntRect & rect)353 void ScrollableArea::invalidateScrollbar(Scrollbar* scrollbar, const IntRect& rect)
354 {
355 if (scrollbar == horizontalScrollbar()) {
356 if (GraphicsLayer* graphicsLayer = layerForHorizontalScrollbar()) {
357 graphicsLayer->setNeedsDisplay();
358 graphicsLayer->setContentsNeedsDisplay();
359 return;
360 }
361 } else if (scrollbar == verticalScrollbar()) {
362 if (GraphicsLayer* graphicsLayer = layerForVerticalScrollbar()) {
363 graphicsLayer->setNeedsDisplay();
364 graphicsLayer->setContentsNeedsDisplay();
365 return;
366 }
367 }
368 invalidateScrollbarRect(scrollbar, rect);
369 }
370
invalidateScrollCorner(const IntRect & rect)371 void ScrollableArea::invalidateScrollCorner(const IntRect& rect)
372 {
373 if (GraphicsLayer* graphicsLayer = layerForScrollCorner()) {
374 graphicsLayer->setNeedsDisplay();
375 return;
376 }
377 invalidateScrollCornerRect(rect);
378 }
379
hasLayerForHorizontalScrollbar() const380 bool ScrollableArea::hasLayerForHorizontalScrollbar() const
381 {
382 return layerForHorizontalScrollbar();
383 }
384
hasLayerForVerticalScrollbar() const385 bool ScrollableArea::hasLayerForVerticalScrollbar() const
386 {
387 return layerForVerticalScrollbar();
388 }
389
hasLayerForScrollCorner() const390 bool ScrollableArea::hasLayerForScrollCorner() const
391 {
392 return layerForScrollCorner();
393 }
394
serviceScrollAnimations()395 void ScrollableArea::serviceScrollAnimations()
396 {
397 if (ScrollAnimator* scrollAnimator = existingScrollAnimator())
398 scrollAnimator->serviceScrollAnimations();
399 }
400
visibleContentRect(IncludeScrollbarsInRect scrollbarInclusion) const401 IntRect ScrollableArea::visibleContentRect(IncludeScrollbarsInRect scrollbarInclusion) const
402 {
403 int verticalScrollbarWidth = 0;
404 int horizontalScrollbarHeight = 0;
405
406 if (scrollbarInclusion == IncludeScrollbars) {
407 if (Scrollbar* verticalBar = verticalScrollbar())
408 verticalScrollbarWidth = !verticalBar->isOverlayScrollbar() ? verticalBar->width() : 0;
409 if (Scrollbar* horizontalBar = horizontalScrollbar())
410 horizontalScrollbarHeight = !horizontalBar->isOverlayScrollbar() ? horizontalBar->height() : 0;
411 }
412
413 return IntRect(scrollPosition().x(),
414 scrollPosition().y(),
415 std::max(0, visibleWidth() + verticalScrollbarWidth),
416 std::max(0, visibleHeight() + horizontalScrollbarHeight));
417 }
418
clampScrollPosition(const IntPoint & scrollPosition) const419 IntPoint ScrollableArea::clampScrollPosition(const IntPoint& scrollPosition) const
420 {
421 return scrollPosition.shrunkTo(maximumScrollPosition()).expandedTo(minimumScrollPosition());
422 }
423
lineStep(ScrollbarOrientation) const424 int ScrollableArea::lineStep(ScrollbarOrientation) const
425 {
426 return pixelsPerLineStep();
427 }
428
pageStep(ScrollbarOrientation orientation) const429 int ScrollableArea::pageStep(ScrollbarOrientation orientation) const
430 {
431 int length = (orientation == HorizontalScrollbar) ? visibleWidth() : visibleHeight();
432 int minPageStep = static_cast<float>(length) * minFractionToStepWhenPaging();
433 int pageStep = std::max(minPageStep, length - maxOverlapBetweenPages());
434
435 return std::max(pageStep, 1);
436 }
437
documentStep(ScrollbarOrientation orientation) const438 int ScrollableArea::documentStep(ScrollbarOrientation orientation) const
439 {
440 return scrollSize(orientation);
441 }
442
pixelStep(ScrollbarOrientation) const443 float ScrollableArea::pixelStep(ScrollbarOrientation) const
444 {
445 return 1;
446 }
447
448 } // namespace WebCore
449