0d6ddfb799ef665847e5a9cc7cf108bf55f3c7a4
[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 #else
50     void* pointer;
51 #endif
52     IntPoint origin;
53     unsigned bitfields : 16;
54 };
55
56 COMPILE_ASSERT(sizeof(ScrollableArea) == sizeof(SameSizeAsScrollableArea), ScrollableArea_should_stay_small);
57
58 ScrollableArea::ScrollableArea()
59     : m_constrainsScrollingToContentEdge(true)
60     , m_inLiveResize(false)
61     , m_verticalScrollElasticity(ScrollElasticityNone)
62     , m_horizontalScrollElasticity(ScrollElasticityNone)
63     , m_scrollbarOverlayStyle(ScrollbarOverlayStyleDefault)
64     , m_scrollOriginChanged(false)
65     , m_scrolledProgrammatically(false)
66 {
67 }
68
69 ScrollableArea::~ScrollableArea()
70 {
71 }
72
73 ScrollAnimator& ScrollableArea::scrollAnimator() const
74 {
75     if (!m_scrollAnimator)
76         m_scrollAnimator = ScrollAnimator::create(const_cast<ScrollableArea&>(*this));
77
78     ASSERT(m_scrollAnimator);
79     return *m_scrollAnimator.get();
80 }
81
82 void ScrollableArea::setScrollOrigin(const IntPoint& origin)
83 {
84     if (m_scrollOrigin != origin) {
85         m_scrollOrigin = origin;
86         m_scrollOriginChanged = true;
87     }
88 }
89
90 float ScrollableArea::adjustScrollStepForFixedContent(float step, ScrollbarOrientation, ScrollGranularity)
91 {
92     return step;
93 }
94
95 bool ScrollableArea::scroll(ScrollDirection direction, ScrollGranularity granularity, float multiplier)
96 {
97     ScrollbarOrientation orientation;
98     Scrollbar* scrollbar;
99     if (direction == ScrollUp || direction == ScrollDown) {
100         orientation = VerticalScrollbar;
101         scrollbar = verticalScrollbar();
102     } else {
103         orientation = HorizontalScrollbar;
104         scrollbar = horizontalScrollbar();
105     }
106
107     if (!scrollbar)
108         return false;
109
110     float step = 0;
111     switch (granularity) {
112     case ScrollByLine:
113         step = scrollbar->lineStep();
114         break;
115     case ScrollByPage:
116         step = scrollbar->pageStep();
117         break;
118     case ScrollByDocument:
119         step = scrollbar->totalSize();
120         break;
121     case ScrollByPixel:
122     case ScrollByPrecisePixel:
123         step = scrollbar->pixelStep();
124         break;
125     }
126
127     if (direction == ScrollUp || direction == ScrollLeft)
128         multiplier = -multiplier;
129
130     step = adjustScrollStepForFixedContent(step, orientation, granularity);
131     return scrollAnimator().scroll(orientation, granularity, step, multiplier);
132 }
133
134 void ScrollableArea::scrollToOffsetWithoutAnimation(const FloatPoint& offset)
135 {
136     scrollAnimator().scrollToOffsetWithoutAnimation(offset);
137 }
138
139 void ScrollableArea::scrollToOffsetWithoutAnimation(ScrollbarOrientation orientation, float offset)
140 {
141     if (orientation == HorizontalScrollbar)
142         scrollToOffsetWithoutAnimation(FloatPoint(offset, scrollAnimator().currentPosition().y()));
143     else
144         scrollToOffsetWithoutAnimation(FloatPoint(scrollAnimator().currentPosition().x(), offset));
145 }
146
147 void ScrollableArea::notifyScrollPositionChanged(const IntPoint& position)
148 {
149     scrollPositionChanged(position);
150     scrollAnimator().setCurrentPosition(position);
151 }
152
153 void ScrollableArea::scrollPositionChanged(const IntPoint& position)
154 {
155     IntPoint oldPosition = scrollPosition();
156     // Tell the derived class to scroll its contents.
157     setScrollOffset(position);
158
159     Scrollbar* verticalScrollbar = this->verticalScrollbar();
160
161     // Tell the scrollbars to update their thumb postions.
162     if (Scrollbar* horizontalScrollbar = this->horizontalScrollbar()) {
163         horizontalScrollbar->offsetDidChange();
164         if (horizontalScrollbar->isOverlayScrollbar() && !hasLayerForHorizontalScrollbar()) {
165             if (!verticalScrollbar)
166                 horizontalScrollbar->invalidate();
167             else {
168                 // If there is both a horizontalScrollbar and a verticalScrollbar,
169                 // then we must also invalidate the corner between them.
170                 IntRect boundsAndCorner = horizontalScrollbar->boundsRect();
171                 boundsAndCorner.setWidth(boundsAndCorner.width() + verticalScrollbar->width());
172                 horizontalScrollbar->invalidateRect(boundsAndCorner);
173             }
174         }
175     }
176     if (verticalScrollbar) {
177         verticalScrollbar->offsetDidChange();
178         if (verticalScrollbar->isOverlayScrollbar() && !hasLayerForVerticalScrollbar())
179             verticalScrollbar->invalidate();
180     }
181
182     if (scrollPosition() != oldPosition)
183         scrollAnimator().notifyContentAreaScrolled(scrollPosition() - oldPosition);
184 }
185
186 bool ScrollableArea::handleWheelEvent(const PlatformWheelEvent& wheelEvent)
187 {
188     if (!isScrollableOrRubberbandable())
189         return false;
190
191     return scrollAnimator().handleWheelEvent(wheelEvent);
192 }
193
194 #if ENABLE(TOUCH_EVENTS)
195 bool ScrollableArea::handleTouchEvent(const PlatformTouchEvent& touchEvent)
196 {
197     return scrollAnimator().handleTouchEvent(touchEvent);
198 }
199 #endif
200
201 // NOTE: Only called from Internals for testing.
202 void ScrollableArea::setScrollOffsetFromInternals(const IntPoint& offset)
203 {
204     setScrollOffsetFromAnimation(offset);
205 }
206
207 void ScrollableArea::setScrollOffsetFromAnimation(const IntPoint& offset)
208 {
209     if (requestScrollPositionUpdate(offset))
210         return;
211
212     scrollPositionChanged(offset);
213 }
214
215 void ScrollableArea::willStartLiveResize()
216 {
217     if (m_inLiveResize)
218         return;
219     m_inLiveResize = true;
220     if (ScrollAnimator* scrollAnimator = existingScrollAnimator())
221         scrollAnimator->willStartLiveResize();
222 }
223
224 void ScrollableArea::willEndLiveResize()
225 {
226     if (!m_inLiveResize)
227         return;
228     m_inLiveResize = false;
229     if (ScrollAnimator* scrollAnimator = existingScrollAnimator())
230         scrollAnimator->willEndLiveResize();
231 }    
232
233 void ScrollableArea::contentAreaWillPaint() const
234 {
235     if (ScrollAnimator* scrollAnimator = existingScrollAnimator())
236         scrollAnimator->contentAreaWillPaint();
237 }
238
239 void ScrollableArea::mouseEnteredContentArea() const
240 {
241     if (ScrollAnimator* scrollAnimator = existingScrollAnimator())
242         scrollAnimator->mouseEnteredContentArea();
243 }
244
245 void ScrollableArea::mouseExitedContentArea() const
246 {
247     if (ScrollAnimator* scrollAnimator = existingScrollAnimator())
248         scrollAnimator->mouseEnteredContentArea();
249 }
250
251 void ScrollableArea::mouseMovedInContentArea() const
252 {
253     if (ScrollAnimator* scrollAnimator = existingScrollAnimator())
254         scrollAnimator->mouseMovedInContentArea();
255 }
256
257 void ScrollableArea::mouseEnteredScrollbar(Scrollbar* scrollbar) const
258 {
259     scrollAnimator().mouseEnteredScrollbar(scrollbar);
260 }
261
262 void ScrollableArea::mouseExitedScrollbar(Scrollbar* scrollbar) const
263 {
264     scrollAnimator().mouseExitedScrollbar(scrollbar);
265 }
266
267 void ScrollableArea::contentAreaDidShow() const
268 {
269     if (ScrollAnimator* scrollAnimator = existingScrollAnimator())
270         scrollAnimator->contentAreaDidShow();
271 }
272
273 void ScrollableArea::contentAreaDidHide() const
274 {
275     if (ScrollAnimator* scrollAnimator = existingScrollAnimator())
276         scrollAnimator->contentAreaDidHide();
277 }
278
279 void ScrollableArea::lockOverlayScrollbarStateToHidden(bool shouldLockState) const
280 {
281     if (ScrollAnimator* scrollAnimator = existingScrollAnimator())
282         scrollAnimator->lockOverlayScrollbarStateToHidden(shouldLockState);
283 }
284
285 bool ScrollableArea::scrollbarsCanBeActive() const
286 {
287     if (ScrollAnimator* scrollAnimator = existingScrollAnimator())
288         return scrollAnimator->scrollbarsCanBeActive();
289     return true;
290 }
291
292 void ScrollableArea::didAddScrollbar(Scrollbar* scrollbar, ScrollbarOrientation orientation)
293 {
294     if (orientation == VerticalScrollbar)
295         scrollAnimator().didAddVerticalScrollbar(scrollbar);
296     else
297         scrollAnimator().didAddHorizontalScrollbar(scrollbar);
298
299     // <rdar://problem/9797253> AppKit resets the scrollbar's style when you attach a scrollbar
300     setScrollbarOverlayStyle(scrollbarOverlayStyle());
301 }
302
303 void ScrollableArea::willRemoveScrollbar(Scrollbar* scrollbar, ScrollbarOrientation orientation)
304 {
305     if (orientation == VerticalScrollbar)
306         scrollAnimator().willRemoveVerticalScrollbar(scrollbar);
307     else
308         scrollAnimator().willRemoveHorizontalScrollbar(scrollbar);
309 }
310
311 void ScrollableArea::contentsResized()
312 {
313     if (ScrollAnimator* scrollAnimator = existingScrollAnimator())
314         scrollAnimator->contentsResized();
315 }
316
317 bool ScrollableArea::hasOverlayScrollbars() const
318 {
319     return (verticalScrollbar() && verticalScrollbar()->isOverlayScrollbar())
320         || (horizontalScrollbar() && horizontalScrollbar()->isOverlayScrollbar());
321 }
322
323 void ScrollableArea::setScrollbarOverlayStyle(ScrollbarOverlayStyle overlayStyle)
324 {
325     m_scrollbarOverlayStyle = overlayStyle;
326
327     if (horizontalScrollbar()) {
328         ScrollbarTheme::theme()->updateScrollbarOverlayStyle(horizontalScrollbar());
329         horizontalScrollbar()->invalidate();
330     }
331     
332     if (verticalScrollbar()) {
333         ScrollbarTheme::theme()->updateScrollbarOverlayStyle(verticalScrollbar());
334         verticalScrollbar()->invalidate();
335     }
336 }
337
338 void ScrollableArea::invalidateScrollbar(Scrollbar* scrollbar, const IntRect& rect)
339 {
340     if (scrollbar == horizontalScrollbar()) {
341         if (GraphicsLayer* graphicsLayer = layerForHorizontalScrollbar()) {
342             graphicsLayer->setNeedsDisplay();
343             graphicsLayer->setContentsNeedsDisplay();
344             return;
345         }
346     } else if (scrollbar == verticalScrollbar()) {
347         if (GraphicsLayer* graphicsLayer = layerForVerticalScrollbar()) {
348             graphicsLayer->setNeedsDisplay();
349             graphicsLayer->setContentsNeedsDisplay();
350             return;
351         }
352     }
353
354     invalidateScrollbarRect(scrollbar, rect);
355 }
356
357 void ScrollableArea::invalidateScrollCorner(const IntRect& rect)
358 {
359     if (GraphicsLayer* graphicsLayer = layerForScrollCorner()) {
360         graphicsLayer->setNeedsDisplay();
361         return;
362     }
363
364     invalidateScrollCornerRect(rect);
365 }
366
367 void ScrollableArea::verticalScrollbarLayerDidChange()
368 {
369     scrollAnimator().verticalScrollbarLayerDidChange();
370 }
371
372 void ScrollableArea::horizontalScrollbarLayerDidChange()
373 {
374     scrollAnimator().horizontalScrollbarLayerDidChange();
375 }
376
377 bool ScrollableArea::hasLayerForHorizontalScrollbar() const
378 {
379     return layerForHorizontalScrollbar();
380 }
381
382 bool ScrollableArea::hasLayerForVerticalScrollbar() const
383 {
384     return layerForVerticalScrollbar();
385 }
386
387 bool ScrollableArea::hasLayerForScrollCorner() const
388 {
389     return layerForScrollCorner();
390 }
391
392 #if ENABLE(CSS_SCROLL_SNAP)
393 void ScrollableArea::setHorizontalSnapOffsets(std::unique_ptr<Vector<LayoutUnit>> horizontalSnapOffsets)
394 {
395     m_horizontalSnapOffsets = WTF::move(horizontalSnapOffsets);
396 }
397
398 void ScrollableArea::setVerticalSnapOffsets(std::unique_ptr<Vector<LayoutUnit>> verticalSnapOffsets)
399 {
400     m_verticalSnapOffsets = WTF::move(verticalSnapOffsets);
401 }
402
403 void ScrollableArea::clearHorizontalSnapOffsets()
404 {
405     m_horizontalSnapOffsets = nullptr;
406 }
407
408 void ScrollableArea::clearVerticalSnapOffsets()
409 {
410     m_verticalSnapOffsets = nullptr;
411 }
412 #endif
413
414 void ScrollableArea::serviceScrollAnimations()
415 {
416     if (ScrollAnimator* scrollAnimator = existingScrollAnimator())
417         scrollAnimator->serviceScrollAnimations();
418 }
419
420 #if PLATFORM(IOS)
421 bool ScrollableArea::isPinnedInBothDirections(const IntSize& scrollDelta) const
422 {
423     return isPinnedHorizontallyInDirection(scrollDelta.width()) && isPinnedVerticallyInDirection(scrollDelta.height());
424 }
425
426 bool ScrollableArea::isPinnedHorizontallyInDirection(int horizontalScrollDelta) const
427 {
428     if (horizontalScrollDelta < 0 && isHorizontalScrollerPinnedToMinimumPosition())
429         return true;
430     if (horizontalScrollDelta > 0 && isHorizontalScrollerPinnedToMaximumPosition())
431         return true;
432     return false;
433 }
434
435 bool ScrollableArea::isPinnedVerticallyInDirection(int verticalScrollDelta) const
436 {
437     if (verticalScrollDelta < 0 && isVerticalScrollerPinnedToMinimumPosition())
438         return true;
439     if (verticalScrollDelta > 0 && isVerticalScrollerPinnedToMaximumPosition())
440         return true;
441     return false;
442 }
443 #endif // PLATFORM(IOS)
444
445 IntPoint ScrollableArea::scrollPosition() const
446 {
447     int x = horizontalScrollbar() ? horizontalScrollbar()->value() : 0;
448     int y = verticalScrollbar() ? verticalScrollbar()->value() : 0;
449     return IntPoint(x, y);
450 }
451
452 IntPoint ScrollableArea::minimumScrollPosition() const
453 {
454     return IntPoint();
455 }
456
457 IntPoint ScrollableArea::maximumScrollPosition() const
458 {
459     return IntPoint(totalContentsSize().width() - visibleWidth(), totalContentsSize().height() - visibleHeight());
460 }
461
462 bool ScrollableArea::scrolledToTop() const
463 {
464     return scrollPosition().y() <= minimumScrollPosition().y();
465 }
466
467 bool ScrollableArea::scrolledToBottom() const
468 {
469     return scrollPosition().y() >= maximumScrollPosition().y();
470 }
471
472 bool ScrollableArea::scrolledToLeft() const
473 {
474     return scrollPosition().x() <= minimumScrollPosition().x();
475 }
476
477 bool ScrollableArea::scrolledToRight() const
478 {
479     return scrollPosition().x() >= maximumScrollPosition().x();
480 }
481
482 IntSize ScrollableArea::totalContentsSize() const
483 {
484     IntSize totalContentsSize = contentsSize();
485     totalContentsSize.setHeight(totalContentsSize.height() + headerHeight() + footerHeight());
486     return totalContentsSize;
487 }
488
489 IntRect ScrollableArea::visibleContentRect(VisibleContentRectBehavior visibleContentRectBehavior) const
490 {
491     return visibleContentRectInternal(ExcludeScrollbars, visibleContentRectBehavior);
492 }
493
494 IntRect ScrollableArea::visibleContentRectIncludingScrollbars(VisibleContentRectBehavior visibleContentRectBehavior) const
495 {
496     return visibleContentRectInternal(IncludeScrollbars, visibleContentRectBehavior);
497 }
498
499 IntRect ScrollableArea::visibleContentRectInternal(VisibleContentRectIncludesScrollbars scrollbarInclusion, VisibleContentRectBehavior) const
500 {
501     int verticalScrollbarWidth = 0;
502     int horizontalScrollbarHeight = 0;
503
504     if (scrollbarInclusion == IncludeScrollbars) {
505         if (Scrollbar* verticalBar = verticalScrollbar())
506             verticalScrollbarWidth = !verticalBar->isOverlayScrollbar() ? verticalBar->width() : 0;
507         if (Scrollbar* horizontalBar = horizontalScrollbar())
508             horizontalScrollbarHeight = !horizontalBar->isOverlayScrollbar() ? horizontalBar->height() : 0;
509     }
510
511     return IntRect(scrollPosition().x(),
512                    scrollPosition().y(),
513                    std::max(0, visibleWidth() + verticalScrollbarWidth),
514                    std::max(0, visibleHeight() + horizontalScrollbarHeight));
515 }
516
517 LayoutPoint ScrollableArea::constrainScrollPositionForOverhang(const LayoutRect& visibleContentRect, const LayoutSize& totalContentsSize, const LayoutPoint& scrollPosition, const LayoutPoint& scrollOrigin, int headerHeight, int footerHeight)
518 {
519     // The viewport rect that we're scrolling shouldn't be larger than our document.
520     LayoutSize idealScrollRectSize(std::min(visibleContentRect.width(), totalContentsSize.width()), std::min(visibleContentRect.height(), totalContentsSize.height()));
521     
522     LayoutRect scrollRect(scrollPosition + scrollOrigin - LayoutSize(0, headerHeight), idealScrollRectSize);
523     LayoutRect documentRect(LayoutPoint(), LayoutSize(totalContentsSize.width(), totalContentsSize.height() - headerHeight - footerHeight));
524
525     // Use intersection to constrain our ideal scroll rect by the document rect.
526     scrollRect.intersect(documentRect);
527
528     if (scrollRect.size() != idealScrollRectSize) {
529         // If the rect was clipped, restore its size, effectively pushing it "down" from the top left.
530         scrollRect.setSize(idealScrollRectSize);
531
532         // If we still clip, push our rect "up" from the bottom right.
533         scrollRect.intersect(documentRect);
534         if (scrollRect.width() < idealScrollRectSize.width())
535             scrollRect.move(-(idealScrollRectSize.width() - scrollRect.width()), 0);
536         if (scrollRect.height() < idealScrollRectSize.height())
537             scrollRect.move(0, -(idealScrollRectSize.height() - scrollRect.height()));
538     }
539
540     return scrollRect.location() - toLayoutSize(scrollOrigin);
541 }
542
543 LayoutPoint ScrollableArea::constrainScrollPositionForOverhang(const LayoutPoint& scrollPosition)
544 {
545     return constrainScrollPositionForOverhang(visibleContentRect(), totalContentsSize(), scrollPosition, scrollOrigin(), headerHeight(), footerHeight());
546 }
547
548 void ScrollableArea::computeScrollbarValueAndOverhang(float currentPosition, float totalSize, float visibleSize, float& doubleValue, float& overhangAmount)
549 {
550     doubleValue = 0;
551     overhangAmount = 0;
552     float maximum = totalSize - visibleSize;
553
554     if (currentPosition < 0) {
555         // Scrolled past the top.
556         doubleValue = 0;
557         overhangAmount = -currentPosition;
558     } else if (visibleSize + currentPosition > totalSize) {
559         // Scrolled past the bottom.
560         doubleValue = 1;
561         overhangAmount = currentPosition + visibleSize - totalSize;
562     } else {
563         // Within the bounds of the scrollable area.
564         if (maximum > 0)
565             doubleValue = currentPosition / maximum;
566         else
567             doubleValue = 0;
568     }
569 }
570
571 } // namespace WebCore