1/*
2 * Copyright (C) 2006, 2007, 2008 Apple Inc. All rights reserved.
3 *
4 * Redistribution and use in source and binary forms, with or without
5 * modification, are permitted provided that the following conditions
6 * are met:
7 * 1. Redistributions of source code must retain the above copyright
8 *    notice, this list of conditions and the following disclaimer.
9 * 2. Redistributions in binary form must reproduce the above copyright
10 *    notice, this list of conditions and the following disclaimer in the
11 *    documentation and/or other materials provided with the distribution.
12 *
13 * THIS SOFTWARE IS PROVIDED BY APPLE COMPUTER, INC. ``AS IS'' AND ANY
14 * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
15 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
16 * PURPOSE ARE DISCLAIMED.  IN NO EVENT SHALL APPLE COMPUTER, INC. OR
17 * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
18 * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
19 * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
20 * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY
21 * OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
22 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
23 * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
24 */
25
26#include "config.h"
27#include "platform/scroll/ScrollView.h"
28
29#include "platform/graphics/GraphicsContextStateSaver.h"
30#include "platform/graphics/GraphicsLayer.h"
31#include "platform/HostWindow.h"
32#include "platform/scroll/ScrollbarTheme.h"
33#include "wtf/StdLibExtras.h"
34
35namespace blink {
36
37ScrollView::ScrollView()
38    : m_horizontalScrollbarMode(ScrollbarAuto)
39    , m_verticalScrollbarMode(ScrollbarAuto)
40    , m_horizontalScrollbarLock(false)
41    , m_verticalScrollbarLock(false)
42    , m_scrollbarsAvoidingResizer(0)
43    , m_scrollbarsSuppressed(false)
44    , m_inUpdateScrollbars(false)
45    , m_drawPanScrollIcon(false)
46    , m_clipsRepaints(true)
47{
48}
49
50ScrollView::~ScrollView()
51{
52}
53
54void ScrollView::addChild(PassRefPtr<Widget> prpChild)
55{
56    Widget* child = prpChild.get();
57    ASSERT(child != this && !child->parent());
58    child->setParent(this);
59    m_children.add(prpChild);
60}
61
62void ScrollView::removeChild(Widget* child)
63{
64    ASSERT(child->parent() == this);
65    child->setParent(0);
66    m_children.remove(child);
67}
68
69void ScrollView::setHasHorizontalScrollbar(bool hasBar)
70{
71    if (hasBar && !m_horizontalScrollbar) {
72        m_horizontalScrollbar = createScrollbar(HorizontalScrollbar);
73        addChild(m_horizontalScrollbar.get());
74        didAddScrollbar(m_horizontalScrollbar.get(), HorizontalScrollbar);
75        m_horizontalScrollbar->styleChanged();
76    } else if (!hasBar && m_horizontalScrollbar) {
77        willRemoveScrollbar(m_horizontalScrollbar.get(), HorizontalScrollbar);
78        removeChild(m_horizontalScrollbar.get());
79        m_horizontalScrollbar = nullptr;
80    }
81}
82
83void ScrollView::setHasVerticalScrollbar(bool hasBar)
84{
85    if (hasBar && !m_verticalScrollbar) {
86        m_verticalScrollbar = createScrollbar(VerticalScrollbar);
87        addChild(m_verticalScrollbar.get());
88        didAddScrollbar(m_verticalScrollbar.get(), VerticalScrollbar);
89        m_verticalScrollbar->styleChanged();
90    } else if (!hasBar && m_verticalScrollbar) {
91        willRemoveScrollbar(m_verticalScrollbar.get(), VerticalScrollbar);
92        removeChild(m_verticalScrollbar.get());
93        m_verticalScrollbar = nullptr;
94    }
95}
96
97PassRefPtr<Scrollbar> ScrollView::createScrollbar(ScrollbarOrientation orientation)
98{
99    return Scrollbar::create(this, orientation, RegularScrollbar);
100}
101
102void ScrollView::setScrollbarModes(ScrollbarMode horizontalMode, ScrollbarMode verticalMode,
103                                   bool horizontalLock, bool verticalLock)
104{
105    bool needsUpdate = false;
106
107    if (horizontalMode != horizontalScrollbarMode() && !m_horizontalScrollbarLock) {
108        m_horizontalScrollbarMode = horizontalMode;
109        needsUpdate = true;
110    }
111
112    if (verticalMode != verticalScrollbarMode() && !m_verticalScrollbarLock) {
113        m_verticalScrollbarMode = verticalMode;
114        needsUpdate = true;
115    }
116
117    if (horizontalLock)
118        setHorizontalScrollbarLock();
119
120    if (verticalLock)
121        setVerticalScrollbarLock();
122
123    if (!needsUpdate)
124        return;
125
126    updateScrollbars(scrollOffset());
127
128    if (!layerForScrolling())
129        return;
130    blink::WebLayer* layer = layerForScrolling()->platformLayer();
131    if (!layer)
132        return;
133    layer->setUserScrollable(userInputScrollable(HorizontalScrollbar), userInputScrollable(VerticalScrollbar));
134}
135
136void ScrollView::scrollbarModes(ScrollbarMode& horizontalMode, ScrollbarMode& verticalMode) const
137{
138    horizontalMode = m_horizontalScrollbarMode;
139    verticalMode = m_verticalScrollbarMode;
140}
141
142void ScrollView::setCanHaveScrollbars(bool canScroll)
143{
144    ScrollbarMode newHorizontalMode;
145    ScrollbarMode newVerticalMode;
146
147    scrollbarModes(newHorizontalMode, newVerticalMode);
148
149    if (canScroll && newVerticalMode == ScrollbarAlwaysOff)
150        newVerticalMode = ScrollbarAuto;
151    else if (!canScroll)
152        newVerticalMode = ScrollbarAlwaysOff;
153
154    if (canScroll && newHorizontalMode == ScrollbarAlwaysOff)
155        newHorizontalMode = ScrollbarAuto;
156    else if (!canScroll)
157        newHorizontalMode = ScrollbarAlwaysOff;
158
159    setScrollbarModes(newHorizontalMode, newVerticalMode);
160}
161
162void ScrollView::setClipsRepaints(bool clipsRepaints)
163{
164    m_clipsRepaints = clipsRepaints;
165}
166
167IntSize ScrollView::unscaledVisibleContentSize(IncludeScrollbarsInRect scrollbarInclusion) const
168{
169    return scrollbarInclusion == ExcludeScrollbars ? excludeScrollbars(frameRect().size()) : frameRect().size();
170}
171
172IntSize ScrollView::excludeScrollbars(const IntSize& size) const
173{
174    int verticalScrollbarWidth = 0;
175    int horizontalScrollbarHeight = 0;
176
177    if (Scrollbar* verticalBar = verticalScrollbar())
178        verticalScrollbarWidth = !verticalBar->isOverlayScrollbar() ? verticalBar->width() : 0;
179    if (Scrollbar* horizontalBar = horizontalScrollbar())
180        horizontalScrollbarHeight = !horizontalBar->isOverlayScrollbar() ? horizontalBar->height() : 0;
181
182    return IntSize(std::max(0, size.width() - verticalScrollbarWidth),
183        std::max(0, size.height() - horizontalScrollbarHeight));
184
185}
186
187IntRect ScrollView::visibleContentRect(IncludeScrollbarsInRect scollbarInclusion) const
188{
189    FloatSize visibleContentSize = unscaledVisibleContentSize(scollbarInclusion);
190    visibleContentSize.scale(1 / visibleContentScaleFactor());
191    return IntRect(IntPoint(m_scrollOffset), expandedIntSize(visibleContentSize));
192}
193
194IntSize ScrollView::contentsSize() const
195{
196    return m_contentsSize;
197}
198
199void ScrollView::setContentsSize(const IntSize& newSize)
200{
201    if (contentsSize() == newSize)
202        return;
203    m_contentsSize = newSize;
204    updateScrollbars(scrollOffset());
205    updateOverhangAreas();
206}
207
208IntPoint ScrollView::maximumScrollPosition() const
209{
210    IntPoint maximumOffset(contentsWidth() - visibleWidth() - scrollOrigin().x(), contentsHeight() - visibleHeight() - scrollOrigin().y());
211    maximumOffset.clampNegativeToZero();
212    return maximumOffset;
213}
214
215IntPoint ScrollView::minimumScrollPosition() const
216{
217    return IntPoint(-scrollOrigin().x(), -scrollOrigin().y());
218}
219
220IntPoint ScrollView::adjustScrollPositionWithinRange(const IntPoint& scrollPoint) const
221{
222    if (!constrainsScrollingToContentEdge())
223        return scrollPoint;
224
225    IntPoint newScrollPosition = scrollPoint.shrunkTo(maximumScrollPosition());
226    newScrollPosition = newScrollPosition.expandedTo(minimumScrollPosition());
227    return newScrollPosition;
228}
229
230void ScrollView::adjustScrollbarOpacity()
231{
232    if (m_horizontalScrollbar && layerForHorizontalScrollbar()) {
233        bool isOpaqueScrollbar = !m_horizontalScrollbar->isOverlayScrollbar();
234        layerForHorizontalScrollbar()->setContentsOpaque(isOpaqueScrollbar);
235    }
236    if (m_verticalScrollbar && layerForVerticalScrollbar()) {
237        bool isOpaqueScrollbar = !m_verticalScrollbar->isOverlayScrollbar();
238        layerForVerticalScrollbar()->setContentsOpaque(isOpaqueScrollbar);
239    }
240}
241
242int ScrollView::scrollSize(ScrollbarOrientation orientation) const
243{
244    Scrollbar* scrollbar = ((orientation == HorizontalScrollbar) ? m_horizontalScrollbar : m_verticalScrollbar).get();
245
246    // If no scrollbars are present, the content may still be scrollable.
247    if (!scrollbar) {
248        IntSize scrollSize = m_contentsSize - visibleContentRect().size();
249        scrollSize.clampNegativeToZero();
250        return orientation == HorizontalScrollbar ? scrollSize.width() : scrollSize.height();
251    }
252
253    return scrollbar->totalSize() - scrollbar->visibleSize();
254}
255
256void ScrollView::notifyPageThatContentAreaWillPaint() const
257{
258}
259
260void ScrollView::setScrollOffset(const IntPoint& offset)
261{
262    scrollTo(toIntSize(adjustScrollPositionWithinRange(offset)));
263}
264
265void ScrollView::scrollTo(const IntSize& newOffset)
266{
267    IntSize scrollDelta = newOffset - m_scrollOffset;
268    if (scrollDelta == IntSize())
269        return;
270    m_scrollOffset = newOffset;
271
272    if (scrollbarsSuppressed())
273        return;
274
275    if (isFrameView())
276        m_pendingScrollDelta += scrollDelta;
277    else
278        scrollContents(scrollDelta);
279}
280
281void ScrollView::setScrollPosition(const IntPoint& scrollPoint, ScrollBehavior scrollBehavior)
282{
283    IntPoint newScrollPosition = adjustScrollPositionWithinRange(scrollPoint);
284
285    if (newScrollPosition == scrollPosition())
286        return;
287
288    if (scrollBehavior == ScrollBehaviorInstant)
289        updateScrollbars(IntSize(newScrollPosition.x(), newScrollPosition.y()));
290    else
291        programmaticallyScrollSmoothlyToOffset(newScrollPosition);
292}
293
294bool ScrollView::scroll(ScrollDirection direction, ScrollGranularity granularity)
295{
296    ScrollDirection physicalDirection =
297        toPhysicalDirection(direction, isVerticalDocument(), isFlippedDocument());
298
299    return ScrollableArea::scroll(physicalDirection, granularity);
300}
301
302IntSize ScrollView::overhangAmount() const
303{
304    IntSize stretch;
305
306    IntPoint currentScrollPosition = scrollPosition();
307    IntPoint minScrollPosition = minimumScrollPosition();
308    IntPoint maxScrollPosition = maximumScrollPosition();
309
310    if (currentScrollPosition.x() < minScrollPosition.x())
311        stretch.setWidth(currentScrollPosition.x() - minScrollPosition.x());
312    if (currentScrollPosition.x() > maxScrollPosition.x())
313        stretch.setWidth(currentScrollPosition.x() - maxScrollPosition.x());
314
315    if (currentScrollPosition.y() < minScrollPosition.y())
316        stretch.setHeight(currentScrollPosition.y() - minScrollPosition.y());
317    if (currentScrollPosition.y() > maxScrollPosition.y())
318        stretch.setHeight(currentScrollPosition.y() - maxScrollPosition.y());
319
320    return stretch;
321}
322
323void ScrollView::windowResizerRectChanged()
324{
325    updateScrollbars(scrollOffset());
326}
327
328static bool useOverlayScrollbars()
329{
330    // FIXME: Need to detect the presence of CSS custom scrollbars, which are non-overlay regardless the ScrollbarTheme.
331    return ScrollbarTheme::theme()->usesOverlayScrollbars();
332}
333
334void ScrollView::computeScrollbarExistence(bool& newHasHorizontalScrollbar, bool& newHasVerticalScrollbar, const IntSize& docSize, ComputeScrollbarExistenceOption option) const
335{
336    bool hasHorizontalScrollbar = m_horizontalScrollbar;
337    bool hasVerticalScrollbar = m_verticalScrollbar;
338
339    newHasHorizontalScrollbar = hasHorizontalScrollbar;
340    newHasVerticalScrollbar = hasVerticalScrollbar;
341
342    ScrollbarMode hScroll = m_horizontalScrollbarMode;
343    ScrollbarMode vScroll = m_verticalScrollbarMode;
344
345    if (hScroll != ScrollbarAuto)
346        newHasHorizontalScrollbar = (hScroll == ScrollbarAlwaysOn);
347    if (vScroll != ScrollbarAuto)
348        newHasVerticalScrollbar = (vScroll == ScrollbarAlwaysOn);
349
350    if (m_scrollbarsSuppressed || (hScroll != ScrollbarAuto && vScroll != ScrollbarAuto))
351        return;
352
353    if (hScroll == ScrollbarAuto)
354        newHasHorizontalScrollbar = docSize.width() > visibleWidth();
355    if (vScroll == ScrollbarAuto)
356        newHasVerticalScrollbar = docSize.height() > visibleHeight();
357
358    if (useOverlayScrollbars())
359        return;
360
361    IntSize fullVisibleSize = visibleContentRect(IncludeScrollbars).size();
362
363    bool attemptToRemoveScrollbars = (option == FirstPass
364        && docSize.width() <= fullVisibleSize.width() && docSize.height() <= fullVisibleSize.height());
365    if (attemptToRemoveScrollbars) {
366        if (hScroll == ScrollbarAuto)
367            newHasHorizontalScrollbar = false;
368        if (vScroll == ScrollbarAuto)
369            newHasVerticalScrollbar = false;
370    }
371
372    // If we ever turn one scrollbar off, always turn the other one off too.
373    // Never ever try to both gain/lose a scrollbar in the same pass.
374    if (!newHasHorizontalScrollbar && hasHorizontalScrollbar && vScroll != ScrollbarAlwaysOn)
375        newHasVerticalScrollbar = false;
376    if (!newHasVerticalScrollbar && hasVerticalScrollbar && hScroll != ScrollbarAlwaysOn)
377        newHasHorizontalScrollbar = false;
378}
379
380void ScrollView::updateScrollbarGeometry()
381{
382    if (m_horizontalScrollbar) {
383        int clientWidth = visibleWidth();
384        IntRect oldRect(m_horizontalScrollbar->frameRect());
385        IntRect hBarRect((shouldPlaceVerticalScrollbarOnLeft() && m_verticalScrollbar) ? m_verticalScrollbar->width() : 0,
386                        height() - m_horizontalScrollbar->height(),
387                        width() - (m_verticalScrollbar ? m_verticalScrollbar->width() : 0),
388                        m_horizontalScrollbar->height());
389        m_horizontalScrollbar->setFrameRect(hBarRect);
390        if (!m_scrollbarsSuppressed && oldRect != m_horizontalScrollbar->frameRect())
391            m_horizontalScrollbar->invalidate();
392
393        if (m_scrollbarsSuppressed)
394            m_horizontalScrollbar->setSuppressInvalidation(true);
395        m_horizontalScrollbar->setEnabled(contentsWidth() > clientWidth);
396        m_horizontalScrollbar->setProportion(clientWidth, contentsWidth());
397        m_horizontalScrollbar->offsetDidChange();
398        if (m_scrollbarsSuppressed)
399            m_horizontalScrollbar->setSuppressInvalidation(false);
400    }
401
402    if (m_verticalScrollbar) {
403        int clientHeight = visibleHeight();
404        IntRect oldRect(m_verticalScrollbar->frameRect());
405        IntRect vBarRect(shouldPlaceVerticalScrollbarOnLeft() ? 0 : (width() - m_verticalScrollbar->width()),
406                         0,
407                         m_verticalScrollbar->width(),
408                         height() - (m_horizontalScrollbar ? m_horizontalScrollbar->height() : 0));
409        m_verticalScrollbar->setFrameRect(vBarRect);
410        if (!m_scrollbarsSuppressed && oldRect != m_verticalScrollbar->frameRect())
411            m_verticalScrollbar->invalidate();
412
413        if (m_scrollbarsSuppressed)
414            m_verticalScrollbar->setSuppressInvalidation(true);
415        m_verticalScrollbar->setEnabled(contentsHeight() > clientHeight);
416        m_verticalScrollbar->setProportion(clientHeight, contentsHeight());
417        m_verticalScrollbar->offsetDidChange();
418        if (m_scrollbarsSuppressed)
419            m_verticalScrollbar->setSuppressInvalidation(false);
420    }
421}
422
423bool ScrollView::adjustScrollbarExistence(ComputeScrollbarExistenceOption option)
424{
425    ASSERT(m_inUpdateScrollbars);
426
427    // If we came in here with the view already needing a layout, then go ahead and do that
428    // first.  (This will be the common case, e.g., when the page changes due to window resizing for example).
429    // This layout will not re-enter updateScrollbars and does not count towards our max layout pass total.
430    if (!m_scrollbarsSuppressed)
431        scrollbarExistenceDidChange();
432
433    bool hasHorizontalScrollbar = m_horizontalScrollbar;
434    bool hasVerticalScrollbar = m_verticalScrollbar;
435
436    bool newHasHorizontalScrollbar = false;
437    bool newHasVerticalScrollbar = false;
438    computeScrollbarExistence(newHasHorizontalScrollbar, newHasVerticalScrollbar, contentsSize(), option);
439
440    bool scrollbarExistenceChanged = hasHorizontalScrollbar != newHasHorizontalScrollbar || hasVerticalScrollbar != newHasVerticalScrollbar;
441    if (!scrollbarExistenceChanged)
442        return false;
443
444    setHasHorizontalScrollbar(newHasHorizontalScrollbar);
445    setHasVerticalScrollbar(newHasVerticalScrollbar);
446
447    if (m_scrollbarsSuppressed)
448        return true;
449
450    if (!useOverlayScrollbars())
451        contentsResized();
452    scrollbarExistenceDidChange();
453    return true;
454}
455
456void ScrollView::updateScrollbars(const IntSize& desiredOffset)
457{
458    if (scrollbarsDisabled()) {
459        setScrollOffsetFromUpdateScrollbars(desiredOffset);
460        return;
461    }
462
463    if (m_inUpdateScrollbars)
464        return;
465    InUpdateScrollbarsScope inUpdateScrollbarsScope(this);
466
467    IntSize oldVisibleSize = visibleSize();
468
469    bool scrollbarExistenceChanged = false;
470    int maxUpdateScrollbarsPass = useOverlayScrollbars() || m_scrollbarsSuppressed ? 1 : 3;
471    for (int updateScrollbarsPass = 0; updateScrollbarsPass < maxUpdateScrollbarsPass; updateScrollbarsPass++) {
472        if (!adjustScrollbarExistence(updateScrollbarsPass ? Incremental : FirstPass))
473            break;
474        scrollbarExistenceChanged = true;
475    }
476
477    updateScrollbarGeometry();
478
479    if (scrollbarExistenceChanged) {
480        // FIXME: Is frameRectsChanged really necessary here? Have any frame rects changed?
481        frameRectsChanged();
482        positionScrollbarLayers();
483        updateScrollCorner();
484    }
485
486    // FIXME: We don't need to do this if we are composited.
487    IntSize newVisibleSize = visibleSize();
488    if (newVisibleSize.width() > oldVisibleSize.width()) {
489        if (shouldPlaceVerticalScrollbarOnLeft())
490            invalidateRect(IntRect(0, 0, newVisibleSize.width() - oldVisibleSize.width(), newVisibleSize.height()));
491        else
492            invalidateRect(IntRect(oldVisibleSize.width(), 0, newVisibleSize.width() - oldVisibleSize.width(), newVisibleSize.height()));
493    }
494    if (newVisibleSize.height() > oldVisibleSize.height())
495        invalidateRect(IntRect(0, oldVisibleSize.height(), newVisibleSize.width(), newVisibleSize.height() - oldVisibleSize.height()));
496
497    setScrollOffsetFromUpdateScrollbars(desiredOffset);
498}
499
500void ScrollView::setScrollOffsetFromUpdateScrollbars(const IntSize& offset)
501{
502    IntPoint adjustedScrollPosition = IntPoint(offset);
503
504    if (!isRubberBandInProgress())
505        adjustedScrollPosition = adjustScrollPositionWithinRange(adjustedScrollPosition);
506
507    if (adjustedScrollPosition != scrollPosition() || scrollOriginChanged()) {
508        ScrollableArea::scrollToOffsetWithoutAnimation(adjustedScrollPosition);
509        resetScrollOriginChanged();
510    }
511}
512
513const int panIconSizeLength = 16;
514
515IntRect ScrollView::rectToCopyOnScroll() const
516{
517    IntRect scrollViewRect = convertToContainingWindow(IntRect((shouldPlaceVerticalScrollbarOnLeft() && verticalScrollbar()) ? verticalScrollbar()->width() : 0, 0, visibleWidth(), visibleHeight()));
518    if (hasOverlayScrollbars()) {
519        int verticalScrollbarWidth = (verticalScrollbar() && !hasLayerForVerticalScrollbar()) ? verticalScrollbar()->width() : 0;
520        int horizontalScrollbarHeight = (horizontalScrollbar() && !hasLayerForHorizontalScrollbar()) ? horizontalScrollbar()->height() : 0;
521
522        scrollViewRect.setWidth(scrollViewRect.width() - verticalScrollbarWidth);
523        scrollViewRect.setHeight(scrollViewRect.height() - horizontalScrollbarHeight);
524    }
525    return scrollViewRect;
526}
527
528void ScrollView::scrollContentsIfNeeded()
529{
530    if (m_pendingScrollDelta.isZero())
531        return;
532    IntSize scrollDelta = m_pendingScrollDelta;
533    m_pendingScrollDelta = IntSize();
534    scrollContents(scrollDelta);
535}
536
537void ScrollView::scrollContents(const IntSize& scrollDelta)
538{
539    HostWindow* window = hostWindow();
540    if (!window)
541        return;
542
543    IntRect clipRect = windowClipRect();
544    IntRect updateRect = clipRect;
545    updateRect.intersect(rectToCopyOnScroll());
546
547    if (m_drawPanScrollIcon) {
548        // FIXME: the pan icon is broken when accelerated compositing is on, since it will draw under the compositing layers.
549        // https://bugs.webkit.org/show_bug.cgi?id=47837
550        int panIconDirtySquareSizeLength = 2 * (panIconSizeLength + std::max(abs(scrollDelta.width()), abs(scrollDelta.height()))); // We only want to repaint what's necessary
551        IntPoint panIconDirtySquareLocation = IntPoint(m_panScrollIconPoint.x() - (panIconDirtySquareSizeLength / 2), m_panScrollIconPoint.y() - (panIconDirtySquareSizeLength / 2));
552        IntRect panScrollIconDirtyRect = IntRect(panIconDirtySquareLocation, IntSize(panIconDirtySquareSizeLength, panIconDirtySquareSizeLength));
553        panScrollIconDirtyRect.intersect(clipRect);
554        window->invalidateContentsAndRootView(panScrollIconDirtyRect);
555    }
556
557    if (!scrollContentsFastPath(-scrollDelta))
558        scrollContentsSlowPath(updateRect);
559
560    // Invalidate the overhang areas if they are visible.
561    updateOverhangAreas();
562
563    // This call will move children with native widgets (plugins) and invalidate them as well.
564    frameRectsChanged();
565}
566
567void ScrollView::scrollContentsSlowPath(const IntRect& updateRect)
568{
569    hostWindow()->invalidateContentsForSlowScroll(updateRect);
570}
571
572IntPoint ScrollView::rootViewToContents(const IntPoint& rootViewPoint) const
573{
574    IntPoint viewPoint = convertFromContainingWindow(rootViewPoint);
575    return viewPoint + scrollOffset();
576}
577
578IntPoint ScrollView::contentsToRootView(const IntPoint& contentsPoint) const
579{
580    IntPoint viewPoint = contentsPoint - scrollOffset();
581    return convertToContainingWindow(viewPoint);
582}
583
584IntRect ScrollView::rootViewToContents(const IntRect& rootViewRect) const
585{
586    IntRect viewRect = convertFromContainingWindow(rootViewRect);
587    viewRect.move(scrollOffset());
588    return viewRect;
589}
590
591IntRect ScrollView::contentsToRootView(const IntRect& contentsRect) const
592{
593    IntRect viewRect = contentsRect;
594    viewRect.move(-scrollOffset());
595    return convertToContainingWindow(viewRect);
596}
597
598IntPoint ScrollView::windowToContents(const IntPoint& windowPoint) const
599{
600    IntPoint viewPoint = convertFromContainingWindow(windowPoint);
601    return viewPoint + scrollOffset();
602}
603
604FloatPoint ScrollView::windowToContents(const FloatPoint& windowPoint) const
605{
606    FloatPoint viewPoint = convertFromContainingWindow(windowPoint);
607    return viewPoint + scrollOffset();
608}
609
610IntPoint ScrollView::contentsToWindow(const IntPoint& contentsPoint) const
611{
612    IntPoint viewPoint = contentsPoint - scrollOffset();
613    return convertToContainingWindow(viewPoint);
614}
615
616IntRect ScrollView::windowToContents(const IntRect& windowRect) const
617{
618    IntRect viewRect = convertFromContainingWindow(windowRect);
619    viewRect.move(scrollOffset());
620    return viewRect;
621}
622
623IntRect ScrollView::contentsToWindow(const IntRect& contentsRect) const
624{
625    IntRect viewRect = contentsRect;
626    viewRect.move(-scrollOffset());
627    return convertToContainingWindow(viewRect);
628}
629
630IntRect ScrollView::contentsToScreen(const IntRect& rect) const
631{
632    HostWindow* window = hostWindow();
633    if (!window)
634        return IntRect();
635    return window->rootViewToScreen(contentsToRootView(rect));
636}
637
638bool ScrollView::containsScrollbarsAvoidingResizer() const
639{
640    return !m_scrollbarsAvoidingResizer;
641}
642
643void ScrollView::adjustScrollbarsAvoidingResizerCount(int overlapDelta)
644{
645    int oldCount = m_scrollbarsAvoidingResizer;
646    m_scrollbarsAvoidingResizer += overlapDelta;
647    if (parent())
648        toScrollView(parent())->adjustScrollbarsAvoidingResizerCount(overlapDelta);
649    else if (!scrollbarsSuppressed()) {
650        // If we went from n to 0 or from 0 to n and we're the outermost view,
651        // we need to invalidate the windowResizerRect(), since it will now need to paint
652        // differently.
653        if ((oldCount > 0 && m_scrollbarsAvoidingResizer == 0) ||
654            (oldCount == 0 && m_scrollbarsAvoidingResizer > 0))
655            invalidateRect(windowResizerRect());
656    }
657}
658
659void ScrollView::setParent(Widget* parentView)
660{
661    if (parentView == parent())
662        return;
663
664    if (m_scrollbarsAvoidingResizer && parent())
665        toScrollView(parent())->adjustScrollbarsAvoidingResizerCount(-m_scrollbarsAvoidingResizer);
666
667    Widget::setParent(parentView);
668
669    if (m_scrollbarsAvoidingResizer && parent())
670        toScrollView(parent())->adjustScrollbarsAvoidingResizerCount(m_scrollbarsAvoidingResizer);
671}
672
673void ScrollView::setScrollbarsSuppressed(bool suppressed, bool repaintOnUnsuppress)
674{
675    if (suppressed == m_scrollbarsSuppressed)
676        return;
677
678    m_scrollbarsSuppressed = suppressed;
679
680    if (repaintOnUnsuppress && !suppressed) {
681        if (m_horizontalScrollbar)
682            m_horizontalScrollbar->invalidate();
683        if (m_verticalScrollbar)
684            m_verticalScrollbar->invalidate();
685
686        // Invalidate the scroll corner too on unsuppress.
687        invalidateRect(scrollCornerRect());
688    }
689}
690
691Scrollbar* ScrollView::scrollbarAtWindowPoint(const IntPoint& windowPoint)
692{
693    IntPoint viewPoint = convertFromContainingWindow(windowPoint);
694    return scrollbarAtViewPoint(viewPoint);
695}
696
697Scrollbar* ScrollView::scrollbarAtViewPoint(const IntPoint& viewPoint)
698{
699    if (m_horizontalScrollbar && m_horizontalScrollbar->shouldParticipateInHitTesting() && m_horizontalScrollbar->frameRect().contains(viewPoint))
700        return m_horizontalScrollbar.get();
701    if (m_verticalScrollbar && m_verticalScrollbar->shouldParticipateInHitTesting() && m_verticalScrollbar->frameRect().contains(viewPoint))
702        return m_verticalScrollbar.get();
703    return 0;
704}
705
706void ScrollView::setFrameRect(const IntRect& newRect)
707{
708    IntRect oldRect = frameRect();
709
710    if (newRect == oldRect)
711        return;
712
713    Widget::setFrameRect(newRect);
714
715    updateScrollbars(scrollOffset());
716
717    frameRectsChanged();
718}
719
720void ScrollView::frameRectsChanged()
721{
722    HashSet<RefPtr<Widget> >::const_iterator end = m_children.end();
723    for (HashSet<RefPtr<Widget> >::const_iterator current = m_children.begin(); current != end; ++current)
724        (*current)->frameRectsChanged();
725}
726
727static void positionScrollbarLayer(GraphicsLayer* graphicsLayer, Scrollbar* scrollbar)
728{
729    if (!graphicsLayer || !scrollbar)
730        return;
731
732    IntRect scrollbarRect = scrollbar->frameRect();
733    graphicsLayer->setPosition(scrollbarRect.location());
734
735    if (scrollbarRect.size() == graphicsLayer->size())
736        return;
737
738    graphicsLayer->setSize(scrollbarRect.size());
739
740    if (graphicsLayer->hasContentsLayer()) {
741        graphicsLayer->setContentsRect(IntRect(0, 0, scrollbarRect.width(), scrollbarRect.height()));
742        return;
743    }
744
745    graphicsLayer->setDrawsContent(true);
746    graphicsLayer->setNeedsDisplay();
747}
748
749static void positionScrollCornerLayer(GraphicsLayer* graphicsLayer, const IntRect& cornerRect)
750{
751    if (!graphicsLayer)
752        return;
753    graphicsLayer->setDrawsContent(!cornerRect.isEmpty());
754    graphicsLayer->setPosition(cornerRect.location());
755    if (cornerRect.size() != graphicsLayer->size())
756        graphicsLayer->setNeedsDisplay();
757    graphicsLayer->setSize(cornerRect.size());
758}
759
760void ScrollView::positionScrollbarLayers()
761{
762    positionScrollbarLayer(layerForHorizontalScrollbar(), horizontalScrollbar());
763    positionScrollbarLayer(layerForVerticalScrollbar(), verticalScrollbar());
764    positionScrollCornerLayer(layerForScrollCorner(), scrollCornerRect());
765}
766
767bool ScrollView::userInputScrollable(ScrollbarOrientation orientation) const
768{
769    ScrollbarMode mode = (orientation == HorizontalScrollbar) ?
770        m_horizontalScrollbarMode : m_verticalScrollbarMode;
771
772    return mode == ScrollbarAuto || mode == ScrollbarAlwaysOn;
773}
774
775bool ScrollView::shouldPlaceVerticalScrollbarOnLeft() const
776{
777    return false;
778}
779
780void ScrollView::contentRectangleForPaintInvalidation(const IntRect& rect)
781{
782    IntRect paintRect = rect;
783    if (clipsPaintInvalidations())
784        paintRect.intersect(visibleContentRect());
785    if (paintRect.isEmpty())
786        return;
787
788    if (HostWindow* window = hostWindow())
789        window->invalidateContentsAndRootView(contentsToWindow(paintRect));
790}
791
792IntRect ScrollView::scrollCornerRect() const
793{
794    IntRect cornerRect;
795
796    if (hasOverlayScrollbars())
797        return cornerRect;
798
799    if (m_horizontalScrollbar && width() - m_horizontalScrollbar->width() > 0) {
800        cornerRect.unite(IntRect(shouldPlaceVerticalScrollbarOnLeft() ? 0 : m_horizontalScrollbar->width(),
801                                 height() - m_horizontalScrollbar->height(),
802                                 width() - m_horizontalScrollbar->width(),
803                                 m_horizontalScrollbar->height()));
804    }
805
806    if (m_verticalScrollbar && height() - m_verticalScrollbar->height() > 0) {
807        cornerRect.unite(IntRect(shouldPlaceVerticalScrollbarOnLeft() ? 0 : (width() - m_verticalScrollbar->width()),
808                                 m_verticalScrollbar->height(),
809                                 m_verticalScrollbar->width(),
810                                 height() - m_verticalScrollbar->height()));
811    }
812
813    return cornerRect;
814}
815
816bool ScrollView::isScrollCornerVisible() const
817{
818    return !scrollCornerRect().isEmpty();
819}
820
821void ScrollView::scrollbarStyleChanged()
822{
823    adjustScrollbarOpacity();
824    contentsResized();
825    updateScrollbars(scrollOffset());
826    positionScrollbarLayers();
827}
828
829void ScrollView::updateScrollCorner()
830{
831}
832
833void ScrollView::paintScrollCorner(GraphicsContext* context, const IntRect& cornerRect)
834{
835    ScrollbarTheme::theme()->paintScrollCorner(context, cornerRect);
836}
837
838void ScrollView::paintScrollbar(GraphicsContext* context, Scrollbar* bar, const IntRect& rect)
839{
840    bar->paint(context, rect);
841}
842
843void ScrollView::invalidateScrollCornerRect(const IntRect& rect)
844{
845    invalidateRect(rect);
846}
847
848void ScrollView::paintScrollbars(GraphicsContext* context, const IntRect& rect)
849{
850    if (m_horizontalScrollbar && !layerForHorizontalScrollbar())
851        paintScrollbar(context, m_horizontalScrollbar.get(), rect);
852    if (m_verticalScrollbar && !layerForVerticalScrollbar())
853        paintScrollbar(context, m_verticalScrollbar.get(), rect);
854
855    if (layerForScrollCorner())
856        return;
857    paintScrollCorner(context, scrollCornerRect());
858}
859
860void ScrollView::paintPanScrollIcon(GraphicsContext* context)
861{
862    DEFINE_STATIC_REF(Image, panScrollIcon, (Image::loadPlatformResource("panIcon")));
863    IntPoint iconGCPoint = m_panScrollIconPoint;
864    if (parent())
865        iconGCPoint = toScrollView(parent())->windowToContents(iconGCPoint);
866    context->drawImage(panScrollIcon, iconGCPoint);
867}
868
869void ScrollView::paint(GraphicsContext* context, const IntRect& rect)
870{
871    notifyPageThatContentAreaWillPaint();
872
873    IntRect documentDirtyRect = rect;
874    IntRect visibleAreaWithoutScrollbars(location(), visibleContentRect().size());
875    documentDirtyRect.intersect(visibleAreaWithoutScrollbars);
876
877    if (!documentDirtyRect.isEmpty()) {
878        GraphicsContextStateSaver stateSaver(*context);
879
880        context->translate(x() - scrollX(), y() - scrollY());
881        context->clip(visibleContentRect());
882
883        documentDirtyRect.moveBy(-location() + scrollPosition());
884        paintContents(context, documentDirtyRect);
885    }
886
887    calculateAndPaintOverhangAreas(context, rect);
888
889    // Now paint the scrollbars.
890    if (!m_scrollbarsSuppressed && (m_horizontalScrollbar || m_verticalScrollbar)) {
891        GraphicsContextStateSaver stateSaver(*context);
892        IntRect scrollViewDirtyRect = rect;
893        IntRect visibleAreaWithScrollbars(location(), visibleContentRect(IncludeScrollbars).size());
894        scrollViewDirtyRect.intersect(visibleAreaWithScrollbars);
895        context->translate(x(), y());
896        scrollViewDirtyRect.moveBy(-location());
897        context->clip(IntRect(IntPoint(), visibleAreaWithScrollbars.size()));
898
899        paintScrollbars(context, scrollViewDirtyRect);
900    }
901
902    // Paint the panScroll Icon
903    if (m_drawPanScrollIcon)
904        paintPanScrollIcon(context);
905}
906
907void ScrollView::calculateOverhangAreasForPainting(IntRect& horizontalOverhangRect, IntRect& verticalOverhangRect)
908{
909    int verticalScrollbarWidth = (verticalScrollbar() && !verticalScrollbar()->isOverlayScrollbar())
910        ? verticalScrollbar()->width() : 0;
911    int horizontalScrollbarHeight = (horizontalScrollbar() && !horizontalScrollbar()->isOverlayScrollbar())
912        ? horizontalScrollbar()->height() : 0;
913
914    int physicalScrollY = scrollPosition().y() + scrollOrigin().y();
915    if (physicalScrollY < 0) {
916        horizontalOverhangRect = frameRect();
917        horizontalOverhangRect.setHeight(-physicalScrollY);
918        horizontalOverhangRect.setWidth(horizontalOverhangRect.width() - verticalScrollbarWidth);
919    } else if (contentsHeight() && physicalScrollY > contentsHeight() - visibleHeight()) {
920        int height = physicalScrollY - (contentsHeight() - visibleHeight());
921        horizontalOverhangRect = frameRect();
922        horizontalOverhangRect.setY(frameRect().maxY() - height - horizontalScrollbarHeight);
923        horizontalOverhangRect.setHeight(height);
924        horizontalOverhangRect.setWidth(horizontalOverhangRect.width() - verticalScrollbarWidth);
925    }
926
927    int physicalScrollX = scrollPosition().x() + scrollOrigin().x();
928    if (physicalScrollX < 0) {
929        verticalOverhangRect.setWidth(-physicalScrollX);
930        verticalOverhangRect.setHeight(frameRect().height() - horizontalOverhangRect.height() - horizontalScrollbarHeight);
931        verticalOverhangRect.setX(frameRect().x());
932        if (horizontalOverhangRect.y() == frameRect().y())
933            verticalOverhangRect.setY(frameRect().y() + horizontalOverhangRect.height());
934        else
935            verticalOverhangRect.setY(frameRect().y());
936    } else if (contentsWidth() && physicalScrollX > contentsWidth() - visibleWidth()) {
937        int width = physicalScrollX - (contentsWidth() - visibleWidth());
938        verticalOverhangRect.setWidth(width);
939        verticalOverhangRect.setHeight(frameRect().height() - horizontalOverhangRect.height() - horizontalScrollbarHeight);
940        verticalOverhangRect.setX(frameRect().maxX() - width - verticalScrollbarWidth);
941        if (horizontalOverhangRect.y() == frameRect().y())
942            verticalOverhangRect.setY(frameRect().y() + horizontalOverhangRect.height());
943        else
944            verticalOverhangRect.setY(frameRect().y());
945    }
946}
947
948void ScrollView::updateOverhangAreas()
949{
950    HostWindow* window = hostWindow();
951    if (!window)
952        return;
953
954    IntRect horizontalOverhangRect;
955    IntRect verticalOverhangRect;
956    calculateOverhangAreasForPainting(horizontalOverhangRect, verticalOverhangRect);
957    if (!horizontalOverhangRect.isEmpty())
958        window->invalidateContentsAndRootView(horizontalOverhangRect);
959    if (!verticalOverhangRect.isEmpty())
960        window->invalidateContentsAndRootView(verticalOverhangRect);
961}
962
963void ScrollView::paintOverhangAreas(GraphicsContext* context, const IntRect& horizontalOverhangRect, const IntRect& verticalOverhangRect, const IntRect& dirtyRect)
964{
965    ScrollbarTheme::theme()->paintOverhangBackground(context, horizontalOverhangRect, verticalOverhangRect, dirtyRect);
966    ScrollbarTheme::theme()->paintOverhangShadows(context, scrollOffset(), horizontalOverhangRect, verticalOverhangRect, dirtyRect);
967}
968
969void ScrollView::calculateAndPaintOverhangAreas(GraphicsContext* context, const IntRect& dirtyRect)
970{
971    IntRect horizontalOverhangRect;
972    IntRect verticalOverhangRect;
973    calculateOverhangAreasForPainting(horizontalOverhangRect, verticalOverhangRect);
974
975    if (dirtyRect.intersects(horizontalOverhangRect) || dirtyRect.intersects(verticalOverhangRect))
976        paintOverhangAreas(context, horizontalOverhangRect, verticalOverhangRect, dirtyRect);
977}
978
979void ScrollView::calculateAndPaintOverhangBackground(GraphicsContext* context, const IntRect& dirtyRect)
980{
981    IntRect horizontalOverhangRect;
982    IntRect verticalOverhangRect;
983    calculateOverhangAreasForPainting(horizontalOverhangRect, verticalOverhangRect);
984
985    if (dirtyRect.intersects(horizontalOverhangRect) || dirtyRect.intersects(verticalOverhangRect))
986        ScrollbarTheme::theme()->paintOverhangBackground(context, horizontalOverhangRect, verticalOverhangRect, dirtyRect);
987}
988
989bool ScrollView::isPointInScrollbarCorner(const IntPoint& windowPoint)
990{
991    if (!scrollbarCornerPresent())
992        return false;
993
994    IntPoint viewPoint = convertFromContainingWindow(windowPoint);
995
996    if (m_horizontalScrollbar) {
997        int horizontalScrollbarYMin = m_horizontalScrollbar->frameRect().y();
998        int horizontalScrollbarYMax = m_horizontalScrollbar->frameRect().y() + m_horizontalScrollbar->frameRect().height();
999        int horizontalScrollbarXMin = m_horizontalScrollbar->frameRect().x() + m_horizontalScrollbar->frameRect().width();
1000
1001        return viewPoint.y() > horizontalScrollbarYMin && viewPoint.y() < horizontalScrollbarYMax && viewPoint.x() > horizontalScrollbarXMin;
1002    }
1003
1004    int verticalScrollbarXMin = m_verticalScrollbar->frameRect().x();
1005    int verticalScrollbarXMax = m_verticalScrollbar->frameRect().x() + m_verticalScrollbar->frameRect().width();
1006    int verticalScrollbarYMin = m_verticalScrollbar->frameRect().y() + m_verticalScrollbar->frameRect().height();
1007
1008    return viewPoint.x() > verticalScrollbarXMin && viewPoint.x() < verticalScrollbarXMax && viewPoint.y() > verticalScrollbarYMin;
1009}
1010
1011bool ScrollView::scrollbarCornerPresent() const
1012{
1013    return (m_horizontalScrollbar && width() - m_horizontalScrollbar->width() > 0)
1014        || (m_verticalScrollbar && height() - m_verticalScrollbar->height() > 0);
1015}
1016
1017IntRect ScrollView::convertFromScrollbarToContainingView(const Scrollbar* scrollbar, const IntRect& localRect) const
1018{
1019    // Scrollbars won't be transformed within us
1020    IntRect newRect = localRect;
1021    newRect.moveBy(scrollbar->location());
1022    return newRect;
1023}
1024
1025IntRect ScrollView::convertFromContainingViewToScrollbar(const Scrollbar* scrollbar, const IntRect& parentRect) const
1026{
1027    IntRect newRect = parentRect;
1028    // Scrollbars won't be transformed within us
1029    newRect.moveBy(-scrollbar->location());
1030    return newRect;
1031}
1032
1033// FIXME: test these on windows
1034IntPoint ScrollView::convertFromScrollbarToContainingView(const Scrollbar* scrollbar, const IntPoint& localPoint) const
1035{
1036    // Scrollbars won't be transformed within us
1037    IntPoint newPoint = localPoint;
1038    newPoint.moveBy(scrollbar->location());
1039    return newPoint;
1040}
1041
1042IntPoint ScrollView::convertFromContainingViewToScrollbar(const Scrollbar* scrollbar, const IntPoint& parentPoint) const
1043{
1044    IntPoint newPoint = parentPoint;
1045    // Scrollbars won't be transformed within us
1046    newPoint.moveBy(-scrollbar->location());
1047    return newPoint;
1048}
1049
1050void ScrollView::setParentVisible(bool visible)
1051{
1052    if (isParentVisible() == visible)
1053        return;
1054
1055    Widget::setParentVisible(visible);
1056
1057    if (!isSelfVisible())
1058        return;
1059
1060    HashSet<RefPtr<Widget> >::iterator end = m_children.end();
1061    for (HashSet<RefPtr<Widget> >::iterator it = m_children.begin(); it != end; ++it)
1062        (*it)->setParentVisible(visible);
1063}
1064
1065void ScrollView::show()
1066{
1067    if (!isSelfVisible()) {
1068        setSelfVisible(true);
1069        if (isParentVisible()) {
1070            HashSet<RefPtr<Widget> >::iterator end = m_children.end();
1071            for (HashSet<RefPtr<Widget> >::iterator it = m_children.begin(); it != end; ++it)
1072                (*it)->setParentVisible(true);
1073        }
1074    }
1075
1076    Widget::show();
1077}
1078
1079void ScrollView::hide()
1080{
1081    if (isSelfVisible()) {
1082        if (isParentVisible()) {
1083            HashSet<RefPtr<Widget> >::iterator end = m_children.end();
1084            for (HashSet<RefPtr<Widget> >::iterator it = m_children.begin(); it != end; ++it)
1085                (*it)->setParentVisible(false);
1086        }
1087        setSelfVisible(false);
1088    }
1089
1090    Widget::hide();
1091}
1092
1093void ScrollView::addPanScrollIcon(const IntPoint& iconPosition)
1094{
1095    HostWindow* window = hostWindow();
1096    if (!window)
1097        return;
1098    m_drawPanScrollIcon = true;
1099    m_panScrollIconPoint = IntPoint(iconPosition.x() - panIconSizeLength / 2 , iconPosition.y() - panIconSizeLength / 2) ;
1100    window->invalidateContentsAndRootView(IntRect(m_panScrollIconPoint, IntSize(panIconSizeLength, panIconSizeLength)));
1101}
1102
1103void ScrollView::removePanScrollIcon()
1104{
1105    HostWindow* window = hostWindow();
1106    if (!window)
1107        return;
1108    m_drawPanScrollIcon = false;
1109    window->invalidateContentsAndRootView(IntRect(m_panScrollIconPoint, IntSize(panIconSizeLength, panIconSizeLength)));
1110}
1111
1112void ScrollView::setScrollOrigin(const IntPoint& origin, bool updatePositionAtAll, bool updatePositionSynchronously)
1113{
1114    if (scrollOrigin() == origin)
1115        return;
1116
1117    ScrollableArea::setScrollOrigin(origin);
1118
1119    // Update if the scroll origin changes, since our position will be different if the content size did not change.
1120    if (updatePositionAtAll && updatePositionSynchronously)
1121        updateScrollbars(scrollOffset());
1122}
1123
1124} // namespace blink
1125