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