Fix reload and programmatic scrolling in RTL documents
[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 ScrollPosition& position)
155 {
156     IntPoint oldPosition = scrollPosition();
157     // Tell the derived class to scroll its contents.
158     setScrollOffset(scrollOffsetFromPosition(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 ScrollOffset& offset)
211 {
212     setScrollOffsetFromAnimation(offset);
213 }
214
215 void ScrollableArea::setScrollOffsetFromAnimation(const ScrollOffset& offset)
216 {
217     ScrollPosition position = scrollPositionFromOffset(offset);
218     if (requestScrollPositionUpdate(position))
219         return;
220
221     scrollPositionChanged(position);
222 }
223
224 void ScrollableArea::willStartLiveResize()
225 {
226     if (m_inLiveResize)
227         return;
228     m_inLiveResize = true;
229     if (ScrollAnimator* scrollAnimator = existingScrollAnimator())
230         scrollAnimator->willStartLiveResize();
231 }
232
233 void ScrollableArea::willEndLiveResize()
234 {
235     if (!m_inLiveResize)
236         return;
237     m_inLiveResize = false;
238     if (ScrollAnimator* scrollAnimator = existingScrollAnimator())
239         scrollAnimator->willEndLiveResize();
240 }    
241
242 void ScrollableArea::contentAreaWillPaint() const
243 {
244     if (ScrollAnimator* scrollAnimator = existingScrollAnimator())
245         scrollAnimator->contentAreaWillPaint();
246 }
247
248 void ScrollableArea::mouseEnteredContentArea() const
249 {
250     if (ScrollAnimator* scrollAnimator = existingScrollAnimator())
251         scrollAnimator->mouseEnteredContentArea();
252 }
253
254 void ScrollableArea::mouseExitedContentArea() const
255 {
256     if (ScrollAnimator* scrollAnimator = existingScrollAnimator())
257         scrollAnimator->mouseExitedContentArea();
258 }
259
260 void ScrollableArea::mouseMovedInContentArea() const
261 {
262     if (ScrollAnimator* scrollAnimator = existingScrollAnimator())
263         scrollAnimator->mouseMovedInContentArea();
264 }
265
266 void ScrollableArea::mouseEnteredScrollbar(Scrollbar* scrollbar) const
267 {
268     scrollAnimator().mouseEnteredScrollbar(scrollbar);
269 }
270
271 void ScrollableArea::mouseExitedScrollbar(Scrollbar* scrollbar) const
272 {
273     scrollAnimator().mouseExitedScrollbar(scrollbar);
274 }
275
276 void ScrollableArea::mouseIsDownInScrollbar(Scrollbar* scrollbar, bool mouseIsDown) const
277 {
278     scrollAnimator().mouseIsDownInScrollbar(scrollbar, mouseIsDown);
279 }
280
281 void ScrollableArea::contentAreaDidShow() const
282 {
283     if (ScrollAnimator* scrollAnimator = existingScrollAnimator())
284         scrollAnimator->contentAreaDidShow();
285 }
286
287 void ScrollableArea::contentAreaDidHide() const
288 {
289     if (ScrollAnimator* scrollAnimator = existingScrollAnimator())
290         scrollAnimator->contentAreaDidHide();
291 }
292
293 void ScrollableArea::lockOverlayScrollbarStateToHidden(bool shouldLockState) const
294 {
295     if (ScrollAnimator* scrollAnimator = existingScrollAnimator())
296         scrollAnimator->lockOverlayScrollbarStateToHidden(shouldLockState);
297 }
298
299 bool ScrollableArea::scrollbarsCanBeActive() const
300 {
301     if (ScrollAnimator* scrollAnimator = existingScrollAnimator())
302         return scrollAnimator->scrollbarsCanBeActive();
303     return true;
304 }
305
306 void ScrollableArea::didAddScrollbar(Scrollbar* scrollbar, ScrollbarOrientation orientation)
307 {
308     if (orientation == VerticalScrollbar)
309         scrollAnimator().didAddVerticalScrollbar(scrollbar);
310     else
311         scrollAnimator().didAddHorizontalScrollbar(scrollbar);
312
313     // <rdar://problem/9797253> AppKit resets the scrollbar's style when you attach a scrollbar
314     setScrollbarOverlayStyle(scrollbarOverlayStyle());
315 }
316
317 void ScrollableArea::willRemoveScrollbar(Scrollbar* scrollbar, ScrollbarOrientation orientation)
318 {
319     if (orientation == VerticalScrollbar)
320         scrollAnimator().willRemoveVerticalScrollbar(scrollbar);
321     else
322         scrollAnimator().willRemoveHorizontalScrollbar(scrollbar);
323 }
324
325 void ScrollableArea::contentsResized()
326 {
327     if (ScrollAnimator* scrollAnimator = existingScrollAnimator())
328         scrollAnimator->contentsResized();
329 }
330
331 void ScrollableArea::availableContentSizeChanged(AvailableSizeChangeReason)
332 {
333     if (ScrollAnimator* scrollAnimator = existingScrollAnimator())
334         scrollAnimator->contentsResized(); // This flashes overlay scrollbars.
335 }
336
337 bool ScrollableArea::hasOverlayScrollbars() const
338 {
339     return (verticalScrollbar() && verticalScrollbar()->isOverlayScrollbar())
340         || (horizontalScrollbar() && horizontalScrollbar()->isOverlayScrollbar());
341 }
342
343 void ScrollableArea::setScrollbarOverlayStyle(ScrollbarOverlayStyle overlayStyle)
344 {
345     m_scrollbarOverlayStyle = overlayStyle;
346
347     if (horizontalScrollbar()) {
348         ScrollbarTheme::theme().updateScrollbarOverlayStyle(*horizontalScrollbar());
349         horizontalScrollbar()->invalidate();
350         if (ScrollAnimator* scrollAnimator = existingScrollAnimator())
351             scrollAnimator->invalidateScrollbarPartLayers(horizontalScrollbar());
352     }
353     
354     if (verticalScrollbar()) {
355         ScrollbarTheme::theme().updateScrollbarOverlayStyle(*verticalScrollbar());
356         verticalScrollbar()->invalidate();
357         if (ScrollAnimator* scrollAnimator = existingScrollAnimator())
358             scrollAnimator->invalidateScrollbarPartLayers(verticalScrollbar());
359     }
360 }
361
362 void ScrollableArea::invalidateScrollbar(Scrollbar* scrollbar, const IntRect& rect)
363 {
364     if (scrollbar == horizontalScrollbar()) {
365         if (GraphicsLayer* graphicsLayer = layerForHorizontalScrollbar()) {
366             graphicsLayer->setNeedsDisplay();
367             graphicsLayer->setContentsNeedsDisplay();
368             return;
369         }
370     } else if (scrollbar == verticalScrollbar()) {
371         if (GraphicsLayer* graphicsLayer = layerForVerticalScrollbar()) {
372             graphicsLayer->setNeedsDisplay();
373             graphicsLayer->setContentsNeedsDisplay();
374             return;
375         }
376     }
377
378     invalidateScrollbarRect(scrollbar, rect);
379 }
380
381 void ScrollableArea::invalidateScrollCorner(const IntRect& rect)
382 {
383     if (GraphicsLayer* graphicsLayer = layerForScrollCorner()) {
384         graphicsLayer->setNeedsDisplay();
385         return;
386     }
387
388     invalidateScrollCornerRect(rect);
389 }
390
391 void ScrollableArea::verticalScrollbarLayerDidChange()
392 {
393     scrollAnimator().verticalScrollbarLayerDidChange();
394 }
395
396 void ScrollableArea::horizontalScrollbarLayerDidChange()
397 {
398     scrollAnimator().horizontalScrollbarLayerDidChange();
399 }
400
401 bool ScrollableArea::hasLayerForHorizontalScrollbar() const
402 {
403     return layerForHorizontalScrollbar();
404 }
405
406 bool ScrollableArea::hasLayerForVerticalScrollbar() const
407 {
408     return layerForVerticalScrollbar();
409 }
410
411 bool ScrollableArea::hasLayerForScrollCorner() const
412 {
413     return layerForScrollCorner();
414 }
415
416 #if ENABLE(CSS_SCROLL_SNAP)
417 void ScrollableArea::setHorizontalSnapOffsets(std::unique_ptr<Vector<LayoutUnit>> horizontalSnapOffsets)
418 {
419     m_horizontalSnapOffsets = WTF::move(horizontalSnapOffsets);
420 }
421
422 void ScrollableArea::setVerticalSnapOffsets(std::unique_ptr<Vector<LayoutUnit>> verticalSnapOffsets)
423 {
424     m_verticalSnapOffsets = WTF::move(verticalSnapOffsets);
425 }
426
427 void ScrollableArea::clearHorizontalSnapOffsets()
428 {
429     m_horizontalSnapOffsets = nullptr;
430     m_currentHorizontalSnapPointIndex = 0;
431 }
432
433 void ScrollableArea::clearVerticalSnapOffsets()
434 {
435     m_verticalSnapOffsets = nullptr;
436     m_currentVerticalSnapPointIndex = 0;
437 }
438
439 IntPoint ScrollableArea::nearestActiveSnapPoint(const IntPoint& currentPosition)
440 {
441     if (!horizontalSnapOffsets() && !verticalSnapOffsets())
442         return currentPosition;
443     
444     if (!existingScrollAnimator())
445         return currentPosition;
446     
447     IntPoint correctedPosition = currentPosition;
448     
449     if (horizontalSnapOffsets()) {
450         const auto& horizontal = *horizontalSnapOffsets();
451         
452         size_t activeIndex = currentHorizontalSnapPointIndex();
453         if (activeIndex < horizontal.size())
454             correctedPosition.setX(horizontal[activeIndex].toInt());
455     }
456     
457     if (verticalSnapOffsets()) {
458         const auto& vertical = *verticalSnapOffsets();
459         
460         size_t activeIndex = currentVerticalSnapPointIndex();
461         if (activeIndex < vertical.size())
462             correctedPosition.setY(vertical[activeIndex].toInt());
463     }
464
465     return correctedPosition;
466 }
467
468 void ScrollableArea::updateScrollSnapState()
469 {
470     if (ScrollAnimator* scrollAnimator = existingScrollAnimator())
471         scrollAnimator->updateScrollSnapState();
472
473     if (isScrollSnapInProgress())
474         return;
475
476     IntPoint currentPosition = scrollPosition();
477     IntPoint correctedPosition = nearestActiveSnapPoint(currentPosition);
478     
479     if (correctedPosition != currentPosition)
480         scrollToOffsetWithoutAnimation(correctedPosition);
481 }
482 #else
483 void ScrollableArea::updateScrollSnapState()
484 {
485 }
486 #endif
487
488
489 void ScrollableArea::serviceScrollAnimations()
490 {
491     if (ScrollAnimator* scrollAnimator = existingScrollAnimator())
492         scrollAnimator->serviceScrollAnimations();
493 }
494
495 #if PLATFORM(IOS)
496 bool ScrollableArea::isPinnedInBothDirections(const IntSize& scrollDelta) const
497 {
498     return isPinnedHorizontallyInDirection(scrollDelta.width()) && isPinnedVerticallyInDirection(scrollDelta.height());
499 }
500
501 bool ScrollableArea::isPinnedHorizontallyInDirection(int horizontalScrollDelta) const
502 {
503     if (horizontalScrollDelta < 0 && isHorizontalScrollerPinnedToMinimumPosition())
504         return true;
505     if (horizontalScrollDelta > 0 && isHorizontalScrollerPinnedToMaximumPosition())
506         return true;
507     return false;
508 }
509
510 bool ScrollableArea::isPinnedVerticallyInDirection(int verticalScrollDelta) const
511 {
512     if (verticalScrollDelta < 0 && isVerticalScrollerPinnedToMinimumPosition())
513         return true;
514     if (verticalScrollDelta > 0 && isVerticalScrollerPinnedToMaximumPosition())
515         return true;
516     return false;
517 }
518 #endif // PLATFORM(IOS)
519
520 IntSize ScrollableArea::scrollbarIntrusion() const
521 {
522     return {
523         verticalScrollbar() ? verticalScrollbar()->occupiedWidth() : 0,
524         horizontalScrollbar() ? horizontalScrollbar()->occupiedHeight() : 0
525     };
526 }
527
528 ScrollPosition ScrollableArea::scrollPosition() const
529 {
530     // FIXME: This relationship seems to be inverted. Scrollbars should be 'view', not 'model', and should get their values from us.
531     int x = horizontalScrollbar() ? horizontalScrollbar()->value() : 0;
532     int y = verticalScrollbar() ? verticalScrollbar()->value() : 0;
533     return IntPoint(x, y);
534 }
535
536 ScrollPosition ScrollableArea::minimumScrollPosition() const
537 {
538     return scrollPositionFromOffset(ScrollPosition());
539 }
540
541 ScrollPosition ScrollableArea::maximumScrollPosition() const
542 {
543     return scrollPositionFromOffset(ScrollPosition(totalContentsSize() - visibleSize()));
544 }
545
546 ScrollOffset ScrollableArea::maximumScrollOffset() const
547 {
548     return ScrollOffset(totalContentsSize() - visibleSize());
549 }
550
551 ScrollPosition ScrollableArea::scrollPositionFromOffset(ScrollOffset offset) const
552 {
553     return scrollPositionFromOffset(offset, toIntSize(m_scrollOrigin));
554 }
555
556 ScrollOffset ScrollableArea::scrollOffsetFromPosition(ScrollPosition position) const
557 {
558     return scrollOffsetFromPosition(position, toIntSize(m_scrollOrigin));
559 }
560
561 bool ScrollableArea::scrolledToTop() const
562 {
563     return scrollPosition().y() <= minimumScrollPosition().y();
564 }
565
566 bool ScrollableArea::scrolledToBottom() const
567 {
568     return scrollPosition().y() >= maximumScrollPosition().y();
569 }
570
571 bool ScrollableArea::scrolledToLeft() const
572 {
573     return scrollPosition().x() <= minimumScrollPosition().x();
574 }
575
576 bool ScrollableArea::scrolledToRight() const
577 {
578     return scrollPosition().x() >= maximumScrollPosition().x();
579 }
580
581 void ScrollableArea::scrollbarStyleChanged(ScrollbarStyle, bool)
582 {
583     availableContentSizeChanged(AvailableSizeChangeReason::ScrollbarsChanged);
584 }
585
586 IntSize ScrollableArea::totalContentsSize() const
587 {
588     IntSize totalContentsSize = contentsSize();
589     totalContentsSize.setHeight(totalContentsSize.height() + headerHeight() + footerHeight());
590     return totalContentsSize;
591 }
592
593 IntRect ScrollableArea::visibleContentRect(VisibleContentRectBehavior visibleContentRectBehavior) const
594 {
595     return visibleContentRectInternal(ExcludeScrollbars, visibleContentRectBehavior);
596 }
597
598 IntRect ScrollableArea::visibleContentRectIncludingScrollbars(VisibleContentRectBehavior visibleContentRectBehavior) const
599 {
600     return visibleContentRectInternal(IncludeScrollbars, visibleContentRectBehavior);
601 }
602
603 IntRect ScrollableArea::visibleContentRectInternal(VisibleContentRectIncludesScrollbars scrollbarInclusion, VisibleContentRectBehavior) const
604 {
605     int verticalScrollbarWidth = 0;
606     int horizontalScrollbarHeight = 0;
607
608     if (scrollbarInclusion == IncludeScrollbars) {
609         if (Scrollbar* verticalBar = verticalScrollbar())
610             verticalScrollbarWidth = verticalBar->occupiedWidth();
611         if (Scrollbar* horizontalBar = horizontalScrollbar())
612             horizontalScrollbarHeight = horizontalBar->occupiedHeight();
613     }
614
615     return IntRect(scrollPosition().x(),
616                    scrollPosition().y(),
617                    std::max(0, visibleWidth() + verticalScrollbarWidth),
618                    std::max(0, visibleHeight() + horizontalScrollbarHeight));
619 }
620
621 LayoutPoint ScrollableArea::constrainScrollPositionForOverhang(const LayoutRect& visibleContentRect, const LayoutSize& totalContentsSize, const LayoutPoint& scrollPosition, const LayoutPoint& scrollOrigin, int headerHeight, int footerHeight)
622 {
623     // The viewport rect that we're scrolling shouldn't be larger than our document.
624     LayoutSize idealScrollRectSize(std::min(visibleContentRect.width(), totalContentsSize.width()), std::min(visibleContentRect.height(), totalContentsSize.height()));
625     
626     LayoutRect scrollRect(scrollPosition + scrollOrigin - LayoutSize(0, headerHeight), idealScrollRectSize);
627     LayoutRect documentRect(LayoutPoint(), LayoutSize(totalContentsSize.width(), totalContentsSize.height() - headerHeight - footerHeight));
628
629     // Use intersection to constrain our ideal scroll rect by the document rect.
630     scrollRect.intersect(documentRect);
631
632     if (scrollRect.size() != idealScrollRectSize) {
633         // If the rect was clipped, restore its size, effectively pushing it "down" from the top left.
634         scrollRect.setSize(idealScrollRectSize);
635
636         // If we still clip, push our rect "up" from the bottom right.
637         scrollRect.intersect(documentRect);
638         if (scrollRect.width() < idealScrollRectSize.width())
639             scrollRect.move(-(idealScrollRectSize.width() - scrollRect.width()), 0);
640         if (scrollRect.height() < idealScrollRectSize.height())
641             scrollRect.move(0, -(idealScrollRectSize.height() - scrollRect.height()));
642     }
643
644     return scrollRect.location() - toLayoutSize(scrollOrigin);
645 }
646
647 LayoutPoint ScrollableArea::constrainScrollPositionForOverhang(const LayoutPoint& scrollPosition)
648 {
649     return constrainScrollPositionForOverhang(visibleContentRect(), totalContentsSize(), scrollPosition, scrollOrigin(), headerHeight(), footerHeight());
650 }
651
652 void ScrollableArea::computeScrollbarValueAndOverhang(float currentPosition, float totalSize, float visibleSize, float& doubleValue, float& overhangAmount)
653 {
654     doubleValue = 0;
655     overhangAmount = 0;
656     float maximum = totalSize - visibleSize;
657
658     if (currentPosition < 0) {
659         // Scrolled past the top.
660         doubleValue = 0;
661         overhangAmount = -currentPosition;
662     } else if (visibleSize + currentPosition > totalSize) {
663         // Scrolled past the bottom.
664         doubleValue = 1;
665         overhangAmount = currentPosition + visibleSize - totalSize;
666     } else {
667         // Within the bounds of the scrollable area.
668         if (maximum > 0)
669             doubleValue = currentPosition / maximum;
670         else
671             doubleValue = 0;
672     }
673 }
674
675 } // namespace WebCore