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