1/*
2 * Copyright (C) 2006, 2007, 2008, 2009, 2010, 2011, 2012 Apple Inc. All rights reserved.
3 *
4 * Portions are Copyright (C) 1998 Netscape Communications Corporation.
5 *
6 * Other contributors:
7 *   Robert O'Callahan <roc+@cs.cmu.edu>
8 *   David Baron <dbaron@fas.harvard.edu>
9 *   Christian Biesinger <cbiesinger@web.de>
10 *   Randall Jesup <rjesup@wgate.com>
11 *   Roland Mainz <roland.mainz@informatik.med.uni-giessen.de>
12 *   Josh Soref <timeless@mac.com>
13 *   Boris Zbarsky <bzbarsky@mit.edu>
14 *
15 * This library is free software; you can redistribute it and/or
16 * modify it under the terms of the GNU Lesser General Public
17 * License as published by the Free Software Foundation; either
18 * version 2.1 of the License, or (at your option) any later version.
19 *
20 * This library is distributed in the hope that it will be useful,
21 * but WITHOUT ANY WARRANTY; without even the implied warranty of
22 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
23 * Lesser General Public License for more details.
24 *
25 * You should have received a copy of the GNU Lesser General Public
26 * License along with this library; if not, write to the Free Software
27 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301  USA
28 *
29 * Alternatively, the contents of this file may be used under the terms
30 * of either the Mozilla Public License Version 1.1, found at
31 * http://www.mozilla.org/MPL/ (the "MPL") or the GNU General Public
32 * License Version 2.0, found at http://www.fsf.org/copyleft/gpl.html
33 * (the "GPL"), in which case the provisions of the MPL or the GPL are
34 * applicable instead of those above.  If you wish to allow use of your
35 * version of this file only under the terms of one of those two
36 * licenses (the MPL or the GPL) and not to allow others to use your
37 * version of this file under the LGPL, indicate your decision by
38 * deletingthe provisions above and replace them with the notice and
39 * other provisions required by the MPL or the GPL, as the case may be.
40 * If you do not delete the provisions above, a recipient may use your
41 * version of this file under any of the LGPL, the MPL or the GPL.
42 */
43
44#include "config.h"
45#include "core/rendering/RenderLayer.h"
46
47#include "core/accessibility/AXObjectCache.h"
48#include "core/css/PseudoStyleRequest.h"
49#include "core/dom/Node.h"
50#include "core/dom/shadow/ShadowRoot.h"
51#include "core/editing/FrameSelection.h"
52#include "core/frame/FrameView.h"
53#include "core/frame/LocalFrame.h"
54#include "core/html/HTMLFrameOwnerElement.h"
55#include "core/inspector/InspectorInstrumentation.h"
56#include "core/page/Chrome.h"
57#include "core/page/EventHandler.h"
58#include "core/page/FocusController.h"
59#include "core/page/Page.h"
60#include "core/page/scrolling/ScrollingCoordinator.h"
61#include "core/rendering/RenderGeometryMap.h"
62#include "core/rendering/RenderScrollbar.h"
63#include "core/rendering/RenderScrollbarPart.h"
64#include "core/rendering/RenderTheme.h"
65#include "core/rendering/RenderView.h"
66#include "core/rendering/compositing/CompositedLayerMapping.h"
67#include "core/rendering/compositing/RenderLayerCompositor.h"
68#include "platform/PlatformGestureEvent.h"
69#include "platform/PlatformMouseEvent.h"
70#include "platform/graphics/GraphicsContextStateSaver.h"
71#include "platform/graphics/GraphicsLayer.h"
72#include "platform/scroll/ScrollAnimator.h"
73#include "platform/scroll/ScrollbarTheme.h"
74#include "public/platform/Platform.h"
75
76namespace blink {
77
78const int ResizerControlExpandRatioForTouch = 2;
79
80RenderLayerScrollableArea::RenderLayerScrollableArea(RenderLayer& layer)
81    : m_layer(layer)
82    , m_inResizeMode(false)
83    , m_scrollsOverflow(false)
84    , m_scrollDimensionsDirty(true)
85    , m_inOverflowRelayout(false)
86    , m_nextTopmostScrollChild(0)
87    , m_topmostScrollChild(0)
88    , m_needsCompositedScrolling(false)
89    , m_scrollCorner(nullptr)
90    , m_resizer(nullptr)
91{
92    ScrollableArea::setConstrainsScrollingToContentEdge(false);
93
94    Node* node = box().node();
95    if (node && node->isElementNode()) {
96        // We save and restore only the scrollOffset as the other scroll values are recalculated.
97        Element* element = toElement(node);
98        m_scrollOffset = element->savedLayerScrollOffset();
99        if (!m_scrollOffset.isZero())
100            scrollAnimator()->setCurrentPosition(FloatPoint(m_scrollOffset.width(), m_scrollOffset.height()));
101        element->setSavedLayerScrollOffset(IntSize());
102    }
103
104    updateResizerAreaSet();
105}
106
107RenderLayerScrollableArea::~RenderLayerScrollableArea()
108{
109    if (inResizeMode() && !box().documentBeingDestroyed()) {
110        if (LocalFrame* frame = box().frame())
111            frame->eventHandler().resizeScrollableAreaDestroyed();
112    }
113
114    if (LocalFrame* frame = box().frame()) {
115        if (FrameView* frameView = frame->view()) {
116            frameView->removeScrollableArea(this);
117        }
118    }
119
120    if (box().frame() && box().frame()->page()) {
121        if (ScrollingCoordinator* scrollingCoordinator = box().frame()->page()->scrollingCoordinator())
122            scrollingCoordinator->willDestroyScrollableArea(this);
123    }
124
125    if (!box().documentBeingDestroyed()) {
126        Node* node = box().node();
127        if (node && node->isElementNode())
128            toElement(node)->setSavedLayerScrollOffset(m_scrollOffset);
129    }
130
131    if (LocalFrame* frame = box().frame()) {
132        if (FrameView* frameView = frame->view())
133            frameView->removeResizerArea(box());
134    }
135
136    destroyScrollbar(HorizontalScrollbar);
137    destroyScrollbar(VerticalScrollbar);
138
139    if (m_scrollCorner)
140        m_scrollCorner->destroy();
141    if (m_resizer)
142        m_resizer->destroy();
143}
144
145HostWindow* RenderLayerScrollableArea::hostWindow() const
146{
147    if (Page* page = box().frame()->page())
148        return &page->chrome();
149    return nullptr;
150}
151
152GraphicsLayer* RenderLayerScrollableArea::layerForScrolling() const
153{
154    return layer()->hasCompositedLayerMapping() ? layer()->compositedLayerMapping()->scrollingContentsLayer() : 0;
155}
156
157GraphicsLayer* RenderLayerScrollableArea::layerForHorizontalScrollbar() const
158{
159    // See crbug.com/343132.
160    DisableCompositingQueryAsserts disabler;
161
162    return layer()->hasCompositedLayerMapping() ? layer()->compositedLayerMapping()->layerForHorizontalScrollbar() : 0;
163}
164
165GraphicsLayer* RenderLayerScrollableArea::layerForVerticalScrollbar() const
166{
167    // See crbug.com/343132.
168    DisableCompositingQueryAsserts disabler;
169
170    return layer()->hasCompositedLayerMapping() ? layer()->compositedLayerMapping()->layerForVerticalScrollbar() : 0;
171}
172
173GraphicsLayer* RenderLayerScrollableArea::layerForScrollCorner() const
174{
175    // See crbug.com/343132.
176    DisableCompositingQueryAsserts disabler;
177
178    return layer()->hasCompositedLayerMapping() ? layer()->compositedLayerMapping()->layerForScrollCorner() : 0;
179}
180
181void RenderLayerScrollableArea::invalidateScrollbarRect(Scrollbar* scrollbar, const IntRect& rect)
182{
183    // See crbug.com/343132.
184    DisableCompositingQueryAsserts disabler;
185
186    if (scrollbar == m_vBar.get()) {
187        if (GraphicsLayer* layer = layerForVerticalScrollbar()) {
188            layer->setNeedsDisplayInRect(rect, WebInvalidationDebugAnnotationsScrollbar);
189            return;
190        }
191    } else {
192        if (GraphicsLayer* layer = layerForHorizontalScrollbar()) {
193            layer->setNeedsDisplayInRect(rect, WebInvalidationDebugAnnotationsScrollbar);
194            return;
195        }
196    }
197
198    IntRect scrollRect = rect;
199    // If we are not yet inserted into the tree, there is no need to issue paint invaldiations.
200    if (!box().parent())
201        return;
202
203    if (scrollbar == m_vBar.get())
204        scrollRect.move(verticalScrollbarStart(0, box().width()), box().borderTop());
205    else
206        scrollRect.move(horizontalScrollbarStart(0), box().height() - box().borderBottom() - scrollbar->height());
207
208    if (scrollRect.isEmpty())
209        return;
210
211    LayoutRect paintInvalidationRect = scrollRect;
212    box().flipForWritingMode(paintInvalidationRect);
213
214    IntRect intRect = pixelSnappedIntRect(paintInvalidationRect);
215
216    if (box().frameView()->isInPerformLayout())
217        addScrollbarDamage(scrollbar, intRect);
218    else
219        box().invalidatePaintRectangle(intRect);
220}
221
222void RenderLayerScrollableArea::invalidateScrollCornerRect(const IntRect& rect)
223{
224    if (GraphicsLayer* layer = layerForScrollCorner()) {
225        layer->setNeedsDisplayInRect(rect, WebInvalidationDebugAnnotationsScrollbar);
226        return;
227    }
228
229    if (m_scrollCorner)
230        m_scrollCorner->invalidatePaintRectangle(rect);
231    if (m_resizer)
232        m_resizer->invalidatePaintRectangle(rect);
233}
234
235bool RenderLayerScrollableArea::isActive() const
236{
237    Page* page = box().frame()->page();
238    return page && page->focusController().isActive();
239}
240
241bool RenderLayerScrollableArea::isScrollCornerVisible() const
242{
243    return !scrollCornerRect().isEmpty();
244}
245
246static int cornerStart(const RenderStyle* style, int minX, int maxX, int thickness)
247{
248    if (style->shouldPlaceBlockDirectionScrollbarOnLogicalLeft())
249        return minX + style->borderLeftWidth();
250    return maxX - thickness - style->borderRightWidth();
251}
252
253static IntRect cornerRect(const RenderStyle* style, const Scrollbar* horizontalScrollbar, const Scrollbar* verticalScrollbar, const IntRect& bounds)
254{
255    int horizontalThickness;
256    int verticalThickness;
257    if (!verticalScrollbar && !horizontalScrollbar) {
258        // FIXME: This isn't right. We need to know the thickness of custom scrollbars
259        // even when they don't exist in order to set the resizer square size properly.
260        horizontalThickness = ScrollbarTheme::theme()->scrollbarThickness();
261        verticalThickness = horizontalThickness;
262    } else if (verticalScrollbar && !horizontalScrollbar) {
263        horizontalThickness = verticalScrollbar->width();
264        verticalThickness = horizontalThickness;
265    } else if (horizontalScrollbar && !verticalScrollbar) {
266        verticalThickness = horizontalScrollbar->height();
267        horizontalThickness = verticalThickness;
268    } else {
269        horizontalThickness = verticalScrollbar->width();
270        verticalThickness = horizontalScrollbar->height();
271    }
272    return IntRect(cornerStart(style, bounds.x(), bounds.maxX(), horizontalThickness),
273        bounds.maxY() - verticalThickness - style->borderBottomWidth(),
274        horizontalThickness, verticalThickness);
275}
276
277
278IntRect RenderLayerScrollableArea::scrollCornerRect() const
279{
280    // We have a scrollbar corner when a scrollbar is visible and not filling the entire length of the box.
281    // This happens when:
282    // (a) A resizer is present and at least one scrollbar is present
283    // (b) Both scrollbars are present.
284    bool hasHorizontalBar = horizontalScrollbar();
285    bool hasVerticalBar = verticalScrollbar();
286    bool hasResizer = box().style()->resize() != RESIZE_NONE;
287    if ((hasHorizontalBar && hasVerticalBar) || (hasResizer && (hasHorizontalBar || hasVerticalBar)))
288        return cornerRect(box().style(), horizontalScrollbar(), verticalScrollbar(), box().pixelSnappedBorderBoxRect());
289    return IntRect();
290}
291
292IntRect RenderLayerScrollableArea::convertFromScrollbarToContainingView(const Scrollbar* scrollbar, const IntRect& scrollbarRect) const
293{
294    RenderView* view = box().view();
295    if (!view)
296        return scrollbarRect;
297
298    IntRect rect = scrollbarRect;
299    rect.move(scrollbarOffset(scrollbar));
300
301    return view->frameView()->convertFromRenderer(box(), rect);
302}
303
304IntRect RenderLayerScrollableArea::convertFromContainingViewToScrollbar(const Scrollbar* scrollbar, const IntRect& parentRect) const
305{
306    RenderView* view = box().view();
307    if (!view)
308        return parentRect;
309
310    IntRect rect = view->frameView()->convertToRenderer(box(), parentRect);
311    rect.move(-scrollbarOffset(scrollbar));
312    return rect;
313}
314
315IntPoint RenderLayerScrollableArea::convertFromScrollbarToContainingView(const Scrollbar* scrollbar, const IntPoint& scrollbarPoint) const
316{
317    RenderView* view = box().view();
318    if (!view)
319        return scrollbarPoint;
320
321    IntPoint point = scrollbarPoint;
322    point.move(scrollbarOffset(scrollbar));
323    return view->frameView()->convertFromRenderer(box(), point);
324}
325
326IntPoint RenderLayerScrollableArea::convertFromContainingViewToScrollbar(const Scrollbar* scrollbar, const IntPoint& parentPoint) const
327{
328    RenderView* view = box().view();
329    if (!view)
330        return parentPoint;
331
332    IntPoint point = view->frameView()->convertToRenderer(box(), parentPoint);
333
334    point.move(-scrollbarOffset(scrollbar));
335    return point;
336}
337
338int RenderLayerScrollableArea::scrollSize(ScrollbarOrientation orientation) const
339{
340    IntSize scrollDimensions = maximumScrollPosition() - minimumScrollPosition();
341    return (orientation == HorizontalScrollbar) ? scrollDimensions.width() : scrollDimensions.height();
342}
343
344void RenderLayerScrollableArea::setScrollOffset(const IntPoint& newScrollOffset)
345{
346    if (!box().isMarquee()) {
347        // Ensure that the dimensions will be computed if they need to be (for overflow:hidden blocks).
348        if (m_scrollDimensionsDirty)
349            computeScrollDimensions();
350    }
351
352    if (scrollOffset() == toIntSize(newScrollOffset))
353        return;
354
355    setScrollOffset(toIntSize(newScrollOffset));
356
357    LocalFrame* frame = box().frame();
358    ASSERT(frame);
359
360    RefPtr<FrameView> frameView = box().frameView();
361
362    TRACE_EVENT1(TRACE_DISABLED_BY_DEFAULT("devtools.timeline"), "ScrollLayer", "data", InspectorScrollLayerEvent::data(&box()));
363    // FIXME(361045): remove InspectorInstrumentation calls once DevTools Timeline migrates to tracing.
364    InspectorInstrumentation::willScrollLayer(&box());
365
366    const RenderLayerModelObject* paintInvalidationContainer = box().containerForPaintInvalidation();
367
368    // Update the positions of our child layers (if needed as only fixed layers should be impacted by a scroll).
369    // We don't update compositing layers, because we need to do a deep update from the compositing ancestor.
370    if (!frameView->isInPerformLayout()) {
371        // If we're in the middle of layout, we'll just update layers once layout has finished.
372        layer()->clipper().clearClipRectsIncludingDescendants();
373        box().setPreviousPaintInvalidationRect(box().boundsRectForPaintInvalidation(paintInvalidationContainer));
374        // Update regions, scrolling may change the clip of a particular region.
375        frameView->updateAnnotatedRegions();
376        frameView->setNeedsUpdateWidgetPositions();
377        updateCompositingLayersAfterScroll();
378    }
379
380    // The caret rect needs to be invalidated after scrolling
381    frame->selection().setCaretRectNeedsUpdate();
382
383    FloatQuad quadForFakeMouseMoveEvent = FloatQuad(layer()->renderer()->previousPaintInvalidationRect());
384
385    quadForFakeMouseMoveEvent = paintInvalidationContainer->localToAbsoluteQuad(quadForFakeMouseMoveEvent);
386    frame->eventHandler().dispatchFakeMouseMoveEventSoonInQuad(quadForFakeMouseMoveEvent);
387
388    bool requiresPaintInvalidation = true;
389
390    if (!box().isMarquee() && box().view()->compositor()->inCompositingMode()) {
391        // Hits in virtual/gpu/fast/canvas/canvas-scroll-path-into-view.html.
392        DisableCompositingQueryAsserts disabler;
393        bool onlyScrolledCompositedLayers = scrollsOverflow()
394            && !layer()->hasVisibleNonLayerContent()
395            && !layer()->hasNonCompositedChild()
396            && !layer()->hasBlockSelectionGapBounds()
397            && box().style()->backgroundLayers().attachment() != LocalBackgroundAttachment;
398
399        if (usesCompositedScrolling() || onlyScrolledCompositedLayers)
400            requiresPaintInvalidation = false;
401    }
402
403    // Just schedule a full paint invalidation of our object.
404    if (requiresPaintInvalidation)
405        box().setShouldDoFullPaintInvalidation(true);
406
407    // Schedule the scroll DOM event.
408    if (box().node())
409        box().node()->document().enqueueScrollEventForNode(box().node());
410
411    if (AXObjectCache* cache = box().document().existingAXObjectCache())
412        cache->handleScrollPositionChanged(&box());
413
414    InspectorInstrumentation::didScrollLayer(&box());
415}
416
417IntPoint RenderLayerScrollableArea::scrollPosition() const
418{
419    return IntPoint(m_scrollOffset);
420}
421
422IntPoint RenderLayerScrollableArea::minimumScrollPosition() const
423{
424    return -scrollOrigin();
425}
426
427IntPoint RenderLayerScrollableArea::maximumScrollPosition() const
428{
429    if (!box().hasOverflowClip())
430        return -scrollOrigin();
431    return -scrollOrigin() + IntPoint(pixelSnappedScrollWidth(), pixelSnappedScrollHeight()) - enclosingIntRect(box().clientBoxRect()).size();
432}
433
434IntRect RenderLayerScrollableArea::visibleContentRect(IncludeScrollbarsInRect scrollbarInclusion) const
435{
436    int verticalScrollbarWidth = 0;
437    int horizontalScrollbarHeight = 0;
438    if (scrollbarInclusion == IncludeScrollbars) {
439        verticalScrollbarWidth = (verticalScrollbar() && !verticalScrollbar()->isOverlayScrollbar()) ? verticalScrollbar()->width() : 0;
440        horizontalScrollbarHeight = (horizontalScrollbar() && !horizontalScrollbar()->isOverlayScrollbar()) ? horizontalScrollbar()->height() : 0;
441    }
442
443    return IntRect(IntPoint(scrollXOffset(), scrollYOffset()),
444        IntSize(max(0, layer()->size().width() - verticalScrollbarWidth), max(0, layer()->size().height() - horizontalScrollbarHeight)));
445}
446
447int RenderLayerScrollableArea::visibleHeight() const
448{
449    return layer()->size().height();
450}
451
452int RenderLayerScrollableArea::visibleWidth() const
453{
454    return layer()->size().width();
455}
456
457IntSize RenderLayerScrollableArea::contentsSize() const
458{
459    return IntSize(scrollWidth(), scrollHeight());
460}
461
462IntSize RenderLayerScrollableArea::overhangAmount() const
463{
464    return IntSize();
465}
466
467IntPoint RenderLayerScrollableArea::lastKnownMousePosition() const
468{
469    return box().frame() ? box().frame()->eventHandler().lastKnownMousePosition() : IntPoint();
470}
471
472bool RenderLayerScrollableArea::shouldSuspendScrollAnimations() const
473{
474    RenderView* view = box().view();
475    if (!view)
476        return true;
477    return view->frameView()->shouldSuspendScrollAnimations();
478}
479
480bool RenderLayerScrollableArea::scrollbarsCanBeActive() const
481{
482    RenderView* view = box().view();
483    if (!view)
484        return false;
485    return view->frameView()->scrollbarsCanBeActive();
486}
487
488IntRect RenderLayerScrollableArea::scrollableAreaBoundingBox() const
489{
490    return box().absoluteBoundingBoxRect();
491}
492
493bool RenderLayerScrollableArea::userInputScrollable(ScrollbarOrientation orientation) const
494{
495    if (box().isIntristicallyScrollable(orientation))
496        return true;
497
498    EOverflow overflowStyle = (orientation == HorizontalScrollbar) ?
499        box().style()->overflowX() : box().style()->overflowY();
500    return (overflowStyle == OSCROLL || overflowStyle == OAUTO || overflowStyle == OOVERLAY);
501}
502
503bool RenderLayerScrollableArea::shouldPlaceVerticalScrollbarOnLeft() const
504{
505    return box().style()->shouldPlaceBlockDirectionScrollbarOnLogicalLeft();
506}
507
508int RenderLayerScrollableArea::pageStep(ScrollbarOrientation orientation) const
509{
510    int length = (orientation == HorizontalScrollbar) ?
511        box().pixelSnappedClientWidth() : box().pixelSnappedClientHeight();
512    int minPageStep = static_cast<float>(length) * ScrollableArea::minFractionToStepWhenPaging();
513    int pageStep = max(minPageStep, length - ScrollableArea::maxOverlapBetweenPages());
514
515    return max(pageStep, 1);
516}
517
518RenderBox& RenderLayerScrollableArea::box() const
519{
520    return *m_layer.renderBox();
521}
522
523RenderLayer* RenderLayerScrollableArea::layer() const
524{
525    return &m_layer;
526}
527
528LayoutUnit RenderLayerScrollableArea::scrollWidth() const
529{
530    if (m_scrollDimensionsDirty)
531        const_cast<RenderLayerScrollableArea*>(this)->computeScrollDimensions();
532    return m_overflowRect.width();
533}
534
535LayoutUnit RenderLayerScrollableArea::scrollHeight() const
536{
537    if (m_scrollDimensionsDirty)
538        const_cast<RenderLayerScrollableArea*>(this)->computeScrollDimensions();
539    return m_overflowRect.height();
540}
541
542int RenderLayerScrollableArea::pixelSnappedScrollWidth() const
543{
544    return snapSizeToPixel(scrollWidth(), box().clientLeft() + box().x());
545}
546
547int RenderLayerScrollableArea::pixelSnappedScrollHeight() const
548{
549    return snapSizeToPixel(scrollHeight(), box().clientTop() + box().y());
550}
551
552void RenderLayerScrollableArea::computeScrollDimensions()
553{
554    m_scrollDimensionsDirty = false;
555
556    m_overflowRect = box().layoutOverflowRect();
557    box().flipForWritingMode(m_overflowRect);
558
559    int scrollableLeftOverflow = m_overflowRect.x() - box().borderLeft() - (box().style()->shouldPlaceBlockDirectionScrollbarOnLogicalLeft() ? box().verticalScrollbarWidth() : 0);
560    int scrollableTopOverflow = m_overflowRect.y() - box().borderTop();
561    setScrollOrigin(IntPoint(-scrollableLeftOverflow, -scrollableTopOverflow));
562}
563
564void RenderLayerScrollableArea::scrollToOffset(const IntSize& scrollOffset, ScrollOffsetClamping clamp)
565{
566    IntSize newScrollOffset = clamp == ScrollOffsetClamped ? clampScrollOffset(scrollOffset) : scrollOffset;
567    if (newScrollOffset != adjustedScrollOffset())
568        scrollToOffsetWithoutAnimation(-scrollOrigin() + newScrollOffset);
569}
570
571void RenderLayerScrollableArea::updateAfterLayout()
572{
573    m_scrollDimensionsDirty = true;
574    IntSize originalScrollOffset = adjustedScrollOffset();
575
576    computeScrollDimensions();
577
578    if (!box().isMarquee()) {
579        // Layout may cause us to be at an invalid scroll position. In this case we need
580        // to pull our scroll offsets back to the max (or push them up to the min).
581        IntSize clampedScrollOffset = clampScrollOffset(adjustedScrollOffset());
582        if (clampedScrollOffset != adjustedScrollOffset())
583            scrollToOffset(clampedScrollOffset);
584    }
585
586    if (originalScrollOffset != adjustedScrollOffset())
587        scrollToOffsetWithoutAnimation(-scrollOrigin() + adjustedScrollOffset());
588
589    bool hasHorizontalOverflow = this->hasHorizontalOverflow();
590    bool hasVerticalOverflow = this->hasVerticalOverflow();
591
592    {
593        // Hits in compositing/overflow/automatically-opt-into-composited-scrolling-after-style-change.html.
594        DisableCompositingQueryAsserts disabler;
595
596        // overflow:scroll should just enable/disable.
597        if (box().style()->overflowX() == OSCROLL)
598            horizontalScrollbar()->setEnabled(hasHorizontalOverflow);
599        if (box().style()->overflowY() == OSCROLL)
600            verticalScrollbar()->setEnabled(hasVerticalOverflow);
601    }
602
603    // overflow:auto may need to lay out again if scrollbars got added/removed.
604    bool autoHorizontalScrollBarChanged = box().hasAutoHorizontalScrollbar() && (hasHorizontalScrollbar() != hasHorizontalOverflow);
605    bool autoVerticalScrollBarChanged = box().hasAutoVerticalScrollbar() && (hasVerticalScrollbar() != hasVerticalOverflow);
606
607    if (autoHorizontalScrollBarChanged || autoVerticalScrollBarChanged) {
608        if (box().hasAutoHorizontalScrollbar())
609            setHasHorizontalScrollbar(hasHorizontalOverflow);
610        if (box().hasAutoVerticalScrollbar())
611            setHasVerticalScrollbar(hasVerticalOverflow);
612
613        if (hasVerticalOverflow || hasHorizontalOverflow)
614            updateScrollCornerStyle();
615
616        layer()->updateSelfPaintingLayer();
617
618        // Force an update since we know the scrollbars have changed things.
619        if (box().document().hasAnnotatedRegions())
620            box().document().setAnnotatedRegionsDirty(true);
621
622        if (box().style()->overflowX() == OAUTO || box().style()->overflowY() == OAUTO) {
623            if (!m_inOverflowRelayout) {
624                // Our proprietary overflow: overlay value doesn't trigger a layout.
625                m_inOverflowRelayout = true;
626                SubtreeLayoutScope layoutScope(box());
627                layoutScope.setNeedsLayout(&box());
628                if (box().isRenderBlock()) {
629                    RenderBlock& block = toRenderBlock(box());
630                    block.scrollbarsChanged(autoHorizontalScrollBarChanged, autoVerticalScrollBarChanged);
631                    block.layoutBlock(true);
632                } else {
633                    box().layout();
634                }
635                m_inOverflowRelayout = false;
636            }
637        }
638    }
639
640    {
641        // Hits in compositing/overflow/automatically-opt-into-composited-scrolling-after-style-change.html.
642        DisableCompositingQueryAsserts disabler;
643
644        // Set up the range (and page step/line step).
645        if (Scrollbar* horizontalScrollbar = this->horizontalScrollbar()) {
646            int clientWidth = box().pixelSnappedClientWidth();
647            horizontalScrollbar->setProportion(clientWidth, overflowRect().width());
648        }
649        if (Scrollbar* verticalScrollbar = this->verticalScrollbar()) {
650            int clientHeight = box().pixelSnappedClientHeight();
651            verticalScrollbar->setProportion(clientHeight, overflowRect().height());
652        }
653    }
654
655    bool hasOverflow = hasScrollableHorizontalOverflow() || hasScrollableVerticalOverflow();
656    updateScrollableAreaSet(hasOverflow);
657
658    if (hasOverflow) {
659        DisableCompositingQueryAsserts disabler;
660        positionOverflowControls(IntSize());
661    }
662}
663
664bool RenderLayerScrollableArea::hasHorizontalOverflow() const
665{
666    ASSERT(!m_scrollDimensionsDirty);
667
668    return pixelSnappedScrollWidth() > box().pixelSnappedClientWidth();
669}
670
671bool RenderLayerScrollableArea::hasVerticalOverflow() const
672{
673    ASSERT(!m_scrollDimensionsDirty);
674
675    return pixelSnappedScrollHeight() > box().pixelSnappedClientHeight();
676}
677
678bool RenderLayerScrollableArea::hasScrollableHorizontalOverflow() const
679{
680    return hasHorizontalOverflow() && box().scrollsOverflowX();
681}
682
683bool RenderLayerScrollableArea::hasScrollableVerticalOverflow() const
684{
685    return hasVerticalOverflow() && box().scrollsOverflowY();
686}
687
688static bool overflowRequiresScrollbar(EOverflow overflow)
689{
690    return overflow == OSCROLL;
691}
692
693static bool overflowDefinesAutomaticScrollbar(EOverflow overflow)
694{
695    return overflow == OAUTO || overflow == OOVERLAY;
696}
697
698// This function returns true if the given box requires overflow scrollbars (as
699// opposed to the 'viewport' scrollbars managed by the RenderLayerCompositor).
700// FIXME: we should use the same scrolling machinery for both the viewport and
701// overflow. Currently, we need to avoid producing scrollbars here if they'll be
702// handled externally in the RLC.
703static bool canHaveOverflowScrollbars(const RenderBox& box)
704{
705    return !box.isRenderView() && box.document().viewportDefiningElement() != box.node();
706}
707
708void RenderLayerScrollableArea::updateAfterStyleChange(const RenderStyle* oldStyle)
709{
710    if (!canHaveOverflowScrollbars(box()))
711        return;
712
713    if (!m_scrollDimensionsDirty)
714        updateScrollableAreaSet(hasScrollableHorizontalOverflow() || hasScrollableVerticalOverflow());
715
716    EOverflow overflowX = box().style()->overflowX();
717    EOverflow overflowY = box().style()->overflowY();
718
719    // To avoid doing a relayout in updateScrollbarsAfterLayout, we try to keep any automatic scrollbar that was already present.
720    bool needsHorizontalScrollbar = (hasHorizontalScrollbar() && overflowDefinesAutomaticScrollbar(overflowX)) || overflowRequiresScrollbar(overflowX);
721    bool needsVerticalScrollbar = (hasVerticalScrollbar() && overflowDefinesAutomaticScrollbar(overflowY)) || overflowRequiresScrollbar(overflowY);
722    setHasHorizontalScrollbar(needsHorizontalScrollbar);
723    setHasVerticalScrollbar(needsVerticalScrollbar);
724
725    // With overflow: scroll, scrollbars are always visible but may be disabled.
726    // When switching to another value, we need to re-enable them (see bug 11985).
727    if (needsHorizontalScrollbar && oldStyle && oldStyle->overflowX() == OSCROLL && overflowX != OSCROLL) {
728        ASSERT(hasHorizontalScrollbar());
729        m_hBar->setEnabled(true);
730    }
731
732    if (needsVerticalScrollbar && oldStyle && oldStyle->overflowY() == OSCROLL && overflowY != OSCROLL) {
733        ASSERT(hasVerticalScrollbar());
734        m_vBar->setEnabled(true);
735    }
736
737    // FIXME: Need to detect a swap from custom to native scrollbars (and vice versa).
738    if (m_hBar)
739        m_hBar->styleChanged();
740    if (m_vBar)
741        m_vBar->styleChanged();
742
743    updateScrollCornerStyle();
744    updateResizerAreaSet();
745    updateResizerStyle();
746}
747
748bool RenderLayerScrollableArea::updateAfterCompositingChange()
749{
750    layer()->updateScrollingStateAfterCompositingChange();
751    const bool layersChanged = m_topmostScrollChild != m_nextTopmostScrollChild;
752    m_topmostScrollChild = m_nextTopmostScrollChild;
753    m_nextTopmostScrollChild = nullptr;
754    return layersChanged;
755}
756
757void RenderLayerScrollableArea::updateAfterOverflowRecalc()
758{
759    computeScrollDimensions();
760    if (Scrollbar* horizontalScrollbar = this->horizontalScrollbar()) {
761        int clientWidth = box().pixelSnappedClientWidth();
762        horizontalScrollbar->setProportion(clientWidth, overflowRect().width());
763    }
764    if (Scrollbar* verticalScrollbar = this->verticalScrollbar()) {
765        int clientHeight = box().pixelSnappedClientHeight();
766        verticalScrollbar->setProportion(clientHeight, overflowRect().height());
767    }
768
769    bool hasHorizontalOverflow = this->hasHorizontalOverflow();
770    bool hasVerticalOverflow = this->hasVerticalOverflow();
771    bool autoHorizontalScrollBarChanged = box().hasAutoHorizontalScrollbar() && (hasHorizontalScrollbar() != hasHorizontalOverflow);
772    bool autoVerticalScrollBarChanged = box().hasAutoVerticalScrollbar() && (hasVerticalScrollbar() != hasVerticalOverflow);
773    if (autoHorizontalScrollBarChanged || autoVerticalScrollBarChanged)
774        box().setNeedsLayoutAndFullPaintInvalidation();
775}
776
777IntSize RenderLayerScrollableArea::clampScrollOffset(const IntSize& scrollOffset) const
778{
779    int maxX = scrollWidth() - box().pixelSnappedClientWidth();
780    int maxY = scrollHeight() - box().pixelSnappedClientHeight();
781
782    int x = std::max(std::min(scrollOffset.width(), maxX), 0);
783    int y = std::max(std::min(scrollOffset.height(), maxY), 0);
784    return IntSize(x, y);
785}
786
787IntRect RenderLayerScrollableArea::rectForHorizontalScrollbar(const IntRect& borderBoxRect) const
788{
789    if (!m_hBar)
790        return IntRect();
791
792    const IntRect& scrollCorner = scrollCornerRect();
793
794    return IntRect(horizontalScrollbarStart(borderBoxRect.x()),
795        borderBoxRect.maxY() - box().borderBottom() - m_hBar->height(),
796        borderBoxRect.width() - (box().borderLeft() + box().borderRight()) - scrollCorner.width(),
797        m_hBar->height());
798}
799
800IntRect RenderLayerScrollableArea::rectForVerticalScrollbar(const IntRect& borderBoxRect) const
801{
802    if (!m_vBar)
803        return IntRect();
804
805    const IntRect& scrollCorner = scrollCornerRect();
806
807    return IntRect(verticalScrollbarStart(borderBoxRect.x(), borderBoxRect.maxX()),
808        borderBoxRect.y() + box().borderTop(),
809        m_vBar->width(),
810        borderBoxRect.height() - (box().borderTop() + box().borderBottom()) - scrollCorner.height());
811}
812
813LayoutUnit RenderLayerScrollableArea::verticalScrollbarStart(int minX, int maxX) const
814{
815    if (box().style()->shouldPlaceBlockDirectionScrollbarOnLogicalLeft())
816        return minX + box().borderLeft();
817    return maxX - box().borderRight() - m_vBar->width();
818}
819
820LayoutUnit RenderLayerScrollableArea::horizontalScrollbarStart(int minX) const
821{
822    int x = minX + box().borderLeft();
823    if (box().style()->shouldPlaceBlockDirectionScrollbarOnLogicalLeft())
824        x += m_vBar ? m_vBar->width() : resizerCornerRect(box().pixelSnappedBorderBoxRect(), ResizerForPointer).width();
825    return x;
826}
827
828IntSize RenderLayerScrollableArea::scrollbarOffset(const Scrollbar* scrollbar) const
829{
830    if (scrollbar == m_vBar.get())
831        return IntSize(verticalScrollbarStart(0, box().width()), box().borderTop());
832
833    if (scrollbar == m_hBar.get())
834        return IntSize(horizontalScrollbarStart(0), box().height() - box().borderBottom() - scrollbar->height());
835
836    ASSERT_NOT_REACHED();
837    return IntSize();
838}
839
840static inline RenderObject* rendererForScrollbar(RenderObject& renderer)
841{
842    if (Node* node = renderer.node()) {
843        if (ShadowRoot* shadowRoot = node->containingShadowRoot()) {
844            if (shadowRoot->type() == ShadowRoot::UserAgentShadowRoot)
845                return shadowRoot->host()->renderer();
846        }
847    }
848
849    return &renderer;
850}
851
852PassRefPtr<Scrollbar> RenderLayerScrollableArea::createScrollbar(ScrollbarOrientation orientation)
853{
854    RefPtr<Scrollbar> widget;
855    RenderObject* actualRenderer = rendererForScrollbar(box());
856    bool hasCustomScrollbarStyle = actualRenderer->isBox() && actualRenderer->style()->hasPseudoStyle(SCROLLBAR);
857    if (hasCustomScrollbarStyle) {
858        widget = RenderScrollbar::createCustomScrollbar(this, orientation, actualRenderer->node());
859    } else {
860        ScrollbarControlSize scrollbarSize = RegularScrollbar;
861        if (actualRenderer->style()->hasAppearance())
862            scrollbarSize = RenderTheme::theme().scrollbarControlSizeForPart(actualRenderer->style()->appearance());
863        widget = Scrollbar::create(this, orientation, scrollbarSize);
864        if (orientation == HorizontalScrollbar)
865            didAddScrollbar(widget.get(), HorizontalScrollbar);
866        else
867            didAddScrollbar(widget.get(), VerticalScrollbar);
868    }
869    box().document().view()->addChild(widget.get());
870    return widget.release();
871}
872
873void RenderLayerScrollableArea::destroyScrollbar(ScrollbarOrientation orientation)
874{
875    RefPtr<Scrollbar>& scrollbar = orientation == HorizontalScrollbar ? m_hBar : m_vBar;
876    if (!scrollbar)
877        return;
878
879    if (!scrollbar->isCustomScrollbar())
880        willRemoveScrollbar(scrollbar.get(), orientation);
881
882    scrollbar->removeFromParent();
883    scrollbar->disconnectFromScrollableArea();
884    scrollbar = nullptr;
885}
886
887void RenderLayerScrollableArea::setHasHorizontalScrollbar(bool hasScrollbar)
888{
889    if (hasScrollbar == hasHorizontalScrollbar())
890        return;
891
892    if (hasScrollbar) {
893        // This doesn't hit in any tests, but since the equivalent code in setHasVerticalScrollbar
894        // does, presumably this code does as well.
895        DisableCompositingQueryAsserts disabler;
896        m_hBar = createScrollbar(HorizontalScrollbar);
897    } else {
898        destroyScrollbar(HorizontalScrollbar);
899    }
900
901    // Destroying or creating one bar can cause our scrollbar corner to come and go. We need to update the opposite scrollbar's style.
902    if (m_hBar)
903        m_hBar->styleChanged();
904    if (m_vBar)
905        m_vBar->styleChanged();
906
907    // Force an update since we know the scrollbars have changed things.
908    if (box().document().hasAnnotatedRegions())
909        box().document().setAnnotatedRegionsDirty(true);
910}
911
912void RenderLayerScrollableArea::setHasVerticalScrollbar(bool hasScrollbar)
913{
914    if (hasScrollbar == hasVerticalScrollbar())
915        return;
916
917    if (hasScrollbar) {
918        // Hits in compositing/overflow/automatically-opt-into-composited-scrolling-after-style-change.html
919        DisableCompositingQueryAsserts disabler;
920        m_vBar = createScrollbar(VerticalScrollbar);
921    } else {
922        destroyScrollbar(VerticalScrollbar);
923    }
924
925    // Destroying or creating one bar can cause our scrollbar corner to come and go. We need to update the opposite scrollbar's style.
926    if (m_hBar)
927        m_hBar->styleChanged();
928    if (m_vBar)
929        m_vBar->styleChanged();
930
931    // Force an update since we know the scrollbars have changed things.
932    if (box().document().hasAnnotatedRegions())
933        box().document().setAnnotatedRegionsDirty(true);
934}
935
936int RenderLayerScrollableArea::verticalScrollbarWidth(OverlayScrollbarSizeRelevancy relevancy) const
937{
938    if (!m_vBar || (m_vBar->isOverlayScrollbar() && (relevancy == IgnoreOverlayScrollbarSize || !m_vBar->shouldParticipateInHitTesting())))
939        return 0;
940    return m_vBar->width();
941}
942
943int RenderLayerScrollableArea::horizontalScrollbarHeight(OverlayScrollbarSizeRelevancy relevancy) const
944{
945    if (!m_hBar || (m_hBar->isOverlayScrollbar() && (relevancy == IgnoreOverlayScrollbarSize || !m_hBar->shouldParticipateInHitTesting())))
946        return 0;
947    return m_hBar->height();
948}
949
950void RenderLayerScrollableArea::positionOverflowControls(const IntSize& offsetFromRoot)
951{
952    if (!hasScrollbar() && !box().canResize())
953        return;
954
955    const IntRect borderBox = box().pixelSnappedBorderBoxRect();
956    if (Scrollbar* verticalScrollbar = this->verticalScrollbar()) {
957        IntRect vBarRect = rectForVerticalScrollbar(borderBox);
958        vBarRect.move(offsetFromRoot);
959        verticalScrollbar->setFrameRect(vBarRect);
960    }
961
962    if (Scrollbar* horizontalScrollbar = this->horizontalScrollbar()) {
963        IntRect hBarRect = rectForHorizontalScrollbar(borderBox);
964        hBarRect.move(offsetFromRoot);
965        horizontalScrollbar->setFrameRect(hBarRect);
966    }
967
968    const IntRect& scrollCorner = scrollCornerRect();
969    if (m_scrollCorner)
970        m_scrollCorner->setFrameRect(scrollCorner);
971
972    if (m_resizer)
973        m_resizer->setFrameRect(resizerCornerRect(borderBox, ResizerForPointer));
974
975    // FIXME, this should eventually be removed, once we are certain that composited
976    // controls get correctly positioned on a compositor update. For now, conservatively
977    // leaving this unchanged.
978    if (layer()->hasCompositedLayerMapping())
979        layer()->compositedLayerMapping()->positionOverflowControlsLayers(offsetFromRoot);
980}
981
982void RenderLayerScrollableArea::updateScrollCornerStyle()
983{
984    if (!m_scrollCorner && !hasScrollbar())
985        return;
986    if (!m_scrollCorner && hasOverlayScrollbars())
987        return;
988
989    RenderObject* actualRenderer = rendererForScrollbar(box());
990    RefPtr<RenderStyle> corner = box().hasOverflowClip() ? actualRenderer->getUncachedPseudoStyle(PseudoStyleRequest(SCROLLBAR_CORNER), actualRenderer->style()) : PassRefPtr<RenderStyle>(nullptr);
991    if (corner) {
992        if (!m_scrollCorner) {
993            m_scrollCorner = RenderScrollbarPart::createAnonymous(&box().document());
994            m_scrollCorner->setParent(&box());
995        }
996        m_scrollCorner->setStyle(corner.release());
997    } else if (m_scrollCorner) {
998        m_scrollCorner->destroy();
999        m_scrollCorner = nullptr;
1000    }
1001}
1002
1003void RenderLayerScrollableArea::paintOverflowControls(GraphicsContext* context, const IntPoint& paintOffset, const IntRect& damageRect, bool paintingOverlayControls)
1004{
1005    // Don't do anything if we have no overflow.
1006    if (!box().hasOverflowClip())
1007        return;
1008
1009    IntPoint adjustedPaintOffset = paintOffset;
1010    if (paintingOverlayControls)
1011        adjustedPaintOffset = m_cachedOverlayScrollbarOffset;
1012
1013    // Move the scrollbar widgets if necessary. We normally move and resize widgets during layout,
1014    // but sometimes widgets can move without layout occurring (most notably when you scroll a
1015    // document that contains fixed positioned elements).
1016    positionOverflowControls(toIntSize(adjustedPaintOffset));
1017
1018    // Overlay scrollbars paint in a second pass through the layer tree so that they will paint
1019    // on top of everything else. If this is the normal painting pass, paintingOverlayControls
1020    // will be false, and we should just tell the root layer that there are overlay scrollbars
1021    // that need to be painted. That will cause the second pass through the layer tree to run,
1022    // and we'll paint the scrollbars then. In the meantime, cache tx and ty so that the
1023    // second pass doesn't need to re-enter the RenderTree to get it right.
1024    if (hasOverlayScrollbars() && !paintingOverlayControls) {
1025        m_cachedOverlayScrollbarOffset = paintOffset;
1026        // It's not necessary to do the second pass if the scrollbars paint into layers.
1027        if ((m_hBar && layerForHorizontalScrollbar()) || (m_vBar && layerForVerticalScrollbar()))
1028            return;
1029        IntRect localDamgeRect = damageRect;
1030        localDamgeRect.moveBy(-paintOffset);
1031        if (!overflowControlsIntersectRect(localDamgeRect))
1032            return;
1033
1034        RenderView* renderView = box().view();
1035
1036        RenderLayer* paintingRoot = layer()->enclosingLayerWithCompositedLayerMapping(IncludeSelf);
1037        if (!paintingRoot)
1038            paintingRoot = renderView->layer();
1039
1040        paintingRoot->setContainsDirtyOverlayScrollbars(true);
1041        return;
1042    }
1043
1044    // This check is required to avoid painting custom CSS scrollbars twice.
1045    if (paintingOverlayControls && !hasOverlayScrollbars())
1046        return;
1047
1048    // Now that we're sure the scrollbars are in the right place, paint them.
1049    if (m_hBar && !layerForHorizontalScrollbar())
1050        m_hBar->paint(context, damageRect);
1051    if (m_vBar && !layerForVerticalScrollbar())
1052        m_vBar->paint(context, damageRect);
1053
1054    if (layerForScrollCorner())
1055        return;
1056
1057    // We fill our scroll corner with white if we have a scrollbar that doesn't run all the way up to the
1058    // edge of the box.
1059    paintScrollCorner(context, adjustedPaintOffset, damageRect);
1060
1061    // Paint our resizer last, since it sits on top of the scroll corner.
1062    paintResizer(context, adjustedPaintOffset, damageRect);
1063}
1064
1065void RenderLayerScrollableArea::paintScrollCorner(GraphicsContext* context, const IntPoint& paintOffset, const IntRect& damageRect)
1066{
1067    IntRect absRect = scrollCornerRect();
1068    absRect.moveBy(paintOffset);
1069    if (!absRect.intersects(damageRect))
1070        return;
1071
1072    if (m_scrollCorner) {
1073        m_scrollCorner->paintIntoRect(context, paintOffset, absRect);
1074        return;
1075    }
1076
1077    // We don't want to paint white if we have overlay scrollbars, since we need
1078    // to see what is behind it.
1079    if (!hasOverlayScrollbars())
1080        context->fillRect(absRect, Color::white);
1081}
1082
1083bool RenderLayerScrollableArea::hitTestOverflowControls(HitTestResult& result, const IntPoint& localPoint)
1084{
1085    if (!hasScrollbar() && !box().canResize())
1086        return false;
1087
1088    IntRect resizeControlRect;
1089    if (box().style()->resize() != RESIZE_NONE) {
1090        resizeControlRect = resizerCornerRect(box().pixelSnappedBorderBoxRect(), ResizerForPointer);
1091        if (resizeControlRect.contains(localPoint))
1092            return true;
1093    }
1094
1095    int resizeControlSize = max(resizeControlRect.height(), 0);
1096    if (m_vBar && m_vBar->shouldParticipateInHitTesting()) {
1097        LayoutRect vBarRect(verticalScrollbarStart(0, box().width()),
1098            box().borderTop(),
1099            m_vBar->width(),
1100            box().height() - (box().borderTop() + box().borderBottom()) - (m_hBar ? m_hBar->height() : resizeControlSize));
1101        if (vBarRect.contains(localPoint)) {
1102            result.setScrollbar(m_vBar.get());
1103            return true;
1104        }
1105    }
1106
1107    resizeControlSize = max(resizeControlRect.width(), 0);
1108    if (m_hBar && m_hBar->shouldParticipateInHitTesting()) {
1109        LayoutRect hBarRect(horizontalScrollbarStart(0),
1110            box().height() - box().borderBottom() - m_hBar->height(),
1111            box().width() - (box().borderLeft() + box().borderRight()) - (m_vBar ? m_vBar->width() : resizeControlSize),
1112            m_hBar->height());
1113        if (hBarRect.contains(localPoint)) {
1114            result.setScrollbar(m_hBar.get());
1115            return true;
1116        }
1117    }
1118
1119    // FIXME: We should hit test the m_scrollCorner and pass it back through the result.
1120
1121    return false;
1122}
1123
1124IntRect RenderLayerScrollableArea::resizerCornerRect(const IntRect& bounds, ResizerHitTestType resizerHitTestType) const
1125{
1126    if (box().style()->resize() == RESIZE_NONE)
1127        return IntRect();
1128    IntRect corner = cornerRect(box().style(), horizontalScrollbar(), verticalScrollbar(), bounds);
1129
1130    if (resizerHitTestType == ResizerForTouch) {
1131        // We make the resizer virtually larger for touch hit testing. With the
1132        // expanding ratio k = ResizerControlExpandRatioForTouch, we first move
1133        // the resizer rect (of width w & height h), by (-w * (k-1), -h * (k-1)),
1134        // then expand the rect by new_w/h = w/h * k.
1135        int expandRatio = ResizerControlExpandRatioForTouch - 1;
1136        corner.move(-corner.width() * expandRatio, -corner.height() * expandRatio);
1137        corner.expand(corner.width() * expandRatio, corner.height() * expandRatio);
1138    }
1139
1140    return corner;
1141}
1142
1143IntRect RenderLayerScrollableArea::scrollCornerAndResizerRect() const
1144{
1145    IntRect scrollCornerAndResizer = scrollCornerRect();
1146    if (scrollCornerAndResizer.isEmpty())
1147        scrollCornerAndResizer = resizerCornerRect(box().pixelSnappedBorderBoxRect(), ResizerForPointer);
1148    return scrollCornerAndResizer;
1149}
1150
1151bool RenderLayerScrollableArea::overflowControlsIntersectRect(const IntRect& localRect) const
1152{
1153    const IntRect borderBox = box().pixelSnappedBorderBoxRect();
1154
1155    if (rectForHorizontalScrollbar(borderBox).intersects(localRect))
1156        return true;
1157
1158    if (rectForVerticalScrollbar(borderBox).intersects(localRect))
1159        return true;
1160
1161    if (scrollCornerRect().intersects(localRect))
1162        return true;
1163
1164    if (resizerCornerRect(borderBox, ResizerForPointer).intersects(localRect))
1165        return true;
1166
1167    return false;
1168}
1169
1170void RenderLayerScrollableArea::paintResizer(GraphicsContext* context, const IntPoint& paintOffset, const IntRect& damageRect)
1171{
1172    if (box().style()->resize() == RESIZE_NONE)
1173        return;
1174
1175    IntRect absRect = resizerCornerRect(box().pixelSnappedBorderBoxRect(), ResizerForPointer);
1176    absRect.moveBy(paintOffset);
1177    if (!absRect.intersects(damageRect))
1178        return;
1179
1180    if (m_resizer) {
1181        m_resizer->paintIntoRect(context, paintOffset, absRect);
1182        return;
1183    }
1184
1185    drawPlatformResizerImage(context, absRect);
1186
1187    // Draw a frame around the resizer (1px grey line) if there are any scrollbars present.
1188    // Clipping will exclude the right and bottom edges of this frame.
1189    if (!hasOverlayScrollbars() && hasScrollbar()) {
1190        GraphicsContextStateSaver stateSaver(*context);
1191        context->clip(absRect);
1192        IntRect largerCorner = absRect;
1193        largerCorner.setSize(IntSize(largerCorner.width() + 1, largerCorner.height() + 1));
1194        context->setStrokeColor(Color(217, 217, 217));
1195        context->setStrokeThickness(1.0f);
1196        context->setFillColor(Color::transparent);
1197        context->drawRect(largerCorner);
1198    }
1199}
1200
1201bool RenderLayerScrollableArea::isPointInResizeControl(const IntPoint& absolutePoint, ResizerHitTestType resizerHitTestType) const
1202{
1203    if (!box().canResize())
1204        return false;
1205
1206    IntPoint localPoint = roundedIntPoint(box().absoluteToLocal(absolutePoint, UseTransforms));
1207    IntRect localBounds(0, 0, box().pixelSnappedWidth(), box().pixelSnappedHeight());
1208    return resizerCornerRect(localBounds, resizerHitTestType).contains(localPoint);
1209}
1210
1211bool RenderLayerScrollableArea::hitTestResizerInFragments(const LayerFragments& layerFragments, const HitTestLocation& hitTestLocation) const
1212{
1213    if (!box().canResize())
1214        return false;
1215
1216    if (layerFragments.isEmpty())
1217        return false;
1218
1219    for (int i = layerFragments.size() - 1; i >= 0; --i) {
1220        const LayerFragment& fragment = layerFragments.at(i);
1221        if (fragment.backgroundRect.intersects(hitTestLocation) && resizerCornerRect(pixelSnappedIntRect(fragment.layerBounds), ResizerForPointer).contains(hitTestLocation.roundedPoint()))
1222            return true;
1223    }
1224
1225    return false;
1226}
1227
1228void RenderLayerScrollableArea::updateResizerAreaSet()
1229{
1230    LocalFrame* frame = box().frame();
1231    if (!frame)
1232        return;
1233    FrameView* frameView = frame->view();
1234    if (!frameView)
1235        return;
1236    if (box().canResize())
1237        frameView->addResizerArea(box());
1238    else
1239        frameView->removeResizerArea(box());
1240}
1241
1242void RenderLayerScrollableArea::updateResizerStyle()
1243{
1244    if (!m_resizer && !box().canResize())
1245        return;
1246
1247    RenderObject* actualRenderer = rendererForScrollbar(box());
1248    RefPtr<RenderStyle> resizer = box().hasOverflowClip() ? actualRenderer->getUncachedPseudoStyle(PseudoStyleRequest(RESIZER), actualRenderer->style()) : PassRefPtr<RenderStyle>(nullptr);
1249    if (resizer) {
1250        if (!m_resizer) {
1251            m_resizer = RenderScrollbarPart::createAnonymous(&box().document());
1252            m_resizer->setParent(&box());
1253        }
1254        m_resizer->setStyle(resizer.release());
1255    } else if (m_resizer) {
1256        m_resizer->destroy();
1257        m_resizer = nullptr;
1258    }
1259}
1260
1261void RenderLayerScrollableArea::drawPlatformResizerImage(GraphicsContext* context, IntRect resizerCornerRect)
1262{
1263    float deviceScaleFactor = blink::deviceScaleFactor(box().frame());
1264
1265    RefPtr<Image> resizeCornerImage;
1266    IntSize cornerResizerSize;
1267    if (deviceScaleFactor >= 2) {
1268        DEFINE_STATIC_REF(Image, resizeCornerImageHiRes, (Image::loadPlatformResource("textAreaResizeCorner@2x")));
1269        resizeCornerImage = resizeCornerImageHiRes;
1270        cornerResizerSize = resizeCornerImage->size();
1271        cornerResizerSize.scale(0.5f);
1272    } else {
1273        DEFINE_STATIC_REF(Image, resizeCornerImageLoRes, (Image::loadPlatformResource("textAreaResizeCorner")));
1274        resizeCornerImage = resizeCornerImageLoRes;
1275        cornerResizerSize = resizeCornerImage->size();
1276    }
1277
1278    if (box().style()->shouldPlaceBlockDirectionScrollbarOnLogicalLeft()) {
1279        context->save();
1280        context->translate(resizerCornerRect.x() + cornerResizerSize.width(), resizerCornerRect.y() + resizerCornerRect.height() - cornerResizerSize.height());
1281        context->scale(-1.0, 1.0);
1282        context->drawImage(resizeCornerImage.get(), IntRect(IntPoint(), cornerResizerSize));
1283        context->restore();
1284        return;
1285    }
1286    IntRect imageRect(resizerCornerRect.maxXMaxYCorner() - cornerResizerSize, cornerResizerSize);
1287    context->drawImage(resizeCornerImage.get(), imageRect);
1288}
1289
1290IntSize RenderLayerScrollableArea::offsetFromResizeCorner(const IntPoint& absolutePoint) const
1291{
1292    // Currently the resize corner is either the bottom right corner or the bottom left corner.
1293    // FIXME: This assumes the location is 0, 0. Is this guaranteed to always be the case?
1294    IntSize elementSize = layer()->size();
1295    if (box().style()->shouldPlaceBlockDirectionScrollbarOnLogicalLeft())
1296        elementSize.setWidth(0);
1297    IntPoint resizerPoint = IntPoint(elementSize);
1298    IntPoint localPoint = roundedIntPoint(box().absoluteToLocal(absolutePoint, UseTransforms));
1299    return localPoint - resizerPoint;
1300}
1301
1302void RenderLayerScrollableArea::resize(const PlatformEvent& evt, const LayoutSize& oldOffset)
1303{
1304    // FIXME: This should be possible on generated content but is not right now.
1305    if (!inResizeMode() || !box().canResize() || !box().node())
1306        return;
1307
1308    ASSERT(box().node()->isElementNode());
1309    Element* element = toElement(box().node());
1310
1311    Document& document = element->document();
1312
1313    IntPoint pos;
1314    const PlatformGestureEvent* gevt = 0;
1315
1316    switch (evt.type()) {
1317    case PlatformEvent::MouseMoved:
1318        if (!document.frame()->eventHandler().mousePressed())
1319            return;
1320        pos = static_cast<const PlatformMouseEvent*>(&evt)->position();
1321        break;
1322    case PlatformEvent::GestureScrollUpdate:
1323    case PlatformEvent::GestureScrollUpdateWithoutPropagation:
1324        pos = static_cast<const PlatformGestureEvent*>(&evt)->position();
1325        gevt = static_cast<const PlatformGestureEvent*>(&evt);
1326        pos = gevt->position();
1327        pos.move(gevt->deltaX(), gevt->deltaY());
1328        break;
1329    default:
1330        ASSERT_NOT_REACHED();
1331    }
1332
1333    float zoomFactor = box().style()->effectiveZoom();
1334
1335    LayoutSize newOffset = offsetFromResizeCorner(document.view()->windowToContents(pos));
1336    newOffset.setWidth(newOffset.width() / zoomFactor);
1337    newOffset.setHeight(newOffset.height() / zoomFactor);
1338
1339    LayoutSize currentSize = LayoutSize(box().width() / zoomFactor, box().height() / zoomFactor);
1340    LayoutSize minimumSize = element->minimumSizeForResizing().shrunkTo(currentSize);
1341    element->setMinimumSizeForResizing(minimumSize);
1342
1343    LayoutSize adjustedOldOffset = LayoutSize(oldOffset.width() / zoomFactor, oldOffset.height() / zoomFactor);
1344    if (box().style()->shouldPlaceBlockDirectionScrollbarOnLogicalLeft()) {
1345        newOffset.setWidth(-newOffset.width());
1346        adjustedOldOffset.setWidth(-adjustedOldOffset.width());
1347    }
1348
1349    LayoutSize difference = (currentSize + newOffset - adjustedOldOffset).expandedTo(minimumSize) - currentSize;
1350
1351    bool isBoxSizingBorder = box().style()->boxSizing() == BORDER_BOX;
1352
1353    EResize resize = box().style()->resize();
1354    if (resize != RESIZE_VERTICAL && difference.width()) {
1355        if (element->isFormControlElement()) {
1356            // Make implicit margins from the theme explicit (see <http://bugs.webkit.org/show_bug.cgi?id=9547>).
1357            element->setInlineStyleProperty(CSSPropertyMarginLeft, box().marginLeft() / zoomFactor, CSSPrimitiveValue::CSS_PX);
1358            element->setInlineStyleProperty(CSSPropertyMarginRight, box().marginRight() / zoomFactor, CSSPrimitiveValue::CSS_PX);
1359        }
1360        LayoutUnit baseWidth = box().width() - (isBoxSizingBorder ? LayoutUnit() : box().borderAndPaddingWidth());
1361        baseWidth = baseWidth / zoomFactor;
1362        element->setInlineStyleProperty(CSSPropertyWidth, roundToInt(baseWidth + difference.width()), CSSPrimitiveValue::CSS_PX);
1363    }
1364
1365    if (resize != RESIZE_HORIZONTAL && difference.height()) {
1366        if (element->isFormControlElement()) {
1367            // Make implicit margins from the theme explicit (see <http://bugs.webkit.org/show_bug.cgi?id=9547>).
1368            element->setInlineStyleProperty(CSSPropertyMarginTop, box().marginTop() / zoomFactor, CSSPrimitiveValue::CSS_PX);
1369            element->setInlineStyleProperty(CSSPropertyMarginBottom, box().marginBottom() / zoomFactor, CSSPrimitiveValue::CSS_PX);
1370        }
1371        LayoutUnit baseHeight = box().height() - (isBoxSizingBorder ? LayoutUnit() : box().borderAndPaddingHeight());
1372        baseHeight = baseHeight / zoomFactor;
1373        element->setInlineStyleProperty(CSSPropertyHeight, roundToInt(baseHeight + difference.height()), CSSPrimitiveValue::CSS_PX);
1374    }
1375
1376    document.updateLayout();
1377
1378    // FIXME (Radar 4118564): We should also autoscroll the window as necessary to keep the point under the cursor in view.
1379}
1380
1381LayoutRect RenderLayerScrollableArea::exposeRect(const LayoutRect& rect, const ScrollAlignment& alignX, const ScrollAlignment& alignY)
1382{
1383    LayoutRect localExposeRect(box().absoluteToLocalQuad(FloatQuad(FloatRect(rect)), UseTransforms).boundingBox());
1384    LayoutRect layerBounds(0, 0, box().clientWidth(), box().clientHeight());
1385    LayoutRect r = ScrollAlignment::getRectToExpose(layerBounds, localExposeRect, alignX, alignY);
1386
1387    IntSize clampedScrollOffset = clampScrollOffset(adjustedScrollOffset() + toIntSize(roundedIntRect(r).location()));
1388    if (clampedScrollOffset == adjustedScrollOffset())
1389        return rect;
1390
1391    IntSize oldScrollOffset = adjustedScrollOffset();
1392    scrollToOffset(clampedScrollOffset);
1393    IntSize scrollOffsetDifference = adjustedScrollOffset() - oldScrollOffset;
1394    localExposeRect.move(-scrollOffsetDifference);
1395    return LayoutRect(box().localToAbsoluteQuad(FloatQuad(FloatRect(localExposeRect)), UseTransforms).boundingBox());
1396}
1397
1398void RenderLayerScrollableArea::updateScrollableAreaSet(bool hasOverflow)
1399{
1400    LocalFrame* frame = box().frame();
1401    if (!frame)
1402        return;
1403
1404    FrameView* frameView = frame->view();
1405    if (!frameView)
1406        return;
1407
1408    // FIXME: Does this need to be fixed later for OOPI?
1409    bool isVisibleToHitTest = box().visibleToHitTesting();
1410    if (HTMLFrameOwnerElement* owner = frame->deprecatedLocalOwner())
1411        isVisibleToHitTest &= owner->renderer() && owner->renderer()->visibleToHitTesting();
1412
1413    bool didScrollOverflow = m_scrollsOverflow;
1414
1415    m_scrollsOverflow = hasOverflow && isVisibleToHitTest;
1416    if (didScrollOverflow == scrollsOverflow())
1417        return;
1418
1419    if (m_scrollsOverflow)
1420        frameView->addScrollableArea(this);
1421    else
1422        frameView->removeScrollableArea(this);
1423}
1424
1425void RenderLayerScrollableArea::updateCompositingLayersAfterScroll()
1426{
1427    RenderLayerCompositor* compositor = box().view()->compositor();
1428    if (compositor->inCompositingMode()) {
1429        if (usesCompositedScrolling()) {
1430            DisableCompositingQueryAsserts disabler;
1431            ASSERT(layer()->hasCompositedLayerMapping());
1432            layer()->compositedLayerMapping()->setNeedsGraphicsLayerUpdate(GraphicsLayerUpdateSubtree);
1433            compositor->setNeedsCompositingUpdate(CompositingUpdateAfterGeometryChange);
1434        } else {
1435            layer()->setNeedsCompositingInputsUpdate();
1436        }
1437    }
1438}
1439
1440bool RenderLayerScrollableArea::usesCompositedScrolling() const
1441{
1442    // Scroll form controls on the main thread so they exhibit correct touch scroll event bubbling
1443    if (box().isIntristicallyScrollable(VerticalScrollbar) || box().isIntristicallyScrollable(HorizontalScrollbar))
1444        return false;
1445
1446    // See https://codereview.chromium.org/176633003/ for the tests that fail without this disabler.
1447    DisableCompositingQueryAsserts disabler;
1448    return layer()->hasCompositedLayerMapping() && layer()->compositedLayerMapping()->scrollingLayer();
1449}
1450
1451static bool layerNeedsCompositedScrolling(const RenderLayer* layer)
1452{
1453    return layer->scrollsOverflow()
1454        && layer->compositor()->preferCompositingToLCDTextEnabled()
1455        && !layer->hasDescendantWithClipPath()
1456        && !layer->hasAncestorWithClipPath()
1457        && !layer->renderer()->style()->hasBorderRadius();
1458}
1459
1460void RenderLayerScrollableArea::updateNeedsCompositedScrolling()
1461{
1462    const bool needsCompositedScrolling = layerNeedsCompositedScrolling(layer());
1463    if (static_cast<bool>(m_needsCompositedScrolling) != needsCompositedScrolling) {
1464        m_needsCompositedScrolling = needsCompositedScrolling;
1465        layer()->didUpdateNeedsCompositedScrolling();
1466    }
1467}
1468
1469void RenderLayerScrollableArea::setTopmostScrollChild(RenderLayer* scrollChild)
1470{
1471    // We only want to track the topmost scroll child for scrollable areas with
1472    // overlay scrollbars.
1473    if (!hasOverlayScrollbars())
1474        return;
1475    m_nextTopmostScrollChild = scrollChild;
1476}
1477
1478} // namespace blink
1479