Web Automation: elements larger than the viewport have incorrect in-view center point
[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_currentScrollType(static_cast<unsigned>(ScrollType::User))
70     , m_scrollShouldClearLatchedState(false)
71 {
72 }
73
74 ScrollableArea::~ScrollableArea() = default;
75
76 ScrollAnimator& ScrollableArea::scrollAnimator() const
77 {
78     if (!m_scrollAnimator) {
79         if (usesMockScrollAnimator()) {
80             m_scrollAnimator = std::make_unique<ScrollAnimatorMock>(const_cast<ScrollableArea&>(*this), [this](const String& message) {
81                 logMockScrollAnimatorMessage(message);
82             });
83         } else
84             m_scrollAnimator = ScrollAnimator::create(const_cast<ScrollableArea&>(*this));
85     }
86
87     ASSERT(m_scrollAnimator);
88     return *m_scrollAnimator.get();
89 }
90
91 void ScrollableArea::setScrollOrigin(const IntPoint& origin)
92 {
93     if (m_scrollOrigin != origin) {
94         m_scrollOrigin = origin;
95         m_scrollOriginChanged = true;
96     }
97 }
98
99 float ScrollableArea::adjustScrollStepForFixedContent(float step, ScrollbarOrientation, ScrollGranularity)
100 {
101     return step;
102 }
103
104 bool ScrollableArea::scroll(ScrollDirection direction, ScrollGranularity granularity, float multiplier)
105 {
106     ScrollbarOrientation orientation;
107     Scrollbar* scrollbar;
108     if (direction == ScrollUp || direction == ScrollDown) {
109         orientation = VerticalScrollbar;
110         scrollbar = verticalScrollbar();
111     } else {
112         orientation = HorizontalScrollbar;
113         scrollbar = horizontalScrollbar();
114     }
115
116     if (!scrollbar)
117         return false;
118
119     float step = 0;
120     switch (granularity) {
121     case ScrollByLine:
122         step = scrollbar->lineStep();
123         break;
124     case ScrollByPage:
125         step = scrollbar->pageStep();
126         break;
127     case ScrollByDocument:
128         step = scrollbar->totalSize();
129         break;
130     case ScrollByPixel:
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 bool ScrollableArea::useDarkAppearanceForScrollbars() const
372 {
373     // If dark appearance is used or the overlay style is light (because of a dark page background), set the dark appearance.
374     return useDarkAppearance() || scrollbarOverlayStyle() == WebCore::ScrollbarOverlayStyleLight;
375 }
376
377 void ScrollableArea::invalidateScrollbar(Scrollbar& scrollbar, const IntRect& rect)
378 {
379     if (&scrollbar == horizontalScrollbar()) {
380         if (GraphicsLayer* graphicsLayer = layerForHorizontalScrollbar()) {
381             graphicsLayer->setNeedsDisplay();
382             graphicsLayer->setContentsNeedsDisplay();
383             return;
384         }
385     } else if (&scrollbar == verticalScrollbar()) {
386         if (GraphicsLayer* graphicsLayer = layerForVerticalScrollbar()) {
387             graphicsLayer->setNeedsDisplay();
388             graphicsLayer->setContentsNeedsDisplay();
389             return;
390         }
391     }
392
393     invalidateScrollbarRect(scrollbar, rect);
394 }
395
396 void ScrollableArea::invalidateScrollCorner(const IntRect& rect)
397 {
398     if (GraphicsLayer* graphicsLayer = layerForScrollCorner()) {
399         graphicsLayer->setNeedsDisplay();
400         return;
401     }
402
403     invalidateScrollCornerRect(rect);
404 }
405
406 void ScrollableArea::verticalScrollbarLayerDidChange()
407 {
408     scrollAnimator().verticalScrollbarLayerDidChange();
409 }
410
411 void ScrollableArea::horizontalScrollbarLayerDidChange()
412 {
413     scrollAnimator().horizontalScrollbarLayerDidChange();
414 }
415
416 bool ScrollableArea::hasLayerForHorizontalScrollbar() const
417 {
418     return layerForHorizontalScrollbar();
419 }
420
421 bool ScrollableArea::hasLayerForVerticalScrollbar() const
422 {
423     return layerForVerticalScrollbar();
424 }
425
426 bool ScrollableArea::hasLayerForScrollCorner() const
427 {
428     return layerForScrollCorner();
429 }
430
431 #if ENABLE(CSS_SCROLL_SNAP)
432 ScrollSnapOffsetsInfo<LayoutUnit>& ScrollableArea::ensureSnapOffsetsInfo()
433 {
434     if (!m_snapOffsetsInfo)
435         m_snapOffsetsInfo = std::make_unique<ScrollSnapOffsetsInfo<LayoutUnit>>();
436     return *m_snapOffsetsInfo;
437 }
438
439 const Vector<LayoutUnit>* ScrollableArea::horizontalSnapOffsets() const
440 {
441     if (!m_snapOffsetsInfo)
442         return nullptr;
443
444     return &m_snapOffsetsInfo->horizontalSnapOffsets;
445 }
446
447 const Vector<ScrollOffsetRange<LayoutUnit>>* ScrollableArea::horizontalSnapOffsetRanges() const
448 {
449     if (!m_snapOffsetsInfo)
450         return nullptr;
451
452     return &m_snapOffsetsInfo->horizontalSnapOffsetRanges;
453 }
454
455 const Vector<ScrollOffsetRange<LayoutUnit>>* ScrollableArea::verticalSnapOffsetRanges() const
456 {
457     if (!m_snapOffsetsInfo)
458         return nullptr;
459
460     return &m_snapOffsetsInfo->verticalSnapOffsetRanges;
461 }
462
463 const Vector<LayoutUnit>* ScrollableArea::verticalSnapOffsets() const
464 {
465     if (!m_snapOffsetsInfo)
466         return nullptr;
467
468     return &m_snapOffsetsInfo->verticalSnapOffsets;
469 }
470
471 void ScrollableArea::setHorizontalSnapOffsets(const Vector<LayoutUnit>& horizontalSnapOffsets)
472 {
473     // Consider having a non-empty set of snap offsets as a cue to initialize the ScrollAnimator.
474     if (horizontalSnapOffsets.size())
475         scrollAnimator();
476
477     ensureSnapOffsetsInfo().horizontalSnapOffsets = horizontalSnapOffsets;
478 }
479
480 void ScrollableArea::setVerticalSnapOffsets(const Vector<LayoutUnit>& verticalSnapOffsets)
481 {
482     // Consider having a non-empty set of snap offsets as a cue to initialize the ScrollAnimator.
483     if (verticalSnapOffsets.size())
484         scrollAnimator();
485
486     ensureSnapOffsetsInfo().verticalSnapOffsets = verticalSnapOffsets;
487 }
488
489 void ScrollableArea::setHorizontalSnapOffsetRanges(const Vector<ScrollOffsetRange<LayoutUnit>>& horizontalRanges)
490 {
491     ensureSnapOffsetsInfo().horizontalSnapOffsetRanges = horizontalRanges;
492 }
493
494 void ScrollableArea::setVerticalSnapOffsetRanges(const Vector<ScrollOffsetRange<LayoutUnit>>& verticalRanges)
495 {
496     ensureSnapOffsetsInfo().verticalSnapOffsetRanges = verticalRanges;
497 }
498
499 void ScrollableArea::clearHorizontalSnapOffsets()
500 {
501     if (!m_snapOffsetsInfo)
502         return;
503
504     m_snapOffsetsInfo->horizontalSnapOffsets = { };
505     m_snapOffsetsInfo->horizontalSnapOffsetRanges = { };
506     m_currentHorizontalSnapPointIndex = 0;
507 }
508
509 void ScrollableArea::clearVerticalSnapOffsets()
510 {
511     if (!m_snapOffsetsInfo)
512         return;
513
514     m_snapOffsetsInfo->verticalSnapOffsets = { };
515     m_snapOffsetsInfo->verticalSnapOffsetRanges = { };
516     m_currentVerticalSnapPointIndex = 0;
517 }
518
519 IntPoint ScrollableArea::nearestActiveSnapPoint(const IntPoint& currentPosition)
520 {
521     if (!horizontalSnapOffsets() && !verticalSnapOffsets())
522         return currentPosition;
523     
524     if (!existingScrollAnimator())
525         return currentPosition;
526     
527     IntPoint correctedPosition = currentPosition;
528     
529     if (horizontalSnapOffsets()) {
530         const auto& horizontal = *horizontalSnapOffsets();
531         
532         size_t activeIndex = currentHorizontalSnapPointIndex();
533         if (activeIndex < horizontal.size())
534             correctedPosition.setX(horizontal[activeIndex].toInt());
535     }
536     
537     if (verticalSnapOffsets()) {
538         const auto& vertical = *verticalSnapOffsets();
539         
540         size_t activeIndex = currentVerticalSnapPointIndex();
541         if (activeIndex < vertical.size())
542             correctedPosition.setY(vertical[activeIndex].toInt());
543     }
544
545     return correctedPosition;
546 }
547
548 void ScrollableArea::updateScrollSnapState()
549 {
550     if (ScrollAnimator* scrollAnimator = existingScrollAnimator())
551         scrollAnimator->updateScrollSnapState();
552
553     if (isScrollSnapInProgress())
554         return;
555
556     IntPoint currentPosition = scrollPosition();
557     IntPoint correctedPosition = nearestActiveSnapPoint(currentPosition);
558     
559     if (correctedPosition != currentPosition)
560         scrollToOffsetWithoutAnimation(correctedPosition);
561 }
562 #else
563 void ScrollableArea::updateScrollSnapState()
564 {
565 }
566 #endif
567
568
569 void ScrollableArea::serviceScrollAnimations()
570 {
571     if (ScrollAnimator* scrollAnimator = existingScrollAnimator())
572         scrollAnimator->serviceScrollAnimations();
573 }
574
575 #if PLATFORM(IOS_FAMILY)
576 bool ScrollableArea::isPinnedInBothDirections(const IntSize& scrollDelta) const
577 {
578     return isPinnedHorizontallyInDirection(scrollDelta.width()) && isPinnedVerticallyInDirection(scrollDelta.height());
579 }
580
581 bool ScrollableArea::isPinnedHorizontallyInDirection(int horizontalScrollDelta) const
582 {
583     if (horizontalScrollDelta < 0 && isHorizontalScrollerPinnedToMinimumPosition())
584         return true;
585     if (horizontalScrollDelta > 0 && isHorizontalScrollerPinnedToMaximumPosition())
586         return true;
587     return false;
588 }
589
590 bool ScrollableArea::isPinnedVerticallyInDirection(int verticalScrollDelta) const
591 {
592     if (verticalScrollDelta < 0 && isVerticalScrollerPinnedToMinimumPosition())
593         return true;
594     if (verticalScrollDelta > 0 && isVerticalScrollerPinnedToMaximumPosition())
595         return true;
596     return false;
597 }
598 #endif // PLATFORM(IOS_FAMILY)
599
600 int ScrollableArea::horizontalScrollbarIntrusion() const
601 {
602     return verticalScrollbar() ? verticalScrollbar()->occupiedWidth() : 0;
603 }
604
605 int ScrollableArea::verticalScrollbarIntrusion() const
606 {
607     return horizontalScrollbar() ? horizontalScrollbar()->occupiedHeight() : 0;
608 }
609
610 IntSize ScrollableArea::scrollbarIntrusion() const
611 {
612     return { horizontalScrollbarIntrusion(), verticalScrollbarIntrusion() };
613 }
614
615 ScrollPosition ScrollableArea::scrollPosition() const
616 {
617     // FIXME: This relationship seems to be inverted. Scrollbars should be 'view', not 'model', and should get their values from us.
618     int x = horizontalScrollbar() ? horizontalScrollbar()->value() : 0;
619     int y = verticalScrollbar() ? verticalScrollbar()->value() : 0;
620     return IntPoint(x, y);
621 }
622
623 ScrollPosition ScrollableArea::minimumScrollPosition() const
624 {
625     return scrollPositionFromOffset(ScrollPosition());
626 }
627
628 ScrollPosition ScrollableArea::maximumScrollPosition() const
629 {
630     return scrollPositionFromOffset(ScrollPosition(totalContentsSize() - visibleSize()));
631 }
632
633 ScrollOffset ScrollableArea::maximumScrollOffset() const
634 {
635     return ScrollOffset(totalContentsSize() - visibleSize());
636 }
637
638 ScrollPosition ScrollableArea::scrollPositionFromOffset(ScrollOffset offset) const
639 {
640     return scrollPositionFromOffset(offset, toIntSize(m_scrollOrigin));
641 }
642
643 ScrollOffset ScrollableArea::scrollOffsetFromPosition(ScrollPosition position) const
644 {
645     return scrollOffsetFromPosition(position, toIntSize(m_scrollOrigin));
646 }
647
648 bool ScrollableArea::scrolledToTop() const
649 {
650     return scrollPosition().y() <= minimumScrollPosition().y();
651 }
652
653 bool ScrollableArea::scrolledToBottom() const
654 {
655     return scrollPosition().y() >= maximumScrollPosition().y();
656 }
657
658 bool ScrollableArea::scrolledToLeft() const
659 {
660     return scrollPosition().x() <= minimumScrollPosition().x();
661 }
662
663 bool ScrollableArea::scrolledToRight() const
664 {
665     return scrollPosition().x() >= maximumScrollPosition().x();
666 }
667
668 void ScrollableArea::scrollbarStyleChanged(ScrollbarStyle, bool)
669 {
670     availableContentSizeChanged(AvailableSizeChangeReason::ScrollbarsChanged);
671 }
672
673 IntSize ScrollableArea::reachableTotalContentsSize() const
674 {
675     return totalContentsSize();
676 }
677
678 IntSize ScrollableArea::totalContentsSize() const
679 {
680     IntSize totalContentsSize = contentsSize();
681     totalContentsSize.setHeight(totalContentsSize.height() + headerHeight() + footerHeight());
682     return totalContentsSize;
683 }
684
685 IntRect ScrollableArea::visibleContentRect(VisibleContentRectBehavior visibleContentRectBehavior) const
686 {
687     return visibleContentRectInternal(ExcludeScrollbars, visibleContentRectBehavior);
688 }
689
690 IntRect ScrollableArea::visibleContentRectIncludingScrollbars(VisibleContentRectBehavior visibleContentRectBehavior) const
691 {
692     return visibleContentRectInternal(IncludeScrollbars, visibleContentRectBehavior);
693 }
694
695 IntRect ScrollableArea::visibleContentRectInternal(VisibleContentRectIncludesScrollbars scrollbarInclusion, VisibleContentRectBehavior) const
696 {
697     int verticalScrollbarWidth = 0;
698     int horizontalScrollbarHeight = 0;
699
700     if (scrollbarInclusion == IncludeScrollbars) {
701         if (Scrollbar* verticalBar = verticalScrollbar())
702             verticalScrollbarWidth = verticalBar->occupiedWidth();
703         if (Scrollbar* horizontalBar = horizontalScrollbar())
704             horizontalScrollbarHeight = horizontalBar->occupiedHeight();
705     }
706
707     return IntRect(scrollPosition().x(),
708                    scrollPosition().y(),
709                    std::max(0, visibleWidth() + verticalScrollbarWidth),
710                    std::max(0, visibleHeight() + horizontalScrollbarHeight));
711 }
712
713 LayoutPoint ScrollableArea::constrainScrollPositionForOverhang(const LayoutRect& visibleContentRect, const LayoutSize& totalContentsSize, const LayoutPoint& scrollPosition, const LayoutPoint& scrollOrigin, int headerHeight, int footerHeight)
714 {
715     // The viewport rect that we're scrolling shouldn't be larger than our document.
716     LayoutSize idealScrollRectSize(std::min(visibleContentRect.width(), totalContentsSize.width()), std::min(visibleContentRect.height(), totalContentsSize.height()));
717     
718     LayoutRect scrollRect(scrollPosition + scrollOrigin - LayoutSize(0, headerHeight), idealScrollRectSize);
719     LayoutRect documentRect(LayoutPoint(), LayoutSize(totalContentsSize.width(), totalContentsSize.height() - headerHeight - footerHeight));
720
721     // Use intersection to constrain our ideal scroll rect by the document rect.
722     scrollRect.intersect(documentRect);
723
724     if (scrollRect.size() != idealScrollRectSize) {
725         // If the rect was clipped, restore its size, effectively pushing it "down" from the top left.
726         scrollRect.setSize(idealScrollRectSize);
727
728         // If we still clip, push our rect "up" from the bottom right.
729         scrollRect.intersect(documentRect);
730         if (scrollRect.width() < idealScrollRectSize.width())
731             scrollRect.move(-(idealScrollRectSize.width() - scrollRect.width()), 0_lu);
732         if (scrollRect.height() < idealScrollRectSize.height())
733             scrollRect.move(0_lu, -(idealScrollRectSize.height() - scrollRect.height()));
734     }
735
736     return scrollRect.location() - toLayoutSize(scrollOrigin);
737 }
738
739 LayoutPoint ScrollableArea::constrainScrollPositionForOverhang(const LayoutPoint& scrollPosition)
740 {
741     return constrainScrollPositionForOverhang(visibleContentRect(), totalContentsSize(), scrollPosition, scrollOrigin(), headerHeight(), footerHeight());
742 }
743
744 void ScrollableArea::computeScrollbarValueAndOverhang(float currentPosition, float totalSize, float visibleSize, float& doubleValue, float& overhangAmount)
745 {
746     doubleValue = 0;
747     overhangAmount = 0;
748     float maximum = totalSize - visibleSize;
749
750     if (currentPosition < 0) {
751         // Scrolled past the top.
752         doubleValue = 0;
753         overhangAmount = -currentPosition;
754     } else if (visibleSize + currentPosition > totalSize) {
755         // Scrolled past the bottom.
756         doubleValue = 1;
757         overhangAmount = currentPosition + visibleSize - totalSize;
758     } else {
759         // Within the bounds of the scrollable area.
760         if (maximum > 0)
761             doubleValue = currentPosition / maximum;
762         else
763             doubleValue = 0;
764     }
765 }
766
767 } // namespace WebCore