Make it possible to test rubber-banding in overflow scroll
[WebKit-https.git] / Source / WebCore / platform / ScrollableArea.cpp
1 /*
2  * Copyright (c) 2010, Google Inc. All rights reserved.
3  * Copyright (C) 2008, 2011, 2014-2016 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 "FloatPoint.h"
36 #include "GraphicsContext.h"
37 #include "GraphicsLayer.h"
38 #include "LayoutRect.h"
39 #include "Logging.h"
40 #include "PlatformWheelEvent.h"
41 #include "ScrollAnimator.h"
42 #include "ScrollAnimatorMock.h"
43 #include "ScrollbarTheme.h"
44 #include <wtf/text/TextStream.h>
45
46 namespace WebCore {
47
48 struct SameSizeAsScrollableArea {
49     virtual ~SameSizeAsScrollableArea();
50 #if ENABLE(CSS_SCROLL_SNAP)
51     void* pointers[3];
52     unsigned currentIndices[2];
53 #else
54     void* pointer[2];
55 #endif
56     IntPoint origin;
57     unsigned bitfields : 16;
58 };
59
60 COMPILE_ASSERT(sizeof(ScrollableArea) == sizeof(SameSizeAsScrollableArea), ScrollableArea_should_stay_small);
61
62 ScrollableArea::ScrollableArea()
63     : m_constrainsScrollingToContentEdge(true)
64     , m_inLiveResize(false)
65     , m_verticalScrollElasticity(ScrollElasticityNone)
66     , m_horizontalScrollElasticity(ScrollElasticityNone)
67     , m_scrollbarOverlayStyle(ScrollbarOverlayStyleDefault)
68     , m_scrollOriginChanged(false)
69     , m_scrolledProgrammatically(false)
70 {
71 }
72
73 ScrollableArea::~ScrollableArea() = default;
74
75 ScrollAnimator& ScrollableArea::scrollAnimator() const
76 {
77     if (!m_scrollAnimator) {
78         if (usesMockScrollAnimator()) {
79             m_scrollAnimator = std::make_unique<ScrollAnimatorMock>(const_cast<ScrollableArea&>(*this), [this](const String& message) {
80                 logMockScrollAnimatorMessage(message);
81             });
82         } else
83             m_scrollAnimator = ScrollAnimator::create(const_cast<ScrollableArea&>(*this));
84     }
85
86     ASSERT(m_scrollAnimator);
87     return *m_scrollAnimator.get();
88 }
89
90 void ScrollableArea::setScrollOrigin(const IntPoint& origin)
91 {
92     if (m_scrollOrigin != origin) {
93         m_scrollOrigin = origin;
94         m_scrollOriginChanged = true;
95     }
96 }
97
98 float ScrollableArea::adjustScrollStepForFixedContent(float step, ScrollbarOrientation, ScrollGranularity)
99 {
100     return step;
101 }
102
103 bool ScrollableArea::scroll(ScrollDirection direction, ScrollGranularity granularity, float multiplier)
104 {
105     ScrollbarOrientation orientation;
106     Scrollbar* scrollbar;
107     if (direction == ScrollUp || direction == ScrollDown) {
108         orientation = VerticalScrollbar;
109         scrollbar = verticalScrollbar();
110     } else {
111         orientation = HorizontalScrollbar;
112         scrollbar = horizontalScrollbar();
113     }
114
115     if (!scrollbar)
116         return false;
117
118     float step = 0;
119     switch (granularity) {
120     case ScrollByLine:
121         step = scrollbar->lineStep();
122         break;
123     case ScrollByPage:
124         step = scrollbar->pageStep();
125         break;
126     case ScrollByDocument:
127         step = scrollbar->totalSize();
128         break;
129     case ScrollByPixel:
130     case ScrollByPrecisePixel:
131         step = scrollbar->pixelStep();
132         break;
133     }
134
135     if (direction == ScrollUp || direction == ScrollLeft)
136         multiplier = -multiplier;
137
138     step = adjustScrollStepForFixedContent(step, orientation, granularity);
139     return scrollAnimator().scroll(orientation, granularity, step, multiplier);
140 }
141
142 void ScrollableArea::scrollToOffsetWithoutAnimation(const FloatPoint& offset, ScrollClamping clamping)
143 {
144     LOG_WITH_STREAM(Scrolling, stream << "ScrollableArea " << this << " scrollToOffsetWithoutAnimation " << offset);
145     scrollAnimator().scrollToOffsetWithoutAnimation(offset, clamping);
146 }
147
148 void ScrollableArea::scrollToOffsetWithoutAnimation(ScrollbarOrientation orientation, float offset)
149 {
150     auto currentOffset = scrollOffsetFromPosition(IntPoint(scrollAnimator().currentPosition()));
151     if (orientation == HorizontalScrollbar)
152         scrollToOffsetWithoutAnimation(FloatPoint(offset, currentOffset.y()));
153     else
154         scrollToOffsetWithoutAnimation(FloatPoint(currentOffset.x(), offset));
155 }
156
157 void ScrollableArea::notifyScrollPositionChanged(const ScrollPosition& position)
158 {
159     scrollPositionChanged(position);
160     scrollAnimator().setCurrentPosition(position);
161 }
162
163 void ScrollableArea::scrollPositionChanged(const ScrollPosition& position)
164 {
165     IntPoint oldPosition = scrollPosition();
166     // Tell the derived class to scroll its contents.
167     setScrollOffset(scrollOffsetFromPosition(position));
168
169     Scrollbar* verticalScrollbar = this->verticalScrollbar();
170
171     // Tell the scrollbars to update their thumb postions.
172     if (Scrollbar* horizontalScrollbar = this->horizontalScrollbar()) {
173         horizontalScrollbar->offsetDidChange();
174         if (horizontalScrollbar->isOverlayScrollbar() && !hasLayerForHorizontalScrollbar()) {
175             if (!verticalScrollbar)
176                 horizontalScrollbar->invalidate();
177             else {
178                 // If there is both a horizontalScrollbar and a verticalScrollbar,
179                 // then we must also invalidate the corner between them.
180                 IntRect boundsAndCorner = horizontalScrollbar->boundsRect();
181                 boundsAndCorner.setWidth(boundsAndCorner.width() + verticalScrollbar->width());
182                 horizontalScrollbar->invalidateRect(boundsAndCorner);
183             }
184         }
185     }
186     if (verticalScrollbar) {
187         verticalScrollbar->offsetDidChange();
188         if (verticalScrollbar->isOverlayScrollbar() && !hasLayerForVerticalScrollbar())
189             verticalScrollbar->invalidate();
190     }
191
192     if (scrollPosition() != oldPosition)
193         scrollAnimator().notifyContentAreaScrolled(scrollPosition() - oldPosition);
194 }
195
196 bool ScrollableArea::handleWheelEvent(const PlatformWheelEvent& wheelEvent)
197 {
198     if (!isScrollableOrRubberbandable())
199         return false;
200
201     bool handledEvent = scrollAnimator().handleWheelEvent(wheelEvent);
202 #if ENABLE(CSS_SCROLL_SNAP)
203     if (scrollAnimator().activeScrollSnapIndexDidChange()) {
204         setCurrentHorizontalSnapPointIndex(scrollAnimator().activeScrollSnapIndexForAxis(ScrollEventAxis::Horizontal));
205         setCurrentVerticalSnapPointIndex(scrollAnimator().activeScrollSnapIndexForAxis(ScrollEventAxis::Vertical));
206     }
207 #endif
208     return handledEvent;
209 }
210
211 #if ENABLE(TOUCH_EVENTS)
212 bool ScrollableArea::handleTouchEvent(const PlatformTouchEvent& touchEvent)
213 {
214     return scrollAnimator().handleTouchEvent(touchEvent);
215 }
216 #endif
217
218 // NOTE: Only called from Internals for testing.
219 void ScrollableArea::setScrollOffsetFromInternals(const ScrollOffset& offset)
220 {
221     setScrollOffsetFromAnimation(offset);
222 }
223
224 void ScrollableArea::setScrollOffsetFromAnimation(const ScrollOffset& offset)
225 {
226     ScrollPosition position = scrollPositionFromOffset(offset);
227     if (requestScrollPositionUpdate(position))
228         return;
229
230     scrollPositionChanged(position);
231 }
232
233 void ScrollableArea::willStartLiveResize()
234 {
235     if (m_inLiveResize)
236         return;
237     m_inLiveResize = true;
238     if (ScrollAnimator* scrollAnimator = existingScrollAnimator())
239         scrollAnimator->willStartLiveResize();
240 }
241
242 void ScrollableArea::willEndLiveResize()
243 {
244     if (!m_inLiveResize)
245         return;
246     m_inLiveResize = false;
247     if (ScrollAnimator* scrollAnimator = existingScrollAnimator())
248         scrollAnimator->willEndLiveResize();
249 }    
250
251 void ScrollableArea::contentAreaWillPaint() const
252 {
253     if (ScrollAnimator* scrollAnimator = existingScrollAnimator())
254         scrollAnimator->contentAreaWillPaint();
255 }
256
257 void ScrollableArea::mouseEnteredContentArea() const
258 {
259     if (ScrollAnimator* scrollAnimator = existingScrollAnimator())
260         scrollAnimator->mouseEnteredContentArea();
261 }
262
263 void ScrollableArea::mouseExitedContentArea() const
264 {
265     if (ScrollAnimator* scrollAnimator = existingScrollAnimator())
266         scrollAnimator->mouseExitedContentArea();
267 }
268
269 void ScrollableArea::mouseMovedInContentArea() const
270 {
271     if (ScrollAnimator* scrollAnimator = existingScrollAnimator())
272         scrollAnimator->mouseMovedInContentArea();
273 }
274
275 void ScrollableArea::mouseEnteredScrollbar(Scrollbar* scrollbar) const
276 {
277     scrollAnimator().mouseEnteredScrollbar(scrollbar);
278 }
279
280 void ScrollableArea::mouseExitedScrollbar(Scrollbar* scrollbar) const
281 {
282     scrollAnimator().mouseExitedScrollbar(scrollbar);
283 }
284
285 void ScrollableArea::mouseIsDownInScrollbar(Scrollbar* scrollbar, bool mouseIsDown) const
286 {
287     scrollAnimator().mouseIsDownInScrollbar(scrollbar, mouseIsDown);
288 }
289
290 void ScrollableArea::contentAreaDidShow() const
291 {
292     if (ScrollAnimator* scrollAnimator = existingScrollAnimator())
293         scrollAnimator->contentAreaDidShow();
294 }
295
296 void ScrollableArea::contentAreaDidHide() const
297 {
298     if (ScrollAnimator* scrollAnimator = existingScrollAnimator())
299         scrollAnimator->contentAreaDidHide();
300 }
301
302 void ScrollableArea::lockOverlayScrollbarStateToHidden(bool shouldLockState) const
303 {
304     if (ScrollAnimator* scrollAnimator = existingScrollAnimator())
305         scrollAnimator->lockOverlayScrollbarStateToHidden(shouldLockState);
306 }
307
308 bool ScrollableArea::scrollbarsCanBeActive() const
309 {
310     if (ScrollAnimator* scrollAnimator = existingScrollAnimator())
311         return scrollAnimator->scrollbarsCanBeActive();
312     return true;
313 }
314
315 void ScrollableArea::didAddScrollbar(Scrollbar* scrollbar, ScrollbarOrientation orientation)
316 {
317     if (orientation == VerticalScrollbar)
318         scrollAnimator().didAddVerticalScrollbar(scrollbar);
319     else
320         scrollAnimator().didAddHorizontalScrollbar(scrollbar);
321
322     // <rdar://problem/9797253> AppKit resets the scrollbar's style when you attach a scrollbar
323     setScrollbarOverlayStyle(scrollbarOverlayStyle());
324 }
325
326 void ScrollableArea::willRemoveScrollbar(Scrollbar* scrollbar, ScrollbarOrientation orientation)
327 {
328     if (orientation == VerticalScrollbar)
329         scrollAnimator().willRemoveVerticalScrollbar(scrollbar);
330     else
331         scrollAnimator().willRemoveHorizontalScrollbar(scrollbar);
332 }
333
334 void ScrollableArea::contentsResized()
335 {
336     if (ScrollAnimator* scrollAnimator = existingScrollAnimator())
337         scrollAnimator->contentsResized();
338 }
339
340 void ScrollableArea::availableContentSizeChanged(AvailableSizeChangeReason)
341 {
342     if (ScrollAnimator* scrollAnimator = existingScrollAnimator())
343         scrollAnimator->contentsResized(); // This flashes overlay scrollbars.
344 }
345
346 bool ScrollableArea::hasOverlayScrollbars() const
347 {
348     return (verticalScrollbar() && verticalScrollbar()->isOverlayScrollbar())
349         || (horizontalScrollbar() && horizontalScrollbar()->isOverlayScrollbar());
350 }
351
352 void ScrollableArea::setScrollbarOverlayStyle(ScrollbarOverlayStyle overlayStyle)
353 {
354     m_scrollbarOverlayStyle = overlayStyle;
355
356     if (horizontalScrollbar()) {
357         ScrollbarTheme::theme().updateScrollbarOverlayStyle(*horizontalScrollbar());
358         horizontalScrollbar()->invalidate();
359         if (ScrollAnimator* scrollAnimator = existingScrollAnimator())
360             scrollAnimator->invalidateScrollbarPartLayers(horizontalScrollbar());
361     }
362     
363     if (verticalScrollbar()) {
364         ScrollbarTheme::theme().updateScrollbarOverlayStyle(*verticalScrollbar());
365         verticalScrollbar()->invalidate();
366         if (ScrollAnimator* scrollAnimator = existingScrollAnimator())
367             scrollAnimator->invalidateScrollbarPartLayers(verticalScrollbar());
368     }
369 }
370
371 void ScrollableArea::invalidateScrollbar(Scrollbar& scrollbar, const IntRect& rect)
372 {
373     if (&scrollbar == horizontalScrollbar()) {
374         if (GraphicsLayer* graphicsLayer = layerForHorizontalScrollbar()) {
375             graphicsLayer->setNeedsDisplay();
376             graphicsLayer->setContentsNeedsDisplay();
377             return;
378         }
379     } else if (&scrollbar == verticalScrollbar()) {
380         if (GraphicsLayer* graphicsLayer = layerForVerticalScrollbar()) {
381             graphicsLayer->setNeedsDisplay();
382             graphicsLayer->setContentsNeedsDisplay();
383             return;
384         }
385     }
386
387     invalidateScrollbarRect(scrollbar, rect);
388 }
389
390 void ScrollableArea::invalidateScrollCorner(const IntRect& rect)
391 {
392     if (GraphicsLayer* graphicsLayer = layerForScrollCorner()) {
393         graphicsLayer->setNeedsDisplay();
394         return;
395     }
396
397     invalidateScrollCornerRect(rect);
398 }
399
400 void ScrollableArea::verticalScrollbarLayerDidChange()
401 {
402     scrollAnimator().verticalScrollbarLayerDidChange();
403 }
404
405 void ScrollableArea::horizontalScrollbarLayerDidChange()
406 {
407     scrollAnimator().horizontalScrollbarLayerDidChange();
408 }
409
410 bool ScrollableArea::hasLayerForHorizontalScrollbar() const
411 {
412     return layerForHorizontalScrollbar();
413 }
414
415 bool ScrollableArea::hasLayerForVerticalScrollbar() const
416 {
417     return layerForVerticalScrollbar();
418 }
419
420 bool ScrollableArea::hasLayerForScrollCorner() const
421 {
422     return layerForScrollCorner();
423 }
424
425 #if ENABLE(CSS_SCROLL_SNAP)
426 ScrollSnapOffsetsInfo<LayoutUnit>& ScrollableArea::ensureSnapOffsetsInfo()
427 {
428     if (!m_snapOffsetsInfo)
429         m_snapOffsetsInfo = std::make_unique<ScrollSnapOffsetsInfo<LayoutUnit>>();
430     return *m_snapOffsetsInfo;
431 }
432
433 const Vector<LayoutUnit>* ScrollableArea::horizontalSnapOffsets() const
434 {
435     if (!m_snapOffsetsInfo)
436         return nullptr;
437
438     return &m_snapOffsetsInfo->horizontalSnapOffsets;
439 }
440
441 const Vector<ScrollOffsetRange<LayoutUnit>>* ScrollableArea::horizontalSnapOffsetRanges() const
442 {
443     if (!m_snapOffsetsInfo)
444         return nullptr;
445
446     return &m_snapOffsetsInfo->horizontalSnapOffsetRanges;
447 }
448
449 const Vector<ScrollOffsetRange<LayoutUnit>>* ScrollableArea::verticalSnapOffsetRanges() const
450 {
451     if (!m_snapOffsetsInfo)
452         return nullptr;
453
454     return &m_snapOffsetsInfo->verticalSnapOffsetRanges;
455 }
456
457 const Vector<LayoutUnit>* ScrollableArea::verticalSnapOffsets() const
458 {
459     if (!m_snapOffsetsInfo)
460         return nullptr;
461
462     return &m_snapOffsetsInfo->verticalSnapOffsets;
463 }
464
465 void ScrollableArea::setHorizontalSnapOffsets(const Vector<LayoutUnit>& horizontalSnapOffsets)
466 {
467     // Consider having a non-empty set of snap offsets as a cue to initialize the ScrollAnimator.
468     if (horizontalSnapOffsets.size())
469         scrollAnimator();
470
471     ensureSnapOffsetsInfo().horizontalSnapOffsets = horizontalSnapOffsets;
472 }
473
474 void ScrollableArea::setVerticalSnapOffsets(const Vector<LayoutUnit>& verticalSnapOffsets)
475 {
476     // Consider having a non-empty set of snap offsets as a cue to initialize the ScrollAnimator.
477     if (verticalSnapOffsets.size())
478         scrollAnimator();
479
480     ensureSnapOffsetsInfo().verticalSnapOffsets = verticalSnapOffsets;
481 }
482
483 void ScrollableArea::setHorizontalSnapOffsetRanges(const Vector<ScrollOffsetRange<LayoutUnit>>& horizontalRanges)
484 {
485     ensureSnapOffsetsInfo().horizontalSnapOffsetRanges = horizontalRanges;
486 }
487
488 void ScrollableArea::setVerticalSnapOffsetRanges(const Vector<ScrollOffsetRange<LayoutUnit>>& verticalRanges)
489 {
490     ensureSnapOffsetsInfo().verticalSnapOffsetRanges = verticalRanges;
491 }
492
493 void ScrollableArea::clearHorizontalSnapOffsets()
494 {
495     if (!m_snapOffsetsInfo)
496         return;
497
498     m_snapOffsetsInfo->horizontalSnapOffsets = { };
499     m_snapOffsetsInfo->horizontalSnapOffsetRanges = { };
500     m_currentHorizontalSnapPointIndex = 0;
501 }
502
503 void ScrollableArea::clearVerticalSnapOffsets()
504 {
505     if (!m_snapOffsetsInfo)
506         return;
507
508     m_snapOffsetsInfo->verticalSnapOffsets = { };
509     m_snapOffsetsInfo->verticalSnapOffsetRanges = { };
510     m_currentVerticalSnapPointIndex = 0;
511 }
512
513 IntPoint ScrollableArea::nearestActiveSnapPoint(const IntPoint& currentPosition)
514 {
515     if (!horizontalSnapOffsets() && !verticalSnapOffsets())
516         return currentPosition;
517     
518     if (!existingScrollAnimator())
519         return currentPosition;
520     
521     IntPoint correctedPosition = currentPosition;
522     
523     if (horizontalSnapOffsets()) {
524         const auto& horizontal = *horizontalSnapOffsets();
525         
526         size_t activeIndex = currentHorizontalSnapPointIndex();
527         if (activeIndex < horizontal.size())
528             correctedPosition.setX(horizontal[activeIndex].toInt());
529     }
530     
531     if (verticalSnapOffsets()) {
532         const auto& vertical = *verticalSnapOffsets();
533         
534         size_t activeIndex = currentVerticalSnapPointIndex();
535         if (activeIndex < vertical.size())
536             correctedPosition.setY(vertical[activeIndex].toInt());
537     }
538
539     return correctedPosition;
540 }
541
542 void ScrollableArea::updateScrollSnapState()
543 {
544     if (ScrollAnimator* scrollAnimator = existingScrollAnimator())
545         scrollAnimator->updateScrollSnapState();
546
547     if (isScrollSnapInProgress())
548         return;
549
550     IntPoint currentPosition = scrollPosition();
551     IntPoint correctedPosition = nearestActiveSnapPoint(currentPosition);
552     
553     if (correctedPosition != currentPosition)
554         scrollToOffsetWithoutAnimation(correctedPosition);
555 }
556 #else
557 void ScrollableArea::updateScrollSnapState()
558 {
559 }
560 #endif
561
562
563 void ScrollableArea::serviceScrollAnimations()
564 {
565     if (ScrollAnimator* scrollAnimator = existingScrollAnimator())
566         scrollAnimator->serviceScrollAnimations();
567 }
568
569 #if PLATFORM(IOS)
570 bool ScrollableArea::isPinnedInBothDirections(const IntSize& scrollDelta) const
571 {
572     return isPinnedHorizontallyInDirection(scrollDelta.width()) && isPinnedVerticallyInDirection(scrollDelta.height());
573 }
574
575 bool ScrollableArea::isPinnedHorizontallyInDirection(int horizontalScrollDelta) const
576 {
577     if (horizontalScrollDelta < 0 && isHorizontalScrollerPinnedToMinimumPosition())
578         return true;
579     if (horizontalScrollDelta > 0 && isHorizontalScrollerPinnedToMaximumPosition())
580         return true;
581     return false;
582 }
583
584 bool ScrollableArea::isPinnedVerticallyInDirection(int verticalScrollDelta) const
585 {
586     if (verticalScrollDelta < 0 && isVerticalScrollerPinnedToMinimumPosition())
587         return true;
588     if (verticalScrollDelta > 0 && isVerticalScrollerPinnedToMaximumPosition())
589         return true;
590     return false;
591 }
592 #endif // PLATFORM(IOS)
593
594 int ScrollableArea::horizontalScrollbarIntrusion() const
595 {
596     return verticalScrollbar() ? verticalScrollbar()->occupiedWidth() : 0;
597 }
598
599 int ScrollableArea::verticalScrollbarIntrusion() const
600 {
601     return horizontalScrollbar() ? horizontalScrollbar()->occupiedHeight() : 0;
602 }
603
604 IntSize ScrollableArea::scrollbarIntrusion() const
605 {
606     return { horizontalScrollbarIntrusion(), verticalScrollbarIntrusion() };
607 }
608
609 ScrollPosition ScrollableArea::scrollPosition() const
610 {
611     // FIXME: This relationship seems to be inverted. Scrollbars should be 'view', not 'model', and should get their values from us.
612     int x = horizontalScrollbar() ? horizontalScrollbar()->value() : 0;
613     int y = verticalScrollbar() ? verticalScrollbar()->value() : 0;
614     return IntPoint(x, y);
615 }
616
617 ScrollPosition ScrollableArea::minimumScrollPosition() const
618 {
619     return scrollPositionFromOffset(ScrollPosition());
620 }
621
622 ScrollPosition ScrollableArea::maximumScrollPosition() const
623 {
624     return scrollPositionFromOffset(ScrollPosition(totalContentsSize() - visibleSize()));
625 }
626
627 ScrollOffset ScrollableArea::maximumScrollOffset() const
628 {
629     return ScrollOffset(totalContentsSize() - visibleSize());
630 }
631
632 ScrollPosition ScrollableArea::scrollPositionFromOffset(ScrollOffset offset) const
633 {
634     return scrollPositionFromOffset(offset, toIntSize(m_scrollOrigin));
635 }
636
637 ScrollOffset ScrollableArea::scrollOffsetFromPosition(ScrollPosition position) const
638 {
639     return scrollOffsetFromPosition(position, toIntSize(m_scrollOrigin));
640 }
641
642 bool ScrollableArea::scrolledToTop() const
643 {
644     return scrollPosition().y() <= minimumScrollPosition().y();
645 }
646
647 bool ScrollableArea::scrolledToBottom() const
648 {
649     return scrollPosition().y() >= maximumScrollPosition().y();
650 }
651
652 bool ScrollableArea::scrolledToLeft() const
653 {
654     return scrollPosition().x() <= minimumScrollPosition().x();
655 }
656
657 bool ScrollableArea::scrolledToRight() const
658 {
659     return scrollPosition().x() >= maximumScrollPosition().x();
660 }
661
662 void ScrollableArea::scrollbarStyleChanged(ScrollbarStyle, bool)
663 {
664     availableContentSizeChanged(AvailableSizeChangeReason::ScrollbarsChanged);
665 }
666
667 IntSize ScrollableArea::totalContentsSize() const
668 {
669     IntSize totalContentsSize = contentsSize();
670     totalContentsSize.setHeight(totalContentsSize.height() + headerHeight() + footerHeight());
671     return totalContentsSize;
672 }
673
674 IntRect ScrollableArea::visibleContentRect(VisibleContentRectBehavior visibleContentRectBehavior) const
675 {
676     return visibleContentRectInternal(ExcludeScrollbars, visibleContentRectBehavior);
677 }
678
679 IntRect ScrollableArea::visibleContentRectIncludingScrollbars(VisibleContentRectBehavior visibleContentRectBehavior) const
680 {
681     return visibleContentRectInternal(IncludeScrollbars, visibleContentRectBehavior);
682 }
683
684 IntRect ScrollableArea::visibleContentRectInternal(VisibleContentRectIncludesScrollbars scrollbarInclusion, VisibleContentRectBehavior) const
685 {
686     int verticalScrollbarWidth = 0;
687     int horizontalScrollbarHeight = 0;
688
689     if (scrollbarInclusion == IncludeScrollbars) {
690         if (Scrollbar* verticalBar = verticalScrollbar())
691             verticalScrollbarWidth = verticalBar->occupiedWidth();
692         if (Scrollbar* horizontalBar = horizontalScrollbar())
693             horizontalScrollbarHeight = horizontalBar->occupiedHeight();
694     }
695
696     return IntRect(scrollPosition().x(),
697                    scrollPosition().y(),
698                    std::max(0, visibleWidth() + verticalScrollbarWidth),
699                    std::max(0, visibleHeight() + horizontalScrollbarHeight));
700 }
701
702 LayoutPoint ScrollableArea::constrainScrollPositionForOverhang(const LayoutRect& visibleContentRect, const LayoutSize& totalContentsSize, const LayoutPoint& scrollPosition, const LayoutPoint& scrollOrigin, int headerHeight, int footerHeight)
703 {
704     // The viewport rect that we're scrolling shouldn't be larger than our document.
705     LayoutSize idealScrollRectSize(std::min(visibleContentRect.width(), totalContentsSize.width()), std::min(visibleContentRect.height(), totalContentsSize.height()));
706     
707     LayoutRect scrollRect(scrollPosition + scrollOrigin - LayoutSize(0, headerHeight), idealScrollRectSize);
708     LayoutRect documentRect(LayoutPoint(), LayoutSize(totalContentsSize.width(), totalContentsSize.height() - headerHeight - footerHeight));
709
710     // Use intersection to constrain our ideal scroll rect by the document rect.
711     scrollRect.intersect(documentRect);
712
713     if (scrollRect.size() != idealScrollRectSize) {
714         // If the rect was clipped, restore its size, effectively pushing it "down" from the top left.
715         scrollRect.setSize(idealScrollRectSize);
716
717         // If we still clip, push our rect "up" from the bottom right.
718         scrollRect.intersect(documentRect);
719         if (scrollRect.width() < idealScrollRectSize.width())
720             scrollRect.move(-(idealScrollRectSize.width() - scrollRect.width()), 0);
721         if (scrollRect.height() < idealScrollRectSize.height())
722             scrollRect.move(0, -(idealScrollRectSize.height() - scrollRect.height()));
723     }
724
725     return scrollRect.location() - toLayoutSize(scrollOrigin);
726 }
727
728 LayoutPoint ScrollableArea::constrainScrollPositionForOverhang(const LayoutPoint& scrollPosition)
729 {
730     return constrainScrollPositionForOverhang(visibleContentRect(), totalContentsSize(), scrollPosition, scrollOrigin(), headerHeight(), footerHeight());
731 }
732
733 void ScrollableArea::computeScrollbarValueAndOverhang(float currentPosition, float totalSize, float visibleSize, float& doubleValue, float& overhangAmount)
734 {
735     doubleValue = 0;
736     overhangAmount = 0;
737     float maximum = totalSize - visibleSize;
738
739     if (currentPosition < 0) {
740         // Scrolled past the top.
741         doubleValue = 0;
742         overhangAmount = -currentPosition;
743     } else if (visibleSize + currentPosition > totalSize) {
744         // Scrolled past the bottom.
745         doubleValue = 1;
746         overhangAmount = currentPosition + visibleSize - totalSize;
747     } else {
748         // Within the bounds of the scrollable area.
749         if (maximum > 0)
750             doubleValue = currentPosition / maximum;
751         else
752             doubleValue = 0;
753     }
754 }
755
756 } // namespace WebCore