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
46namespace WebCore {
47
48struct 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
60COMPILE_ASSERT(sizeof(ScrollableArea) == sizeof(SameSizeAsScrollableArea), ScrollableArea_should_stay_small);
61
62ScrollableArea::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
74ScrollableArea::~ScrollableArea() = default;
75
76ScrollAnimator& 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
91void ScrollableArea::setScrollOrigin(const IntPoint& origin)
92{
93 if (m_scrollOrigin != origin) {
94 m_scrollOrigin = origin;
95 m_scrollOriginChanged = true;
96 }
97}
98
99float ScrollableArea::adjustScrollStepForFixedContent(float step, ScrollbarOrientation, ScrollGranularity)
100{
101 return step;
102}
103
104bool 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
142void 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
148void 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
157void ScrollableArea::notifyScrollPositionChanged(const ScrollPosition& position)
158{
159 scrollPositionChanged(position);
160 scrollAnimator().setCurrentPosition(position);
161}
162
163void 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
196bool 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)
212bool ScrollableArea::handleTouchEvent(const PlatformTouchEvent& touchEvent)
213{
214 return scrollAnimator().handleTouchEvent(touchEvent);
215}
216#endif
217
218// NOTE: Only called from Internals for testing.
219void ScrollableArea::setScrollOffsetFromInternals(const ScrollOffset& offset)
220{
221 setScrollOffsetFromAnimation(offset);
222}
223
224void ScrollableArea::setScrollOffsetFromAnimation(const ScrollOffset& offset)
225{
226 ScrollPosition position = scrollPositionFromOffset(offset);
227 if (requestScrollPositionUpdate(position))
228 return;
229
230 scrollPositionChanged(position);
231}
232
233void ScrollableArea::willStartLiveResize()
234{
235 if (m_inLiveResize)
236 return;
237 m_inLiveResize = true;
238 if (ScrollAnimator* scrollAnimator = existingScrollAnimator())
239 scrollAnimator->willStartLiveResize();
240}
241
242void ScrollableArea::willEndLiveResize()
243{
244 if (!m_inLiveResize)
245 return;
246 m_inLiveResize = false;
247 if (ScrollAnimator* scrollAnimator = existingScrollAnimator())
248 scrollAnimator->willEndLiveResize();
249}
250
251void ScrollableArea::contentAreaWillPaint() const
252{
253 if (ScrollAnimator* scrollAnimator = existingScrollAnimator())
254 scrollAnimator->contentAreaWillPaint();
255}
256
257void ScrollableArea::mouseEnteredContentArea() const
258{
259 if (ScrollAnimator* scrollAnimator = existingScrollAnimator())
260 scrollAnimator->mouseEnteredContentArea();
261}
262
263void ScrollableArea::mouseExitedContentArea() const
264{
265 if (ScrollAnimator* scrollAnimator = existingScrollAnimator())
266 scrollAnimator->mouseExitedContentArea();
267}
268
269void ScrollableArea::mouseMovedInContentArea() const
270{
271 if (ScrollAnimator* scrollAnimator = existingScrollAnimator())
272 scrollAnimator->mouseMovedInContentArea();
273}
274
275void ScrollableArea::mouseEnteredScrollbar(Scrollbar* scrollbar) const
276{
277 scrollAnimator().mouseEnteredScrollbar(scrollbar);
278}
279
280void ScrollableArea::mouseExitedScrollbar(Scrollbar* scrollbar) const
281{
282 scrollAnimator().mouseExitedScrollbar(scrollbar);
283}
284
285void ScrollableArea::mouseIsDownInScrollbar(Scrollbar* scrollbar, bool mouseIsDown) const
286{
287 scrollAnimator().mouseIsDownInScrollbar(scrollbar, mouseIsDown);
288}
289
290void ScrollableArea::contentAreaDidShow() const
291{
292 if (ScrollAnimator* scrollAnimator = existingScrollAnimator())
293 scrollAnimator->contentAreaDidShow();
294}
295
296void ScrollableArea::contentAreaDidHide() const
297{
298 if (ScrollAnimator* scrollAnimator = existingScrollAnimator())
299 scrollAnimator->contentAreaDidHide();
300}
301
302void ScrollableArea::lockOverlayScrollbarStateToHidden(bool shouldLockState) const
303{
304 if (ScrollAnimator* scrollAnimator = existingScrollAnimator())
305 scrollAnimator->lockOverlayScrollbarStateToHidden(shouldLockState);
306}
307
308bool ScrollableArea::scrollbarsCanBeActive() const
309{
310 if (ScrollAnimator* scrollAnimator = existingScrollAnimator())
311 return scrollAnimator->scrollbarsCanBeActive();
312 return true;
313}
314
315void 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
326void ScrollableArea::willRemoveScrollbar(Scrollbar* scrollbar, ScrollbarOrientation orientation)
327{
328 if (orientation == VerticalScrollbar)
329 scrollAnimator().willRemoveVerticalScrollbar(scrollbar);
330 else
331 scrollAnimator().willRemoveHorizontalScrollbar(scrollbar);
332}
333
334void ScrollableArea::contentsResized()
335{
336 if (ScrollAnimator* scrollAnimator = existingScrollAnimator())
337 scrollAnimator->contentsResized();
338}
339
340void ScrollableArea::availableContentSizeChanged(AvailableSizeChangeReason)
341{
342 if (ScrollAnimator* scrollAnimator = existingScrollAnimator())
343 scrollAnimator->contentsResized(); // This flashes overlay scrollbars.
344}
345
346bool ScrollableArea::hasOverlayScrollbars() const
347{
348 return (verticalScrollbar() && verticalScrollbar()->isOverlayScrollbar())
349 || (horizontalScrollbar() && horizontalScrollbar()->isOverlayScrollbar());
350}
351
352void 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
371bool 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
377void 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
396void ScrollableArea::invalidateScrollCorner(const IntRect& rect)
397{
398 if (GraphicsLayer* graphicsLayer = layerForScrollCorner()) {
399 graphicsLayer->setNeedsDisplay();
400 return;
401 }
402
403 invalidateScrollCornerRect(rect);
404}
405
406void ScrollableArea::verticalScrollbarLayerDidChange()
407{
408 scrollAnimator().verticalScrollbarLayerDidChange();
409}
410
411void ScrollableArea::horizontalScrollbarLayerDidChange()
412{
413 scrollAnimator().horizontalScrollbarLayerDidChange();
414}
415
416bool ScrollableArea::hasLayerForHorizontalScrollbar() const
417{
418 return layerForHorizontalScrollbar();
419}
420
421bool ScrollableArea::hasLayerForVerticalScrollbar() const
422{
423 return layerForVerticalScrollbar();
424}
425
426bool ScrollableArea::hasLayerForScrollCorner() const
427{
428 return layerForScrollCorner();
429}
430
431#if ENABLE(CSS_SCROLL_SNAP)
432ScrollSnapOffsetsInfo<LayoutUnit>& ScrollableArea::ensureSnapOffsetsInfo()
433{
434 if (!m_snapOffsetsInfo)
435 m_snapOffsetsInfo = std::make_unique<ScrollSnapOffsetsInfo<LayoutUnit>>();
436 return *m_snapOffsetsInfo;
437}
438
439const Vector<LayoutUnit>* ScrollableArea::horizontalSnapOffsets() const
440{
441 if (!m_snapOffsetsInfo)
442 return nullptr;
443
444 return &m_snapOffsetsInfo->horizontalSnapOffsets;
445}
446
447const Vector<ScrollOffsetRange<LayoutUnit>>* ScrollableArea::horizontalSnapOffsetRanges() const
448{
449 if (!m_snapOffsetsInfo)
450 return nullptr;
451
452 return &m_snapOffsetsInfo->horizontalSnapOffsetRanges;
453}
454
455const Vector<ScrollOffsetRange<LayoutUnit>>* ScrollableArea::verticalSnapOffsetRanges() const
456{
457 if (!m_snapOffsetsInfo)
458 return nullptr;
459
460 return &m_snapOffsetsInfo->verticalSnapOffsetRanges;
461}
462
463const Vector<LayoutUnit>* ScrollableArea::verticalSnapOffsets() const
464{
465 if (!m_snapOffsetsInfo)
466 return nullptr;
467
468 return &m_snapOffsetsInfo->verticalSnapOffsets;
469}
470
471void 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
480void 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
489void ScrollableArea::setHorizontalSnapOffsetRanges(const Vector<ScrollOffsetRange<LayoutUnit>>& horizontalRanges)
490{
491 ensureSnapOffsetsInfo().horizontalSnapOffsetRanges = horizontalRanges;
492}
493
494void ScrollableArea::setVerticalSnapOffsetRanges(const Vector<ScrollOffsetRange<LayoutUnit>>& verticalRanges)
495{
496 ensureSnapOffsetsInfo().verticalSnapOffsetRanges = verticalRanges;
497}
498
499void ScrollableArea::clearHorizontalSnapOffsets()
500{
501 if (!m_snapOffsetsInfo)
502 return;
503
504 m_snapOffsetsInfo->horizontalSnapOffsets = { };
505 m_snapOffsetsInfo->horizontalSnapOffsetRanges = { };
506 m_currentHorizontalSnapPointIndex = 0;
507}
508
509void ScrollableArea::clearVerticalSnapOffsets()
510{
511 if (!m_snapOffsetsInfo)
512 return;
513
514 m_snapOffsetsInfo->verticalSnapOffsets = { };
515 m_snapOffsetsInfo->verticalSnapOffsetRanges = { };
516 m_currentVerticalSnapPointIndex = 0;
517}
518
519IntPoint 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
548void 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
563void ScrollableArea::updateScrollSnapState()
564{
565}
566#endif
567
568
569void ScrollableArea::serviceScrollAnimations()
570{
571 if (ScrollAnimator* scrollAnimator = existingScrollAnimator())
572 scrollAnimator->serviceScrollAnimations();
573}
574
575#if PLATFORM(IOS_FAMILY)
576bool ScrollableArea::isPinnedInBothDirections(const IntSize& scrollDelta) const
577{
578 return isPinnedHorizontallyInDirection(scrollDelta.width()) && isPinnedVerticallyInDirection(scrollDelta.height());
579}
580
581bool 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
590bool 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
600int ScrollableArea::horizontalScrollbarIntrusion() const
601{
602 return verticalScrollbar() ? verticalScrollbar()->occupiedWidth() : 0;
603}
604
605int ScrollableArea::verticalScrollbarIntrusion() const
606{
607 return horizontalScrollbar() ? horizontalScrollbar()->occupiedHeight() : 0;
608}
609
610IntSize ScrollableArea::scrollbarIntrusion() const
611{
612 return { horizontalScrollbarIntrusion(), verticalScrollbarIntrusion() };
613}
614
615ScrollPosition 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
623ScrollPosition ScrollableArea::minimumScrollPosition() const
624{
625 return scrollPositionFromOffset(ScrollPosition());
626}
627
628ScrollPosition ScrollableArea::maximumScrollPosition() const
629{
630 return scrollPositionFromOffset(ScrollPosition(totalContentsSize() - visibleSize()));
631}
632
633ScrollOffset ScrollableArea::maximumScrollOffset() const
634{
635 return ScrollOffset(totalContentsSize() - visibleSize());
636}
637
638ScrollPosition ScrollableArea::scrollPositionFromOffset(ScrollOffset offset) const
639{
640 return scrollPositionFromOffset(offset, toIntSize(m_scrollOrigin));
641}
642
643ScrollOffset ScrollableArea::scrollOffsetFromPosition(ScrollPosition position) const
644{
645 return scrollOffsetFromPosition(position, toIntSize(m_scrollOrigin));
646}
647
648bool ScrollableArea::scrolledToTop() const
649{
650 return scrollPosition().y() <= minimumScrollPosition().y();
651}
652
653bool ScrollableArea::scrolledToBottom() const
654{
655 return scrollPosition().y() >= maximumScrollPosition().y();
656}
657
658bool ScrollableArea::scrolledToLeft() const
659{
660 return scrollPosition().x() <= minimumScrollPosition().x();
661}
662
663bool ScrollableArea::scrolledToRight() const
664{
665 return scrollPosition().x() >= maximumScrollPosition().x();
666}
667
668void ScrollableArea::scrollbarStyleChanged(ScrollbarStyle, bool)
669{
670 availableContentSizeChanged(AvailableSizeChangeReason::ScrollbarsChanged);
671}
672
673IntSize ScrollableArea::reachableTotalContentsSize() const
674{
675 return totalContentsSize();
676}
677
678IntSize ScrollableArea::totalContentsSize() const
679{
680 IntSize totalContentsSize = contentsSize();
681 totalContentsSize.setHeight(totalContentsSize.height() + headerHeight() + footerHeight());
682 return totalContentsSize;
683}
684
685IntRect ScrollableArea::visibleContentRect(VisibleContentRectBehavior visibleContentRectBehavior) const
686{
687 return visibleContentRectInternal(ExcludeScrollbars, visibleContentRectBehavior);
688}
689
690IntRect ScrollableArea::visibleContentRectIncludingScrollbars(VisibleContentRectBehavior visibleContentRectBehavior) const
691{
692 return visibleContentRectInternal(IncludeScrollbars, visibleContentRectBehavior);
693}
694
695IntRect 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
713LayoutPoint 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
739LayoutPoint ScrollableArea::constrainScrollPositionForOverhang(const LayoutPoint& scrollPosition)
740{
741 return constrainScrollPositionForOverhang(visibleContentRect(), totalContentsSize(), scrollPosition, scrollOrigin(), headerHeight(), footerHeight());
742}
743
744void 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
768