b03525f5aa2e6770be0986a69fb8330a5c9e4cd9
[WebKit-https.git] / Source / WebCore / platform / ScrollableArea.cpp
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 "ScrollableArea.h"
34
35 #include "GraphicsContext.h"
36 #include "GraphicsLayer.h"
37 #include "FloatPoint.h"
38 #include "PlatformMemoryInstrumentation.h"
39 #include "PlatformWheelEvent.h"
40 #include "ScrollAnimator.h"
41 #include "ScrollbarTheme.h"
42 #include <wtf/PassOwnPtr.h>
43
44 #if PLATFORM(CHROMIUM)
45 #include "TraceEvent.h"
46 #endif
47
48 namespace WebCore {
49
50 struct SameSizeAsScrollableArea {
51     virtual ~SameSizeAsScrollableArea();
52     void* pointer;
53     unsigned bitfields : 16;
54     IntPoint origin;
55 };
56
57 COMPILE_ASSERT(sizeof(ScrollableArea) == sizeof(SameSizeAsScrollableArea), ScrollableArea_should_stay_small);
58
59 ScrollableArea::ScrollableArea()
60     : m_constrainsScrollingToContentEdge(true)
61     , m_inLiveResize(false)
62     , m_verticalScrollElasticity(ScrollElasticityNone)
63     , m_horizontalScrollElasticity(ScrollElasticityNone)
64     , m_scrollbarOverlayStyle(ScrollbarOverlayStyleDefault)
65     , m_scrollOriginChanged(false)
66 {
67 }
68
69 ScrollableArea::~ScrollableArea()
70 {
71 }
72
73 ScrollAnimator* ScrollableArea::scrollAnimator() const
74 {
75     if (!m_scrollAnimator)
76         m_scrollAnimator = ScrollAnimator::create(const_cast<ScrollableArea*>(this));
77
78     return m_scrollAnimator.get();
79 }
80
81 void ScrollableArea::setScrollOrigin(const IntPoint& origin)
82 {
83     if (m_scrollOrigin != origin) {
84         m_scrollOrigin = origin;
85         m_scrollOriginChanged = true;
86     }
87 }
88
89 bool ScrollableArea::scroll(ScrollDirection direction, ScrollGranularity granularity, float multiplier)
90 {
91     ScrollbarOrientation orientation;
92     Scrollbar* scrollbar;
93     if (direction == ScrollUp || direction == ScrollDown) {
94         orientation = VerticalScrollbar;
95         scrollbar = verticalScrollbar();
96     } else {
97         orientation = HorizontalScrollbar;
98         scrollbar = horizontalScrollbar();
99     }
100
101     if (!scrollbar)
102         return false;
103
104     float step = 0;
105     switch (granularity) {
106     case ScrollByLine:
107         step = scrollbar->lineStep();
108         break;
109     case ScrollByPage:
110         step = scrollbar->pageStep();
111         break;
112     case ScrollByDocument:
113         step = scrollbar->totalSize();
114         break;
115     case ScrollByPixel:
116     case ScrollByPrecisePixel:
117         step = scrollbar->pixelStep();
118         break;
119     }
120
121     if (direction == ScrollUp || direction == ScrollLeft)
122         multiplier = -multiplier;
123
124     return scrollAnimator()->scroll(orientation, granularity, step, multiplier);
125 }
126
127 void ScrollableArea::scrollToOffsetWithoutAnimation(const FloatPoint& offset)
128 {
129     scrollAnimator()->scrollToOffsetWithoutAnimation(offset);
130 }
131
132 void ScrollableArea::scrollToOffsetWithoutAnimation(ScrollbarOrientation orientation, float offset)
133 {
134     if (orientation == HorizontalScrollbar)
135         scrollToOffsetWithoutAnimation(FloatPoint(offset, scrollAnimator()->currentPosition().y()));
136     else
137         scrollToOffsetWithoutAnimation(FloatPoint(scrollAnimator()->currentPosition().x(), offset));
138 }
139
140 void ScrollableArea::notifyScrollPositionChanged(const IntPoint& position)
141 {
142     scrollPositionChanged(position);
143     scrollAnimator()->setCurrentPosition(position);
144 }
145
146 void ScrollableArea::scrollPositionChanged(const IntPoint& position)
147 {
148 #if PLATFORM(CHROMIUM)
149     TRACE_EVENT0("webkit", "ScrollableArea::scrollPositionChanged");
150 #endif
151
152     IntPoint oldPosition = scrollPosition();
153     // Tell the derived class to scroll its contents.
154     setScrollOffset(position);
155
156     Scrollbar* verticalScrollbar = this->verticalScrollbar();
157
158     // Tell the scrollbars to update their thumb postions.
159     if (Scrollbar* horizontalScrollbar = this->horizontalScrollbar()) {
160         horizontalScrollbar->offsetDidChange();
161         if (horizontalScrollbar->isOverlayScrollbar() && !hasLayerForHorizontalScrollbar()) {
162             if (!verticalScrollbar)
163                 horizontalScrollbar->invalidate();
164             else {
165                 // If there is both a horizontalScrollbar and a verticalScrollbar,
166                 // then we must also invalidate the corner between them.
167                 IntRect boundsAndCorner = horizontalScrollbar->boundsRect();
168                 boundsAndCorner.setWidth(boundsAndCorner.width() + verticalScrollbar->width());
169                 horizontalScrollbar->invalidateRect(boundsAndCorner);
170             }
171         }
172     }
173     if (verticalScrollbar) {
174         verticalScrollbar->offsetDidChange();
175         if (verticalScrollbar->isOverlayScrollbar() && !hasLayerForVerticalScrollbar())
176             verticalScrollbar->invalidate();
177     }
178
179     if (scrollPosition() != oldPosition)
180         scrollAnimator()->notifyContentAreaScrolled(scrollPosition() - oldPosition);
181 }
182
183 bool ScrollableArea::handleWheelEvent(const PlatformWheelEvent& wheelEvent)
184 {
185     return scrollAnimator()->handleWheelEvent(wheelEvent);
186 }
187
188 // NOTE: Only called from Internals for testing.
189 void ScrollableArea::setScrollOffsetFromInternals(const IntPoint& offset)
190 {
191     setScrollOffsetFromAnimation(offset);
192 }
193
194 void ScrollableArea::setScrollOffsetFromAnimation(const IntPoint& offset)
195 {
196     if (requestScrollPositionUpdate(offset))
197         return;
198
199     scrollPositionChanged(offset);
200 }
201
202 void ScrollableArea::willStartLiveResize()
203 {
204     if (m_inLiveResize)
205         return;
206     m_inLiveResize = true;
207     if (ScrollAnimator* scrollAnimator = existingScrollAnimator())
208         scrollAnimator->willStartLiveResize();
209 }
210
211 void ScrollableArea::willEndLiveResize()
212 {
213     if (!m_inLiveResize)
214         return;
215     m_inLiveResize = false;
216     if (ScrollAnimator* scrollAnimator = existingScrollAnimator())
217         scrollAnimator->willEndLiveResize();
218 }    
219
220 void ScrollableArea::contentAreaWillPaint() const
221 {
222     if (ScrollAnimator* scrollAnimator = existingScrollAnimator())
223         scrollAnimator->contentAreaWillPaint();
224 }
225
226 void ScrollableArea::mouseEnteredContentArea() const
227 {
228     if (ScrollAnimator* scrollAnimator = existingScrollAnimator())
229         scrollAnimator->mouseEnteredContentArea();
230 }
231
232 void ScrollableArea::mouseExitedContentArea() const
233 {
234     if (ScrollAnimator* scrollAnimator = existingScrollAnimator())
235         scrollAnimator->mouseEnteredContentArea();
236 }
237
238 void ScrollableArea::mouseMovedInContentArea() const
239 {
240     if (ScrollAnimator* scrollAnimator = existingScrollAnimator())
241         scrollAnimator->mouseMovedInContentArea();
242 }
243
244 void ScrollableArea::mouseEnteredScrollbar(Scrollbar* scrollbar) const
245 {
246     scrollAnimator()->mouseEnteredScrollbar(scrollbar);
247 }
248
249 void ScrollableArea::mouseExitedScrollbar(Scrollbar* scrollbar) const
250 {
251     scrollAnimator()->mouseExitedScrollbar(scrollbar);
252 }
253
254 void ScrollableArea::contentAreaDidShow() const
255 {
256     if (ScrollAnimator* scrollAnimator = existingScrollAnimator())
257         scrollAnimator->contentAreaDidShow();
258 }
259
260 void ScrollableArea::contentAreaDidHide() const
261 {
262     if (ScrollAnimator* scrollAnimator = existingScrollAnimator())
263         scrollAnimator->contentAreaDidHide();
264 }
265
266 void ScrollableArea::finishCurrentScrollAnimations() const
267 {
268     if (ScrollAnimator* scrollAnimator = existingScrollAnimator())
269         scrollAnimator->finishCurrentScrollAnimations();
270 }
271
272 void ScrollableArea::didAddVerticalScrollbar(Scrollbar* scrollbar)
273 {
274     scrollAnimator()->didAddVerticalScrollbar(scrollbar);
275
276     // <rdar://problem/9797253> AppKit resets the scrollbar's style when you attach a scrollbar
277     setScrollbarOverlayStyle(scrollbarOverlayStyle());
278 }
279
280 void ScrollableArea::willRemoveVerticalScrollbar(Scrollbar* scrollbar)
281 {
282     scrollAnimator()->willRemoveVerticalScrollbar(scrollbar);
283 }
284
285 void ScrollableArea::didAddHorizontalScrollbar(Scrollbar* scrollbar)
286 {
287     scrollAnimator()->didAddHorizontalScrollbar(scrollbar);
288
289     // <rdar://problem/9797253> AppKit resets the scrollbar's style when you attach a scrollbar
290     setScrollbarOverlayStyle(scrollbarOverlayStyle());
291 }
292
293 void ScrollableArea::willRemoveHorizontalScrollbar(Scrollbar* scrollbar)
294 {
295     scrollAnimator()->willRemoveHorizontalScrollbar(scrollbar);
296 }
297
298 void ScrollableArea::contentsResized()
299 {
300     if (ScrollAnimator* scrollAnimator = existingScrollAnimator())
301         scrollAnimator->contentsResized();
302 }
303
304 bool ScrollableArea::hasOverlayScrollbars() const
305 {
306     return (verticalScrollbar() && verticalScrollbar()->isOverlayScrollbar())
307         || (horizontalScrollbar() && horizontalScrollbar()->isOverlayScrollbar());
308 }
309
310 void ScrollableArea::setScrollbarOverlayStyle(ScrollbarOverlayStyle overlayStyle)
311 {
312     m_scrollbarOverlayStyle = overlayStyle;
313
314     if (horizontalScrollbar()) {
315         ScrollbarTheme::theme()->updateScrollbarOverlayStyle(horizontalScrollbar());
316         horizontalScrollbar()->invalidate();
317     }
318     
319     if (verticalScrollbar()) {
320         ScrollbarTheme::theme()->updateScrollbarOverlayStyle(verticalScrollbar());
321         verticalScrollbar()->invalidate();
322     }
323 }
324
325 void ScrollableArea::invalidateScrollbar(Scrollbar* scrollbar, const IntRect& rect)
326 {
327 #if USE(ACCELERATED_COMPOSITING)
328     if (scrollbar == horizontalScrollbar()) {
329         if (GraphicsLayer* graphicsLayer = layerForHorizontalScrollbar()) {
330             graphicsLayer->setNeedsDisplay();
331             graphicsLayer->setContentsNeedsDisplay();
332             return;
333         }
334     } else if (scrollbar == verticalScrollbar()) {
335         if (GraphicsLayer* graphicsLayer = layerForVerticalScrollbar()) {
336             graphicsLayer->setNeedsDisplay();
337             graphicsLayer->setContentsNeedsDisplay();
338             return;
339         }
340     }
341 #endif
342     invalidateScrollbarRect(scrollbar, rect);
343 }
344
345 void ScrollableArea::invalidateScrollCorner(const IntRect& rect)
346 {
347 #if USE(ACCELERATED_COMPOSITING)
348     if (GraphicsLayer* graphicsLayer = layerForScrollCorner()) {
349         graphicsLayer->setNeedsDisplay();
350         return;
351     }
352 #endif
353     invalidateScrollCornerRect(rect);
354 }
355
356 bool ScrollableArea::hasLayerForHorizontalScrollbar() const
357 {
358 #if USE(ACCELERATED_COMPOSITING)
359     return layerForHorizontalScrollbar();
360 #else
361     return false;
362 #endif
363 }
364
365 bool ScrollableArea::hasLayerForVerticalScrollbar() const
366 {
367 #if USE(ACCELERATED_COMPOSITING)
368     return layerForVerticalScrollbar();
369 #else
370     return false;
371 #endif
372 }
373
374 bool ScrollableArea::hasLayerForScrollCorner() const
375 {
376 #if USE(ACCELERATED_COMPOSITING)
377     return layerForScrollCorner();
378 #else
379     return false;
380 #endif
381 }
382
383 void ScrollableArea::serviceScrollAnimations()
384 {
385     if (ScrollAnimator* scrollAnimator = existingScrollAnimator())
386         scrollAnimator->serviceScrollAnimations();
387 }
388
389 IntPoint ScrollableArea::scrollPosition() const
390 {
391     int x = horizontalScrollbar() ? horizontalScrollbar()->value() : 0;
392     int y = verticalScrollbar() ? verticalScrollbar()->value() : 0;
393     return IntPoint(x, y);
394 }
395
396 IntPoint ScrollableArea::minimumScrollPosition() const
397 {
398     return IntPoint();
399 }
400
401 IntPoint ScrollableArea::maximumScrollPosition() const
402 {
403     return IntPoint(totalContentsSize().width() - visibleWidth(), totalContentsSize().height() - visibleHeight());
404 }
405
406 IntSize ScrollableArea::totalContentsSize() const
407 {
408     IntSize totalContentsSize = contentsSize();
409     totalContentsSize.setHeight(totalContentsSize.height() + headerHeight() + footerHeight());
410     return totalContentsSize;
411 }
412
413 IntRect ScrollableArea::visibleContentRect(VisibleContentRectIncludesScrollbars scrollbarInclusion) const
414 {
415     int verticalScrollbarWidth = 0;
416     int horizontalScrollbarHeight = 0;
417
418     if (scrollbarInclusion == IncludeScrollbars) {
419         if (Scrollbar* verticalBar = verticalScrollbar())
420             verticalScrollbarWidth = !verticalBar->isOverlayScrollbar() ? verticalBar->width() : 0;
421         if (Scrollbar* horizontalBar = horizontalScrollbar())
422             horizontalScrollbarHeight = !horizontalBar->isOverlayScrollbar() ? horizontalBar->height() : 0;
423     }
424
425     return IntRect(scrollPosition().x(),
426                    scrollPosition().y(),
427                    std::max(0, visibleWidth() + verticalScrollbarWidth),
428                    std::max(0, visibleHeight() + horizontalScrollbarHeight));
429 }
430
431 static int constrainedScrollPosition(int visibleContentSize, int totalContentsSize, int scrollPosition, int scrollOrigin, int headerHeight, int footerHeight)
432 {
433     int maxValue = totalContentsSize - visibleContentSize - footerHeight;
434     if (maxValue <= 0)
435         return 0;
436
437     if (!scrollOrigin) {
438         if (scrollPosition <= headerHeight)
439             return 0;
440         if (scrollPosition > maxValue)
441             scrollPosition = maxValue - headerHeight;
442         else
443             scrollPosition -= headerHeight;
444     } else {
445         // FIXME: position:fixed elements are currently broken when there is a non-zero y-value in the scroll origin
446         // such as when -webkit-writing-mode:horizontal-bt; is set. But when we fix that, we need to make such
447         // pages work correctly with headers and footers as well. https://bugs.webkit.org/show_bug.cgi?id=113741
448         if (scrollPosition >= 0)
449             return 0;
450         if (scrollPosition < -maxValue)
451             scrollPosition = -maxValue;
452     }
453
454     return scrollPosition;
455 }
456
457 IntPoint ScrollableArea::constrainScrollPositionForOverhang(const IntRect& visibleContentRect, const IntSize& totalContentsSize, const IntPoint& scrollPosition, const IntPoint& scrollOrigin, int headerHeight, int footerHeight)
458 {
459     return IntPoint(constrainedScrollPosition(visibleContentRect.width(), totalContentsSize.width(), scrollPosition.x(), scrollOrigin.x(), 0, 0),
460         constrainedScrollPosition(visibleContentRect.height(), totalContentsSize.height(), scrollPosition.y(), scrollOrigin.y(), headerHeight, footerHeight));
461 }
462
463 IntPoint ScrollableArea::constrainScrollPositionForOverhang(const IntPoint& scrollPosition)
464 {
465     return constrainScrollPositionForOverhang(visibleContentRect(), totalContentsSize(), scrollPosition, scrollOrigin(), headerHeight(), footerHeight());
466 }
467
468 void ScrollableArea::reportMemoryUsage(MemoryObjectInfo* memoryObjectInfo) const
469 {
470     MemoryClassInfo info(memoryObjectInfo, this);
471     info.addMember(m_scrollAnimator, "scrollAnimator");
472 }
473
474 } // namespace WebCore