blob: 5467932c099e0cf3885a9fd225945b60a0550019 [file] [log] [blame]
/*
* Copyright (C) 2006, 2007, 2008, 2009, 2010, 2011, 2012 Apple Inc. All rights reserved.
*
* Portions are Copyright (C) 1998 Netscape Communications Corporation.
*
* Other contributors:
* Robert O'Callahan <roc+@cs.cmu.edu>
* David Baron <dbaron@fas.harvard.edu>
* Christian Biesinger <cbiesinger@web.de>
* Randall Jesup <rjesup@wgate.com>
* Roland Mainz <roland.mainz@informatik.med.uni-giessen.de>
* Josh Soref <timeless@mac.com>
* Boris Zbarsky <bzbarsky@mit.edu>
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation; either
* version 2.1 of the License, or (at your option) any later version.
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this library; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
*
* Alternatively, the contents of this file may be used under the terms
* of either the Mozilla Public License Version 1.1, found at
* http://www.mozilla.org/MPL/ (the "MPL") or the GNU General Public
* License Version 2.0, found at http://www.fsf.org/copyleft/gpl.html
* (the "GPL"), in which case the provisions of the MPL or the GPL are
* applicable instead of those above. If you wish to allow use of your
* version of this file only under the terms of one of those two
* licenses (the MPL or the GPL) and not to allow others to use your
* version of this file under the LGPL, indicate your decision by
* deletingthe provisions above and replace them with the notice and
* other provisions required by the MPL or the GPL, as the case may be.
* If you do not delete the provisions above, a recipient may use your
* version of this file under any of the LGPL, the MPL or the GPL.
*/
#include "config.h"
#include "core/rendering/RenderLayer.h"
#include "core/css/PseudoStyleRequest.h"
#include "core/dom/shadow/ShadowRoot.h"
#include "core/editing/FrameSelection.h"
#include "core/html/HTMLFrameOwnerElement.h"
#include "core/inspector/InspectorInstrumentation.h"
#include "core/page/EventHandler.h"
#include "core/page/FocusController.h"
#include "core/frame/Frame.h"
#include "core/frame/FrameView.h"
#include "core/page/Page.h"
#include "core/page/scrolling/ScrollingCoordinator.h"
#include "core/platform/ScrollAnimator.h"
#include "core/platform/ScrollbarTheme.h"
#include "core/platform/graphics/GraphicsContextStateSaver.h"
#include "core/platform/graphics/GraphicsLayer.h"
#include "core/rendering/CompositedLayerMapping.h"
#include "core/rendering/RenderGeometryMap.h"
#include "core/rendering/RenderLayerCompositor.h"
#include "core/rendering/RenderScrollbar.h"
#include "core/rendering/RenderScrollbarPart.h"
#include "core/rendering/RenderView.h"
#include "platform/PlatformGestureEvent.h"
#include "platform/PlatformMouseEvent.h"
#include "public/platform/Platform.h"
namespace WebCore {
const int ResizerControlExpandRatioForTouch = 2;
RenderLayerScrollableArea::RenderLayerScrollableArea(RenderBox* box)
: m_box(box)
, m_inResizeMode(false)
, m_scrollDimensionsDirty(true)
, m_inOverflowRelayout(false)
, m_willUseCompositedScrollingHasBeenRecorded(false)
, m_isScrollableAreaHasBeenRecorded(false)
, m_scrollCorner(0)
, m_resizer(0)
{
ScrollableArea::setConstrainsScrollingToContentEdge(false);
Node* node = m_box->node();
if (node && node->isElementNode()) {
// We save and restore only the scrollOffset as the other scroll values are recalculated.
Element* element = toElement(node);
m_scrollOffset = element->savedLayerScrollOffset();
if (!m_scrollOffset.isZero())
scrollAnimator()->setCurrentPosition(FloatPoint(m_scrollOffset.width(), m_scrollOffset.height()));
element->setSavedLayerScrollOffset(IntSize());
}
updateResizerAreaSet();
}
RenderLayerScrollableArea::~RenderLayerScrollableArea()
{
if (inResizeMode() && !m_box->documentBeingDestroyed()) {
if (Frame* frame = m_box->frame())
frame->eventHandler().resizeScrollableAreaDestroyed();
}
if (Frame* frame = m_box->frame()) {
if (FrameView* frameView = frame->view()) {
frameView->removeScrollableArea(this);
}
}
if (m_box->frame() && m_box->frame()->page()) {
if (ScrollingCoordinator* scrollingCoordinator = m_box->frame()->page()->scrollingCoordinator())
scrollingCoordinator->willDestroyScrollableArea(this);
}
if (!m_box->documentBeingDestroyed()) {
Node* node = m_box->node();
if (node && node->isElementNode())
toElement(node)->setSavedLayerScrollOffset(m_scrollOffset);
}
if (Frame* frame = m_box->frame()) {
if (FrameView* frameView = frame->view())
frameView->removeResizerArea(m_box);
}
destroyScrollbar(HorizontalScrollbar);
destroyScrollbar(VerticalScrollbar);
if (m_scrollCorner)
m_scrollCorner->destroy();
if (m_resizer)
m_resizer->destroy();
}
ScrollableArea* RenderLayerScrollableArea::enclosingScrollableArea() const
{
if (RenderBox* enclosingScrollableBox = m_box->enclosingScrollableBox())
return enclosingScrollableBox->layer()->scrollableArea();
// FIXME: We should return the frame view here (or possibly an ancestor frame view,
// if the frame view isn't scrollable.
return 0;
}
GraphicsLayer* RenderLayerScrollableArea::layerForScrolling() const
{
return m_box->compositedLayerMapping() ? m_box->compositedLayerMapping()->scrollingContentsLayer() : 0;
}
GraphicsLayer* RenderLayerScrollableArea::layerForHorizontalScrollbar() const
{
return m_box->compositedLayerMapping() ? m_box->compositedLayerMapping()->layerForHorizontalScrollbar() : 0;
}
GraphicsLayer* RenderLayerScrollableArea::layerForVerticalScrollbar() const
{
return m_box->compositedLayerMapping() ? m_box->compositedLayerMapping()->layerForVerticalScrollbar() : 0;
}
GraphicsLayer* RenderLayerScrollableArea::layerForScrollCorner() const
{
return m_box->compositedLayerMapping() ? m_box->compositedLayerMapping()->layerForScrollCorner() : 0;
}
void RenderLayerScrollableArea::invalidateScrollbarRect(Scrollbar* scrollbar, const IntRect& rect)
{
if (scrollbar == m_vBar.get()) {
if (GraphicsLayer* layer = layerForVerticalScrollbar()) {
layer->setNeedsDisplayInRect(rect);
return;
}
} else {
if (GraphicsLayer* layer = layerForHorizontalScrollbar()) {
layer->setNeedsDisplayInRect(rect);
return;
}
}
IntRect scrollRect = rect;
// If we are not yet inserted into the tree, there is no need to repaint.
if (!m_box->parent())
return;
if (scrollbar == m_vBar.get())
scrollRect.move(verticalScrollbarStart(0, m_box->width()), m_box->borderTop());
else
scrollRect.move(horizontalScrollbarStart(0), m_box->height() - m_box->borderBottom() - scrollbar->height());
m_box->repaintRectangle(scrollRect);
}
void RenderLayerScrollableArea::invalidateScrollCornerRect(const IntRect& rect)
{
if (GraphicsLayer* layer = layerForScrollCorner()) {
layer->setNeedsDisplayInRect(rect);
return;
}
if (m_scrollCorner)
m_scrollCorner->repaintRectangle(rect);
if (m_resizer)
m_resizer->repaintRectangle(rect);
}
bool RenderLayerScrollableArea::isActive() const
{
Page* page = m_box->frame()->page();
return page && page->focusController().isActive();
}
bool RenderLayerScrollableArea::isScrollCornerVisible() const
{
return !scrollCornerRect().isEmpty();
}
static int cornerStart(const RenderStyle* style, int minX, int maxX, int thickness)
{
if (style->shouldPlaceBlockDirectionScrollbarOnLogicalLeft())
return minX + style->borderLeftWidth();
return maxX - thickness - style->borderRightWidth();
}
static IntRect cornerRect(const RenderStyle* style, const Scrollbar* horizontalScrollbar, const Scrollbar* verticalScrollbar, const IntRect& bounds)
{
int horizontalThickness;
int verticalThickness;
if (!verticalScrollbar && !horizontalScrollbar) {
// FIXME: This isn't right. We need to know the thickness of custom scrollbars
// even when they don't exist in order to set the resizer square size properly.
horizontalThickness = ScrollbarTheme::theme()->scrollbarThickness();
verticalThickness = horizontalThickness;
} else if (verticalScrollbar && !horizontalScrollbar) {
horizontalThickness = verticalScrollbar->width();
verticalThickness = horizontalThickness;
} else if (horizontalScrollbar && !verticalScrollbar) {
verticalThickness = horizontalScrollbar->height();
horizontalThickness = verticalThickness;
} else {
horizontalThickness = verticalScrollbar->width();
verticalThickness = horizontalScrollbar->height();
}
return IntRect(cornerStart(style, bounds.x(), bounds.maxX(), horizontalThickness),
bounds.maxY() - verticalThickness - style->borderBottomWidth(),
horizontalThickness, verticalThickness);
}
IntRect RenderLayerScrollableArea::scrollCornerRect() const
{
// We have a scrollbar corner when a scrollbar is visible and not filling the entire length of the box.
// This happens when:
// (a) A resizer is present and at least one scrollbar is present
// (b) Both scrollbars are present.
bool hasHorizontalBar = horizontalScrollbar();
bool hasVerticalBar = verticalScrollbar();
bool hasResizer = m_box->style()->resize() != RESIZE_NONE;
if ((hasHorizontalBar && hasVerticalBar) || (hasResizer && (hasHorizontalBar || hasVerticalBar)))
return cornerRect(m_box->style(), horizontalScrollbar(), verticalScrollbar(), m_box->pixelSnappedBorderBoxRect());
return IntRect();
}
IntRect RenderLayerScrollableArea::convertFromScrollbarToContainingView(const Scrollbar* scrollbar, const IntRect& scrollbarRect) const
{
RenderView* view = m_box->view();
if (!view)
return scrollbarRect;
IntRect rect = scrollbarRect;
rect.move(scrollbarOffset(scrollbar));
return view->frameView()->convertFromRenderer(m_box, rect);
}
IntRect RenderLayerScrollableArea::convertFromContainingViewToScrollbar(const Scrollbar* scrollbar, const IntRect& parentRect) const
{
RenderView* view = m_box->view();
if (!view)
return parentRect;
IntRect rect = view->frameView()->convertToRenderer(m_box, parentRect);
rect.move(-scrollbarOffset(scrollbar));
return rect;
}
IntPoint RenderLayerScrollableArea::convertFromScrollbarToContainingView(const Scrollbar* scrollbar, const IntPoint& scrollbarPoint) const
{
RenderView* view = m_box->view();
if (!view)
return scrollbarPoint;
IntPoint point = scrollbarPoint;
point.move(scrollbarOffset(scrollbar));
return view->frameView()->convertFromRenderer(m_box, point);
}
IntPoint RenderLayerScrollableArea::convertFromContainingViewToScrollbar(const Scrollbar* scrollbar, const IntPoint& parentPoint) const
{
RenderView* view = m_box->view();
if (!view)
return parentPoint;
IntPoint point = view->frameView()->convertToRenderer(m_box, parentPoint);
point.move(-scrollbarOffset(scrollbar));
return point;
}
int RenderLayerScrollableArea::scrollSize(ScrollbarOrientation orientation) const
{
IntSize scrollDimensions = maximumScrollPosition() - minimumScrollPosition();
return (orientation == HorizontalScrollbar) ? scrollDimensions.width() : scrollDimensions.height();
}
void RenderLayerScrollableArea::setScrollOffset(const IntPoint& newScrollOffset)
{
if (!m_box->isMarquee()) {
// Ensure that the dimensions will be computed if they need to be (for overflow:hidden blocks).
if (m_scrollDimensionsDirty)
computeScrollDimensions();
}
if (scrollOffset() == toIntSize(newScrollOffset))
return;
setScrollOffset(toIntSize(newScrollOffset));
Frame* frame = m_box->frame();
InspectorInstrumentation::willScrollLayer(m_box);
RenderView* view = m_box->view();
// We should have a RenderView if we're trying to scroll.
ASSERT(view);
// Update the positions of our child layers (if needed as only fixed layers should be impacted by a scroll).
// We don't update compositing layers, because we need to do a deep update from the compositing ancestor.
bool inLayout = view ? view->frameView()->isInLayout() : false;
if (!inLayout) {
// If we're in the middle of layout, we'll just update layers once layout has finished.
layer()->updateLayerPositionsAfterOverflowScroll();
if (view) {
// Update regions, scrolling may change the clip of a particular region.
view->frameView()->updateAnnotatedRegions();
view->updateWidgetPositions();
}
updateCompositingLayersAfterScroll();
}
RenderLayerModelObject* repaintContainer = m_box->containerForRepaint();
if (frame) {
// The caret rect needs to be invalidated after scrolling
frame->selection().setCaretRectNeedsUpdate();
FloatQuad quadForFakeMouseMoveEvent = FloatQuad(layer()->repainter().repaintRect());
if (repaintContainer)
quadForFakeMouseMoveEvent = repaintContainer->localToAbsoluteQuad(quadForFakeMouseMoveEvent);
frame->eventHandler().dispatchFakeMouseMoveEventSoonInQuad(quadForFakeMouseMoveEvent);
}
bool requiresRepaint = true;
if (m_box->view()->compositor()->inCompositingMode()) {
bool onlyScrolledCompositedLayers = !layer()->hasVisibleNonLayerContent()
&& !layer()->hasNonCompositedChild()
&& !layer()->hasBlockSelectionGapBounds();
if (usesCompositedScrolling() || onlyScrolledCompositedLayers)
requiresRepaint = false;
}
// Just schedule a full repaint of our object.
if (view && requiresRepaint)
m_box->repaintUsingContainer(repaintContainer, pixelSnappedIntRect(layer()->repainter().repaintRect()));
// Schedule the scroll DOM event.
if (m_box->node())
m_box->node()->document().enqueueScrollEventForNode(m_box->node());
InspectorInstrumentation::didScrollLayer(m_box);
}
IntPoint RenderLayerScrollableArea::scrollPosition() const
{
return IntPoint(m_scrollOffset);
}
IntPoint RenderLayerScrollableArea::minimumScrollPosition() const
{
return -scrollOrigin();
}
IntPoint RenderLayerScrollableArea::maximumScrollPosition() const
{
if (!m_box->hasOverflowClip())
return -scrollOrigin();
return -scrollOrigin() + enclosingIntRect(m_overflowRect).size() - enclosingIntRect(m_box->clientBoxRect()).size();
}
IntRect RenderLayerScrollableArea::visibleContentRect(IncludeScrollbarsInRect scrollbarInclusion) const
{
int verticalScrollbarWidth = 0;
int horizontalScrollbarHeight = 0;
if (scrollbarInclusion == IncludeScrollbars) {
verticalScrollbarWidth = (verticalScrollbar() && !verticalScrollbar()->isOverlayScrollbar()) ? verticalScrollbar()->width() : 0;
horizontalScrollbarHeight = (horizontalScrollbar() && !horizontalScrollbar()->isOverlayScrollbar()) ? horizontalScrollbar()->height() : 0;
}
return IntRect(IntPoint(scrollXOffset(), scrollYOffset()),
IntSize(max(0, layer()->size().width() - verticalScrollbarWidth), max(0, layer()->size().height() - horizontalScrollbarHeight)));
}
int RenderLayerScrollableArea::visibleHeight() const
{
return layer()->size().height();
}
int RenderLayerScrollableArea::visibleWidth() const
{
return layer()->size().width();
}
IntSize RenderLayerScrollableArea::contentsSize() const
{
return IntSize(scrollWidth(), scrollHeight());
}
IntSize RenderLayerScrollableArea::overhangAmount() const
{
return IntSize();
}
IntPoint RenderLayerScrollableArea::lastKnownMousePosition() const
{
return m_box->frame() ? m_box->frame()->eventHandler().lastKnownMousePosition() : IntPoint();
}
bool RenderLayerScrollableArea::shouldSuspendScrollAnimations() const
{
RenderView* view = m_box->view();
if (!view)
return true;
return view->frameView()->shouldSuspendScrollAnimations();
}
bool RenderLayerScrollableArea::scrollbarsCanBeActive() const
{
RenderView* view = m_box->view();
if (!view)
return false;
return view->frameView()->scrollbarsCanBeActive();
}
IntRect RenderLayerScrollableArea::scrollableAreaBoundingBox() const
{
return m_box->absoluteBoundingBoxRect();
}
bool RenderLayerScrollableArea::userInputScrollable(ScrollbarOrientation orientation) const
{
if (m_box->isIntristicallyScrollable(orientation))
return true;
EOverflow overflowStyle = (orientation == HorizontalScrollbar) ?
m_box->style()->overflowX() : m_box->style()->overflowY();
return (overflowStyle == OSCROLL || overflowStyle == OAUTO || overflowStyle == OOVERLAY);
}
bool RenderLayerScrollableArea::shouldPlaceVerticalScrollbarOnLeft() const
{
return m_box->style()->shouldPlaceBlockDirectionScrollbarOnLogicalLeft();
}
int RenderLayerScrollableArea::pageStep(ScrollbarOrientation orientation) const
{
int length = (orientation == HorizontalScrollbar) ?
m_box->pixelSnappedClientWidth() : m_box->pixelSnappedClientHeight();
int minPageStep = static_cast<float>(length) * ScrollableArea::minFractionToStepWhenPaging();
int pageStep = max(minPageStep, length - ScrollableArea::maxOverlapBetweenPages());
return max(pageStep, 1);
}
RenderLayer* RenderLayerScrollableArea::layer() const
{
return m_box->layer();
}
int RenderLayerScrollableArea::scrollWidth() const
{
if (m_scrollDimensionsDirty)
const_cast<RenderLayerScrollableArea*>(this)->computeScrollDimensions();
return snapSizeToPixel(m_overflowRect.width(), m_box->clientLeft() + m_box->x());
}
int RenderLayerScrollableArea::scrollHeight() const
{
if (m_scrollDimensionsDirty)
const_cast<RenderLayerScrollableArea*>(this)->computeScrollDimensions();
return snapSizeToPixel(m_overflowRect.height(), m_box->clientTop() + m_box->y());
}
void RenderLayerScrollableArea::computeScrollDimensions()
{
m_scrollDimensionsDirty = false;
m_overflowRect = m_box->layoutOverflowRect();
m_box->flipForWritingMode(m_overflowRect);
int scrollableLeftOverflow = m_overflowRect.x() - m_box->borderLeft();
int scrollableTopOverflow = m_overflowRect.y() - m_box->borderTop();
setScrollOrigin(IntPoint(-scrollableLeftOverflow, -scrollableTopOverflow));
}
void RenderLayerScrollableArea::scrollToOffset(const IntSize& scrollOffset, ScrollOffsetClamping clamp)
{
IntSize newScrollOffset = clamp == ScrollOffsetClamped ? clampScrollOffset(scrollOffset) : scrollOffset;
if (newScrollOffset != adjustedScrollOffset())
scrollToOffsetWithoutAnimation(-scrollOrigin() + newScrollOffset);
}
void RenderLayerScrollableArea::updateAfterLayout()
{
// List box parts handle the scrollbars by themselves so we have nothing to do.
if (m_box->style()->appearance() == ListboxPart)
return;
m_scrollDimensionsDirty = true;
IntSize originalScrollOffset = adjustedScrollOffset();
computeScrollDimensions();
if (!m_box->isMarquee()) {
// Layout may cause us to be at an invalid scroll position. In this case we need
// to pull our scroll offsets back to the max (or push them up to the min).
IntSize clampedScrollOffset = clampScrollOffset(adjustedScrollOffset());
if (clampedScrollOffset != adjustedScrollOffset())
scrollToOffset(clampedScrollOffset);
}
if (originalScrollOffset != adjustedScrollOffset())
scrollToOffsetWithoutAnimation(-scrollOrigin() + adjustedScrollOffset());
bool hasHorizontalOverflow = this->hasHorizontalOverflow();
bool hasVerticalOverflow = this->hasVerticalOverflow();
// overflow:scroll should just enable/disable.
if (m_box->style()->overflowX() == OSCROLL)
horizontalScrollbar()->setEnabled(hasHorizontalOverflow);
if (m_box->style()->overflowY() == OSCROLL)
verticalScrollbar()->setEnabled(hasVerticalOverflow);
// overflow:auto may need to lay out again if scrollbars got added/removed.
bool autoHorizontalScrollBarChanged = m_box->hasAutoHorizontalScrollbar() && (hasHorizontalScrollbar() != hasHorizontalOverflow);
bool autoVerticalScrollBarChanged = m_box->hasAutoVerticalScrollbar() && (hasVerticalScrollbar() != hasVerticalOverflow);
if (autoHorizontalScrollBarChanged || autoVerticalScrollBarChanged) {
if (m_box->hasAutoHorizontalScrollbar())
setHasHorizontalScrollbar(hasHorizontalOverflow);
if (m_box->hasAutoVerticalScrollbar())
setHasVerticalScrollbar(hasVerticalOverflow);
layer()->updateSelfPaintingLayer();
// Force an update since we know the scrollbars have changed things.
if (m_box->document().hasAnnotatedRegions())
m_box->document().setAnnotatedRegionsDirty(true);
m_box->repaint();
if (m_box->style()->overflowX() == OAUTO || m_box->style()->overflowY() == OAUTO) {
if (!m_inOverflowRelayout) {
// Our proprietary overflow: overlay value doesn't trigger a layout.
m_inOverflowRelayout = true;
SubtreeLayoutScope layoutScope(m_box);
layoutScope.setNeedsLayout(m_box);
if (m_box->isRenderBlock()) {
RenderBlock* block = toRenderBlock(m_box);
block->scrollbarsChanged(autoHorizontalScrollBarChanged, autoVerticalScrollBarChanged);
block->layoutBlock(true);
} else {
m_box->layout();
}
m_inOverflowRelayout = false;
}
}
}
// Set up the range (and page step/line step).
if (Scrollbar* horizontalScrollbar = this->horizontalScrollbar()) {
int clientWidth = m_box->pixelSnappedClientWidth();
horizontalScrollbar->setProportion(clientWidth, overflowRect().width());
}
if (Scrollbar* verticalScrollbar = this->verticalScrollbar()) {
int clientHeight = m_box->pixelSnappedClientHeight();
verticalScrollbar->setProportion(clientHeight, overflowRect().height());
}
updateScrollableAreaSet(hasScrollableHorizontalOverflow() || hasScrollableVerticalOverflow());
// Composited scrolling may need to be enabled or disabled if the amount of overflow changed.
if (m_box->view() && m_box->view()->compositor()->updateLayerCompositingState(m_box->layer()))
m_box->view()->compositor()->setCompositingLayersNeedRebuild();
}
bool RenderLayerScrollableArea::hasHorizontalOverflow() const
{
ASSERT(!m_scrollDimensionsDirty);
return scrollWidth() > m_box->pixelSnappedClientWidth();
}
bool RenderLayerScrollableArea::hasVerticalOverflow() const
{
ASSERT(!m_scrollDimensionsDirty);
return scrollHeight() > m_box->pixelSnappedClientHeight();
}
bool RenderLayerScrollableArea::hasScrollableHorizontalOverflow() const
{
return hasHorizontalOverflow() && m_box->scrollsOverflowX();
}
bool RenderLayerScrollableArea::hasScrollableVerticalOverflow() const
{
return hasVerticalOverflow() && m_box->scrollsOverflowY();
}
static bool overflowRequiresScrollbar(EOverflow overflow)
{
return overflow == OSCROLL;
}
static bool overflowDefinesAutomaticScrollbar(EOverflow overflow)
{
return overflow == OAUTO || overflow == OOVERLAY;
}
void RenderLayerScrollableArea::updateAfterStyleChange(const RenderStyle* oldStyle)
{
// List box parts handle the scrollbars by themselves so we have nothing to do.
if (m_box->style()->appearance() == ListboxPart)
return;
if (!m_scrollDimensionsDirty)
updateScrollableAreaSet(hasScrollableHorizontalOverflow() || hasScrollableVerticalOverflow());
EOverflow overflowX = m_box->style()->overflowX();
EOverflow overflowY = m_box->style()->overflowY();
// To avoid doing a relayout in updateScrollbarsAfterLayout, we try to keep any automatic scrollbar that was already present.
bool needsHorizontalScrollbar = (hasHorizontalScrollbar() && overflowDefinesAutomaticScrollbar(overflowX)) || overflowRequiresScrollbar(overflowX);
bool needsVerticalScrollbar = (hasVerticalScrollbar() && overflowDefinesAutomaticScrollbar(overflowY)) || overflowRequiresScrollbar(overflowY);
setHasHorizontalScrollbar(needsHorizontalScrollbar);
setHasVerticalScrollbar(needsVerticalScrollbar);
// With overflow: scroll, scrollbars are always visible but may be disabled.
// When switching to another value, we need to re-enable them (see bug 11985).
if (needsHorizontalScrollbar && oldStyle && oldStyle->overflowX() == OSCROLL && overflowX != OSCROLL) {
ASSERT(hasHorizontalScrollbar());
m_hBar->setEnabled(true);
}
if (needsVerticalScrollbar && oldStyle && oldStyle->overflowY() == OSCROLL && overflowY != OSCROLL) {
ASSERT(hasVerticalScrollbar());
m_vBar->setEnabled(true);
}
// FIXME: Need to detect a swap from custom to native scrollbars (and vice versa).
if (m_hBar)
m_hBar->styleChanged();
if (m_vBar)
m_vBar->styleChanged();
updateScrollCornerStyle();
updateResizerAreaSet();
updateResizerStyle();
}
IntSize RenderLayerScrollableArea::clampScrollOffset(const IntSize& scrollOffset) const
{
int maxX = scrollWidth() - m_box->pixelSnappedClientWidth();
int maxY = scrollHeight() - m_box->pixelSnappedClientHeight();
int x = std::max(std::min(scrollOffset.width(), maxX), 0);
int y = std::max(std::min(scrollOffset.height(), maxY), 0);
return IntSize(x, y);
}
IntRect RenderLayerScrollableArea::rectForHorizontalScrollbar(const IntRect& borderBoxRect) const
{
if (!m_hBar)
return IntRect();
const IntRect& scrollCorner = scrollCornerRect();
return IntRect(horizontalScrollbarStart(borderBoxRect.x()),
borderBoxRect.maxY() - m_box->borderBottom() - m_hBar->height(),
borderBoxRect.width() - (m_box->borderLeft() + m_box->borderRight()) - scrollCorner.width(),
m_hBar->height());
}
IntRect RenderLayerScrollableArea::rectForVerticalScrollbar(const IntRect& borderBoxRect) const
{
if (!m_vBar)
return IntRect();
const IntRect& scrollCorner = scrollCornerRect();
return IntRect(verticalScrollbarStart(borderBoxRect.x(), borderBoxRect.maxX()),
borderBoxRect.y() + m_box->borderTop(),
m_vBar->width(),
borderBoxRect.height() - (m_box->borderTop() + m_box->borderBottom()) - scrollCorner.height());
}
LayoutUnit RenderLayerScrollableArea::verticalScrollbarStart(int minX, int maxX) const
{
if (m_box->style()->shouldPlaceBlockDirectionScrollbarOnLogicalLeft())
return minX + m_box->borderLeft();
return maxX - m_box->borderRight() - m_vBar->width();
}
LayoutUnit RenderLayerScrollableArea::horizontalScrollbarStart(int minX) const
{
int x = minX + m_box->borderLeft();
if (m_box->style()->shouldPlaceBlockDirectionScrollbarOnLogicalLeft())
x += m_vBar ? m_vBar->width() : resizerCornerRect(m_box->pixelSnappedBorderBoxRect(), ResizerForPointer).width();
return x;
}
IntSize RenderLayerScrollableArea::scrollbarOffset(const Scrollbar* scrollbar) const
{
if (scrollbar == m_vBar.get())
return IntSize(verticalScrollbarStart(0, m_box->width()), m_box->borderTop());
if (scrollbar == m_hBar.get())
return IntSize(horizontalScrollbarStart(0), m_box->height() - m_box->borderBottom() - scrollbar->height());
ASSERT_NOT_REACHED();
return IntSize();
}
static inline RenderObject* rendererForScrollbar(RenderObject* renderer)
{
if (Node* node = renderer->node()) {
if (ShadowRoot* shadowRoot = node->containingShadowRoot()) {
if (shadowRoot->type() == ShadowRoot::UserAgentShadowRoot)
return shadowRoot->host()->renderer();
}
}
return renderer;
}
PassRefPtr<Scrollbar> RenderLayerScrollableArea::createScrollbar(ScrollbarOrientation orientation)
{
RefPtr<Scrollbar> widget;
RenderObject* actualRenderer = rendererForScrollbar(m_box);
bool hasCustomScrollbarStyle = actualRenderer->isBox() && actualRenderer->style()->hasPseudoStyle(SCROLLBAR);
if (hasCustomScrollbarStyle) {
widget = RenderScrollbar::createCustomScrollbar(this, orientation, actualRenderer->node());
} else {
widget = Scrollbar::create(this, orientation, RegularScrollbar);
if (orientation == HorizontalScrollbar)
didAddHorizontalScrollbar(widget.get());
else
didAddVerticalScrollbar(widget.get());
}
m_box->document().view()->addChild(widget.get());
return widget.release();
}
void RenderLayerScrollableArea::destroyScrollbar(ScrollbarOrientation orientation)
{
RefPtr<Scrollbar>& scrollbar = orientation == HorizontalScrollbar ? m_hBar : m_vBar;
if (!scrollbar)
return;
if (!scrollbar->isCustomScrollbar()) {
if (orientation == HorizontalScrollbar)
willRemoveHorizontalScrollbar(scrollbar.get());
else
willRemoveVerticalScrollbar(scrollbar.get());
}
scrollbar->removeFromParent();
scrollbar->disconnectFromScrollableArea();
scrollbar = 0;
}
void RenderLayerScrollableArea::setHasHorizontalScrollbar(bool hasScrollbar)
{
if (hasScrollbar == hasHorizontalScrollbar())
return;
if (hasScrollbar)
m_hBar = createScrollbar(HorizontalScrollbar);
else
destroyScrollbar(HorizontalScrollbar);
// Destroying or creating one bar can cause our scrollbar corner to come and go. We need to update the opposite scrollbar's style.
if (m_hBar)
m_hBar->styleChanged();
if (m_vBar)
m_vBar->styleChanged();
// Force an update since we know the scrollbars have changed things.
if (m_box->document().hasAnnotatedRegions())
m_box->document().setAnnotatedRegionsDirty(true);
}
void RenderLayerScrollableArea::setHasVerticalScrollbar(bool hasScrollbar)
{
if (hasScrollbar == hasVerticalScrollbar())
return;
if (hasScrollbar)
m_vBar = createScrollbar(VerticalScrollbar);
else
destroyScrollbar(VerticalScrollbar);
// Destroying or creating one bar can cause our scrollbar corner to come and go. We need to update the opposite scrollbar's style.
if (m_hBar)
m_hBar->styleChanged();
if (m_vBar)
m_vBar->styleChanged();
// Force an update since we know the scrollbars have changed things.
if (m_box->document().hasAnnotatedRegions())
m_box->document().setAnnotatedRegionsDirty(true);
}
int RenderLayerScrollableArea::verticalScrollbarWidth(OverlayScrollbarSizeRelevancy relevancy) const
{
if (!m_vBar || (m_vBar->isOverlayScrollbar() && (relevancy == IgnoreOverlayScrollbarSize || !m_vBar->shouldParticipateInHitTesting())))
return 0;
return m_vBar->width();
}
int RenderLayerScrollableArea::horizontalScrollbarHeight(OverlayScrollbarSizeRelevancy relevancy) const
{
if (!m_hBar || (m_hBar->isOverlayScrollbar() && (relevancy == IgnoreOverlayScrollbarSize || !m_hBar->shouldParticipateInHitTesting())))
return 0;
return m_hBar->height();
}
void RenderLayerScrollableArea::positionOverflowControls()
{
RenderGeometryMap geometryMap(UseTransforms);
RenderView* view = m_box->view();
if (m_box->layer() != view->layer() && m_box->layer()->parent())
geometryMap.pushMappingsToAncestor(m_box->layer()->parent(), 0);
LayoutPoint offsetFromRoot = LayoutPoint(geometryMap.absolutePoint(FloatPoint()));
positionOverflowControls(toIntSize(roundedIntPoint(offsetFromRoot)));
}
void RenderLayerScrollableArea::positionOverflowControls(const IntSize& offsetFromRoot)
{
if (!hasScrollbar() && !m_box->canResize())
return;
const IntRect borderBox = m_box->pixelSnappedBorderBoxRect();
if (Scrollbar* verticalScrollbar = this->verticalScrollbar()) {
IntRect vBarRect = rectForVerticalScrollbar(borderBox);
vBarRect.move(offsetFromRoot);
verticalScrollbar->setFrameRect(vBarRect);
}
if (Scrollbar* horizontalScrollbar = this->horizontalScrollbar()) {
IntRect hBarRect = rectForHorizontalScrollbar(borderBox);
hBarRect.move(offsetFromRoot);
horizontalScrollbar->setFrameRect(hBarRect);
}
const IntRect& scrollCorner = scrollCornerRect();
if (m_scrollCorner)
m_scrollCorner->setFrameRect(scrollCorner);
if (m_resizer)
m_resizer->setFrameRect(resizerCornerRect(borderBox, ResizerForPointer));
if (m_box->compositedLayerMapping())
m_box->compositedLayerMapping()->positionOverflowControlsLayers(offsetFromRoot);
}
void RenderLayerScrollableArea::positionNewlyCreatedOverflowControls()
{
if (!m_box->compositedLayerMapping()->hasUnpositionedOverflowControlsLayers())
return;
RenderGeometryMap geometryMap(UseTransforms);
RenderView* view = m_box->view();
if (this != view->layer()->scrollableArea() && m_box->layer()->parent())
geometryMap.pushMappingsToAncestor(m_box->layer()->parent(), 0);
LayoutPoint offsetFromRoot = LayoutPoint(geometryMap.absolutePoint(FloatPoint()));
positionOverflowControls(toIntSize(roundedIntPoint(offsetFromRoot)));
}
bool RenderLayerScrollableArea::scrollsOverflow() const
{
if (FrameView* frameView = m_box->view()->frameView())
return frameView->containsScrollableArea(this);
return false;
}
void RenderLayerScrollableArea::updateScrollCornerStyle()
{
RenderObject* actualRenderer = rendererForScrollbar(m_box);
RefPtr<RenderStyle> corner = m_box->hasOverflowClip() ? actualRenderer->getUncachedPseudoStyle(PseudoStyleRequest(SCROLLBAR_CORNER), actualRenderer->style()) : PassRefPtr<RenderStyle>(0);
if (corner) {
if (!m_scrollCorner) {
m_scrollCorner = RenderScrollbarPart::createAnonymous(&m_box->document());
m_scrollCorner->setParent(m_box);
}
m_scrollCorner->setStyle(corner.release());
} else if (m_scrollCorner) {
m_scrollCorner->destroy();
m_scrollCorner = 0;
}
}
void RenderLayerScrollableArea::paintOverflowControls(GraphicsContext* context, const IntPoint& paintOffset, const IntRect& damageRect, bool paintingOverlayControls)
{
// Don't do anything if we have no overflow.
if (!m_box->hasOverflowClip())
return;
IntPoint adjustedPaintOffset = paintOffset;
if (paintingOverlayControls)
adjustedPaintOffset = m_cachedOverlayScrollbarOffset;
// Move the scrollbar widgets if necessary. We normally move and resize widgets during layout,
// but sometimes widgets can move without layout occurring (most notably when you scroll a
// document that contains fixed positioned elements).
positionOverflowControls(toIntSize(adjustedPaintOffset));
// Overlay scrollbars paint in a second pass through the layer tree so that they will paint
// on top of everything else. If this is the normal painting pass, paintingOverlayControls
// will be false, and we should just tell the root layer that there are overlay scrollbars
// that need to be painted. That will cause the second pass through the layer tree to run,
// and we'll paint the scrollbars then. In the meantime, cache tx and ty so that the
// second pass doesn't need to re-enter the RenderTree to get it right.
if (hasOverlayScrollbars() && !paintingOverlayControls) {
m_cachedOverlayScrollbarOffset = paintOffset;
// It's not necessary to do the second pass if the scrollbars paint into layers.
if ((m_hBar && layerForHorizontalScrollbar()) || (m_vBar && layerForVerticalScrollbar()))
return;
IntRect localDamgeRect = damageRect;
localDamgeRect.moveBy(-paintOffset);
if (!overflowControlsIntersectRect(localDamgeRect))
return;
RenderView* renderView = m_box->view();
RenderLayer* paintingRoot = layer()->enclosingCompositingLayer();
if (!paintingRoot)
paintingRoot = renderView->layer();
paintingRoot->setContainsDirtyOverlayScrollbars(true);
return;
}
// This check is required to avoid painting custom CSS scrollbars twice.
if (paintingOverlayControls && !hasOverlayScrollbars())
return;
// Now that we're sure the scrollbars are in the right place, paint them.
if (m_hBar && !layerForHorizontalScrollbar())
m_hBar->paint(context, damageRect);
if (m_vBar && !layerForVerticalScrollbar())
m_vBar->paint(context, damageRect);
if (layerForScrollCorner())
return;
// We fill our scroll corner with white if we have a scrollbar that doesn't run all the way up to the
// edge of the box.
paintScrollCorner(context, adjustedPaintOffset, damageRect);
// Paint our resizer last, since it sits on top of the scroll corner.
paintResizer(context, adjustedPaintOffset, damageRect);
}
void RenderLayerScrollableArea::paintScrollCorner(GraphicsContext* context, const IntPoint& paintOffset, const IntRect& damageRect)
{
IntRect absRect = scrollCornerRect();
absRect.moveBy(paintOffset);
if (!absRect.intersects(damageRect))
return;
if (context->updatingControlTints()) {
updateScrollCornerStyle();
return;
}
if (m_scrollCorner) {
m_scrollCorner->paintIntoRect(context, paintOffset, absRect);
return;
}
// We don't want to paint white if we have overlay scrollbars, since we need
// to see what is behind it.
if (!hasOverlayScrollbars())
context->fillRect(absRect, Color::white);
}
bool RenderLayerScrollableArea::hitTestOverflowControls(HitTestResult& result, const IntPoint& localPoint)
{
if (!hasScrollbar() && !m_box->canResize())
return false;
IntRect resizeControlRect;
if (m_box->style()->resize() != RESIZE_NONE) {
resizeControlRect = resizerCornerRect(m_box->pixelSnappedBorderBoxRect(), ResizerForPointer);
if (resizeControlRect.contains(localPoint))
return true;
}
int resizeControlSize = max(resizeControlRect.height(), 0);
if (m_vBar && m_vBar->shouldParticipateInHitTesting()) {
LayoutRect vBarRect(verticalScrollbarStart(0, m_box->width()),
m_box->borderTop(),
m_vBar->width(),
m_box->height() - (m_box->borderTop() + m_box->borderBottom()) - (m_hBar ? m_hBar->height() : resizeControlSize));
if (vBarRect.contains(localPoint)) {
result.setScrollbar(m_vBar.get());
return true;
}
}
resizeControlSize = max(resizeControlRect.width(), 0);
if (m_hBar && m_hBar->shouldParticipateInHitTesting()) {
LayoutRect hBarRect(horizontalScrollbarStart(0),
m_box->height() - m_box->borderBottom() - m_hBar->height(),
m_box->width() - (m_box->borderLeft() + m_box->borderRight()) - (m_vBar ? m_vBar->width() : resizeControlSize),
m_hBar->height());
if (hBarRect.contains(localPoint)) {
result.setScrollbar(m_hBar.get());
return true;
}
}
// FIXME: We should hit test the m_scrollCorner and pass it back through the result.
return false;
}
IntRect RenderLayerScrollableArea::resizerCornerRect(const IntRect& bounds, ResizerHitTestType resizerHitTestType) const
{
if (m_box->style()->resize() == RESIZE_NONE)
return IntRect();
IntRect corner = cornerRect(m_box->style(), horizontalScrollbar(), verticalScrollbar(), bounds);
if (resizerHitTestType == ResizerForTouch) {
// We make the resizer virtually larger for touch hit testing. With the
// expanding ratio k = ResizerControlExpandRatioForTouch, we first move
// the resizer rect (of width w & height h), by (-w * (k-1), -h * (k-1)),
// then expand the rect by new_w/h = w/h * k.
int expandRatio = ResizerControlExpandRatioForTouch - 1;
corner.move(-corner.width() * expandRatio, -corner.height() * expandRatio);
corner.expand(corner.width() * expandRatio, corner.height() * expandRatio);
}
return corner;
}
IntRect RenderLayerScrollableArea::scrollCornerAndResizerRect() const
{
IntRect scrollCornerAndResizer = scrollCornerRect();
if (scrollCornerAndResizer.isEmpty())
scrollCornerAndResizer = resizerCornerRect(m_box->pixelSnappedBorderBoxRect(), ResizerForPointer);
return scrollCornerAndResizer;
}
bool RenderLayerScrollableArea::overflowControlsIntersectRect(const IntRect& localRect) const
{
const IntRect borderBox = m_box->pixelSnappedBorderBoxRect();
if (rectForHorizontalScrollbar(borderBox).intersects(localRect))
return true;
if (rectForVerticalScrollbar(borderBox).intersects(localRect))
return true;
if (scrollCornerRect().intersects(localRect))
return true;
if (resizerCornerRect(borderBox, ResizerForPointer).intersects(localRect))
return true;
return false;
}
void RenderLayerScrollableArea::paintResizer(GraphicsContext* context, const IntPoint& paintOffset, const IntRect& damageRect)
{
if (m_box->style()->resize() == RESIZE_NONE)
return;
IntRect absRect = resizerCornerRect(m_box->pixelSnappedBorderBoxRect(), ResizerForPointer);
absRect.moveBy(paintOffset);
if (!absRect.intersects(damageRect))
return;
if (context->updatingControlTints()) {
updateResizerStyle();
return;
}
if (m_resizer) {
m_resizer->paintIntoRect(context, paintOffset, absRect);
return;
}
drawPlatformResizerImage(context, absRect);
// Draw a frame around the resizer (1px grey line) if there are any scrollbars present.
// Clipping will exclude the right and bottom edges of this frame.
if (!hasOverlayScrollbars() && hasScrollbar()) {
GraphicsContextStateSaver stateSaver(*context);
context->clip(absRect);
IntRect largerCorner = absRect;
largerCorner.setSize(IntSize(largerCorner.width() + 1, largerCorner.height() + 1));
context->setStrokeColor(Color(217, 217, 217));
context->setStrokeThickness(1.0f);
context->setFillColor(Color::transparent);
context->drawRect(largerCorner);
}
}
bool RenderLayerScrollableArea::isPointInResizeControl(const IntPoint& absolutePoint, ResizerHitTestType resizerHitTestType) const
{
if (!m_box->canResize())
return false;
IntPoint localPoint = roundedIntPoint(m_box->absoluteToLocal(absolutePoint, UseTransforms));
IntRect localBounds(0, 0, m_box->pixelSnappedWidth(), m_box->pixelSnappedHeight());
return resizerCornerRect(localBounds, resizerHitTestType).contains(localPoint);
}
bool RenderLayerScrollableArea::hitTestResizerInFragments(const LayerFragments& layerFragments, const HitTestLocation& hitTestLocation) const
{
if (!m_box->canResize())
return false;
if (layerFragments.isEmpty())
return false;
for (int i = layerFragments.size() - 1; i >= 0; --i) {
const LayerFragment& fragment = layerFragments.at(i);
if (fragment.backgroundRect.intersects(hitTestLocation) && resizerCornerRect(pixelSnappedIntRect(fragment.layerBounds), ResizerForPointer).contains(hitTestLocation.roundedPoint()))
return true;
}
return false;
}
void RenderLayerScrollableArea::updateResizerAreaSet()
{
Frame* frame = m_box->frame();
if (!frame)
return;
FrameView* frameView = frame->view();
if (!frameView)
return;
if (m_box->canResize())
frameView->addResizerArea(m_box);
else
frameView->removeResizerArea(m_box);
}
void RenderLayerScrollableArea::updateResizerStyle()
{
RenderObject* actualRenderer = rendererForScrollbar(m_box);
RefPtr<RenderStyle> resizer = m_box->hasOverflowClip() ? actualRenderer->getUncachedPseudoStyle(PseudoStyleRequest(RESIZER), actualRenderer->style()) : PassRefPtr<RenderStyle>(0);
if (resizer) {
if (!m_resizer) {
m_resizer = RenderScrollbarPart::createAnonymous(&m_box->document());
m_resizer->setParent(m_box);
}
m_resizer->setStyle(resizer.release());
} else if (m_resizer) {
m_resizer->destroy();
m_resizer = 0;
}
}
void RenderLayerScrollableArea::drawPlatformResizerImage(GraphicsContext* context, IntRect resizerCornerRect)
{
float deviceScaleFactor = WebCore::deviceScaleFactor(m_box->frame());
RefPtr<Image> resizeCornerImage;
IntSize cornerResizerSize;
if (deviceScaleFactor >= 2) {
DEFINE_STATIC_LOCAL(Image*, resizeCornerImageHiRes, (Image::loadPlatformResource("textAreaResizeCorner@2x").leakRef()));
resizeCornerImage = resizeCornerImageHiRes;
cornerResizerSize = resizeCornerImage->size();
cornerResizerSize.scale(0.5f);
} else {
DEFINE_STATIC_LOCAL(Image*, resizeCornerImageLoRes, (Image::loadPlatformResource("textAreaResizeCorner").leakRef()));
resizeCornerImage = resizeCornerImageLoRes;
cornerResizerSize = resizeCornerImage->size();
}
if (m_box->style()->shouldPlaceBlockDirectionScrollbarOnLogicalLeft()) {
context->save();
context->translate(resizerCornerRect.x() + cornerResizerSize.width(), resizerCornerRect.y() + resizerCornerRect.height() - cornerResizerSize.height());
context->scale(FloatSize(-1.0, 1.0));
context->drawImage(resizeCornerImage.get(), IntRect(IntPoint(), cornerResizerSize));
context->restore();
return;
}
IntRect imageRect(resizerCornerRect.maxXMaxYCorner() - cornerResizerSize, cornerResizerSize);
context->drawImage(resizeCornerImage.get(), imageRect);
}
IntSize RenderLayerScrollableArea::offsetFromResizeCorner(const IntPoint& absolutePoint) const
{
// Currently the resize corner is either the bottom right corner or the bottom left corner.
// FIXME: This assumes the location is 0, 0. Is this guaranteed to always be the case?
IntSize elementSize = layer()->size();
if (m_box->style()->shouldPlaceBlockDirectionScrollbarOnLogicalLeft())
elementSize.setWidth(0);
IntPoint resizerPoint = IntPoint(elementSize);
IntPoint localPoint = roundedIntPoint(m_box->absoluteToLocal(absolutePoint, UseTransforms));
return localPoint - resizerPoint;
}
void RenderLayerScrollableArea::resize(const PlatformEvent& evt, const LayoutSize& oldOffset)
{
// FIXME: This should be possible on generated content but is not right now.
if (!inResizeMode() || !m_box->canResize() || !m_box->node())
return;
ASSERT(m_box->node()->isElementNode());
Element* element = toElement(m_box->node());
Document& document = element->document();
IntPoint pos;
const PlatformGestureEvent* gevt = 0;
switch (evt.type()) {
case PlatformEvent::MouseMoved:
if (!document.frame()->eventHandler().mousePressed())
return;
pos = static_cast<const PlatformMouseEvent*>(&evt)->position();
break;
case PlatformEvent::GestureScrollUpdate:
case PlatformEvent::GestureScrollUpdateWithoutPropagation:
pos = static_cast<const PlatformGestureEvent*>(&evt)->position();
gevt = static_cast<const PlatformGestureEvent*>(&evt);
pos = gevt->position();
pos.move(gevt->deltaX(), gevt->deltaY());
break;
default:
ASSERT_NOT_REACHED();
}
float zoomFactor = m_box->style()->effectiveZoom();
LayoutSize newOffset = offsetFromResizeCorner(document.view()->windowToContents(pos));
newOffset.setWidth(newOffset.width() / zoomFactor);
newOffset.setHeight(newOffset.height() / zoomFactor);
LayoutSize currentSize = LayoutSize(m_box->width() / zoomFactor, m_box->height() / zoomFactor);
LayoutSize minimumSize = element->minimumSizeForResizing().shrunkTo(currentSize);
element->setMinimumSizeForResizing(minimumSize);
LayoutSize adjustedOldOffset = LayoutSize(oldOffset.width() / zoomFactor, oldOffset.height() / zoomFactor);
if (m_box->style()->shouldPlaceBlockDirectionScrollbarOnLogicalLeft()) {
newOffset.setWidth(-newOffset.width());
adjustedOldOffset.setWidth(-adjustedOldOffset.width());
}
LayoutSize difference = (currentSize + newOffset - adjustedOldOffset).expandedTo(minimumSize) - currentSize;
bool isBoxSizingBorder = m_box->style()->boxSizing() == BORDER_BOX;
EResize resize = m_box->style()->resize();
if (resize != RESIZE_VERTICAL && difference.width()) {
if (element->isFormControlElement()) {
// Make implicit margins from the theme explicit (see <http://bugs.webkit.org/show_bug.cgi?id=9547>).
element->setInlineStyleProperty(CSSPropertyMarginLeft, m_box->marginLeft() / zoomFactor, CSSPrimitiveValue::CSS_PX);
element->setInlineStyleProperty(CSSPropertyMarginRight, m_box->marginRight() / zoomFactor, CSSPrimitiveValue::CSS_PX);
}
LayoutUnit baseWidth = m_box->width() - (isBoxSizingBorder ? LayoutUnit() : m_box->borderAndPaddingWidth());
baseWidth = baseWidth / zoomFactor;
element->setInlineStyleProperty(CSSPropertyWidth, roundToInt(baseWidth + difference.width()), CSSPrimitiveValue::CSS_PX);
}
if (resize != RESIZE_HORIZONTAL && difference.height()) {
if (element->isFormControlElement()) {
// Make implicit margins from the theme explicit (see <http://bugs.webkit.org/show_bug.cgi?id=9547>).
element->setInlineStyleProperty(CSSPropertyMarginTop, m_box->marginTop() / zoomFactor, CSSPrimitiveValue::CSS_PX);
element->setInlineStyleProperty(CSSPropertyMarginBottom, m_box->marginBottom() / zoomFactor, CSSPrimitiveValue::CSS_PX);
}
LayoutUnit baseHeight = m_box->height() - (isBoxSizingBorder ? LayoutUnit() : m_box->borderAndPaddingHeight());
baseHeight = baseHeight / zoomFactor;
element->setInlineStyleProperty(CSSPropertyHeight, roundToInt(baseHeight + difference.height()), CSSPrimitiveValue::CSS_PX);
}
document.updateLayout();
// FIXME (Radar 4118564): We should also autoscroll the window as necessary to keep the point under the cursor in view.
}
LayoutRect RenderLayerScrollableArea::exposeRect(const LayoutRect& rect, const ScrollAlignment& alignX, const ScrollAlignment& alignY)
{
LayoutRect localExposeRect(m_box->absoluteToLocalQuad(FloatQuad(FloatRect(rect)), UseTransforms).boundingBox());
LayoutRect layerBounds(0, 0, m_box->clientWidth(), m_box->clientHeight());
LayoutRect r = ScrollAlignment::getRectToExpose(layerBounds, localExposeRect, alignX, alignY);
IntSize clampedScrollOffset = clampScrollOffset(adjustedScrollOffset() + toIntSize(roundedIntRect(r).location()));
if (clampedScrollOffset == adjustedScrollOffset())
return rect;
IntSize oldScrollOffset = adjustedScrollOffset();
scrollToOffset(clampedScrollOffset);
IntSize scrollOffsetDifference = adjustedScrollOffset() - oldScrollOffset;
localExposeRect.move(-scrollOffsetDifference);
return LayoutRect(m_box->localToAbsoluteQuad(FloatQuad(FloatRect(localExposeRect)), UseTransforms).boundingBox());
}
void RenderLayerScrollableArea::updateScrollableAreaSet(bool hasOverflow)
{
Frame* frame = m_box->frame();
if (!frame)
return;
FrameView* frameView = frame->view();
if (!frameView)
return;
bool isVisibleToHitTest = m_box->visibleToHitTesting();
if (HTMLFrameOwnerElement* owner = frame->ownerElement())
isVisibleToHitTest &= owner->renderer() && owner->renderer()->visibleToHitTesting();
bool requiresScrollableArea = hasOverflow && isVisibleToHitTest;
bool updatedScrollableAreaSet = false;
if (requiresScrollableArea) {
if (frameView->addScrollableArea(this))
updatedScrollableAreaSet = true;
} else {
if (frameView->removeScrollableArea(this))
updatedScrollableAreaSet = true;
}
if (updatedScrollableAreaSet) {
// Count the total number of RenderLayers that are scrollable areas for
// any period. We only want to record this at most once per RenderLayer.
if (requiresScrollableArea && !m_isScrollableAreaHasBeenRecorded) {
WebKit::Platform::current()->histogramEnumeration("Renderer.CompositedScrolling", RenderLayer::IsScrollableAreaBucket, RenderLayer::CompositedScrollingHistogramMax);
m_isScrollableAreaHasBeenRecorded = true;
}
// We always want composited scrolling if compositor driven accelerated
// scrolling is enabled. Since we will not update needs composited scrolling
// in this case, we must force our state to update.
if (layer()->compositorDrivenAcceleratedScrollingEnabled())
layer()->didUpdateNeedsCompositedScrolling();
else if (requiresScrollableArea)
m_box->view()->compositor()->setNeedsUpdateCompositingRequirementsState();
else
setNeedsCompositedScrolling(false);
}
}
void RenderLayerScrollableArea::updateNeedsCompositedScrolling()
{
TRACE_EVENT0("comp-scroll", "RenderLayer::updateNeedsCompositedScrolling");
layer()->stackingNode()->updateDescendantsAreContiguousInStackingOrder();
layer()->updateDescendantDependentFlags();
ASSERT(scrollsOverflow());
const bool needsToBeStackingContainer = layer()->acceleratedCompositingForOverflowScrollEnabled()
&& layer()->stackingNode()->descendantsAreContiguousInStackingOrder()
&& !layer()->hasUnclippedDescendant();
const bool needsToBeStackingContainerDidChange = layer()->stackingNode()->setNeedsToBeStackingContainer(needsToBeStackingContainer);
const bool needsCompositedScrolling = needsToBeStackingContainer
|| layer()->compositorDrivenAcceleratedScrollingEnabled();
// We gather a boolean value for use with Google UMA histograms to
// quantify the actual effects of a set of patches attempting to
// relax composited scrolling requirements, thereby increasing the
// number of composited overflow divs.
if (layer()->acceleratedCompositingForOverflowScrollEnabled())
WebKit::Platform::current()->histogramEnumeration("Renderer.NeedsCompositedScrolling", needsCompositedScrolling, 2);
const bool needsCompositedScrollingDidChange = setNeedsCompositedScrolling(needsCompositedScrolling);
if (needsToBeStackingContainerDidChange || needsCompositedScrollingDidChange) {
// Note, the z-order lists may need to be rebuilt, but our code guarantees
// that we have not affected stacking, so we will not dirty
// m_descendantsAreContiguousInStackingOrder for either us or our stacking
// context or container.
layer()->didUpdateNeedsCompositedScrolling();
}
}
bool RenderLayerScrollableArea::setNeedsCompositedScrolling(bool needsCompositedScrolling)
{
if (layer()->m_needsCompositedScrolling == needsCompositedScrolling)
return false;
// Count the total number of RenderLayers which need composited scrolling at
// some point. This should be recorded at most once per RenderLayer, so we
// check m_willUseCompositedScrollingHasBeenRecorded.
if (layer()->acceleratedCompositingForOverflowScrollEnabled() && !m_willUseCompositedScrollingHasBeenRecorded) {
WebKit::Platform::current()->histogramEnumeration("Renderer.CompositedScrolling", RenderLayer::WillUseCompositedScrollingBucket, RenderLayer::CompositedScrollingHistogramMax);
m_willUseCompositedScrollingHasBeenRecorded = true;
}
layer()->m_needsCompositedScrolling = needsCompositedScrolling;
return true;
}
void RenderLayerScrollableArea::updateHasVisibleNonLayerContent()
{
layer()->updateHasVisibleNonLayerContent();
}
void RenderLayerScrollableArea::updateCompositingLayersAfterScroll()
{
RenderLayerCompositor* compositor = m_box->view()->compositor();
if (compositor->inCompositingMode()) {
// Our stacking container is guaranteed to contain all of our descendants that may need
// repositioning, so update compositing layers from there.
if (RenderLayer* compositingAncestor = m_box->layer()->stackingNode()->ancestorStackingContainerNode()->layer()->enclosingCompositingLayer()) {
if (usesCompositedScrolling())
compositor->updateCompositingLayers(CompositingUpdateOnCompositedScroll, compositingAncestor);
else
compositor->updateCompositingLayers(CompositingUpdateOnScroll, compositingAncestor);
}
}
}
bool RenderLayerScrollableArea::usesCompositedScrolling() const
{
// Scroll form controls on the main thread so they exhibit correct touch scroll event bubbling
if (m_box && (m_box->isIntristicallyScrollable(VerticalScrollbar) || m_box->isIntristicallyScrollable(HorizontalScrollbar)))
return false;
return m_box->compositedLayerMapping() && m_box->compositedLayerMapping()->scrollingLayer();
}
} // Namespace WebCore