blob: 957df9c1bf388fec56c0767215b5535b1c098bbc [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 "CSSPropertyNames.h"
#include "HTMLNames.h"
#include "RuntimeEnabledFeatures.h"
#include "SVGNames.h"
#include "core/css/PseudoStyleRequest.h"
#include "core/dom/Document.h"
#include "core/dom/DocumentEventQueue.h"
#include "core/dom/shadow/ShadowRoot.h"
#include "core/html/HTMLFrameElement.h"
#include "core/html/HTMLFrameOwnerElement.h"
#include "core/page/EventHandler.h"
#include "core/page/FocusController.h"
#include "core/page/Frame.h"
#include "core/page/FrameView.h"
#include "core/page/Page.h"
#include "core/page/Settings.h"
#include "core/page/UseCounter.h"
#include "core/page/animation/AnimationController.h"
#include "core/page/scrolling/ScrollingCoordinator.h"
#include "core/platform/HistogramSupport.h"
#include "core/platform/Partitions.h"
#include "core/platform/PlatformGestureEvent.h"
#include "core/platform/PlatformMouseEvent.h"
#include "core/platform/ScrollAnimator.h"
#include "core/platform/Scrollbar.h"
#include "core/platform/ScrollbarTheme.h"
#include "core/platform/chromium/TraceEvent.h"
#include "core/platform/graphics/FloatPoint3D.h"
#include "core/platform/graphics/FloatRect.h"
#include "core/platform/graphics/GraphicsContextStateSaver.h"
#include "core/platform/graphics/filters/ReferenceFilter.h"
#include "core/platform/graphics/filters/SourceGraphic.h"
#include "core/platform/graphics/filters/custom/CustomFilterGlobalContext.h"
#include "core/platform/graphics/filters/custom/CustomFilterOperation.h"
#include "core/platform/graphics/filters/custom/CustomFilterValidatedProgram.h"
#include "core/platform/graphics/filters/custom/ValidatedCustomFilterOperation.h"
#include "core/platform/graphics/transforms/ScaleTransformOperation.h"
#include "core/platform/graphics/transforms/TransformationMatrix.h"
#include "core/platform/graphics/transforms/TranslateTransformOperation.h"
#include "core/rendering/ColumnInfo.h"
#include "core/rendering/FilterEffectRenderer.h"
#include "core/rendering/HitTestRequest.h"
#include "core/rendering/HitTestResult.h"
#include "core/rendering/HitTestingTransformState.h"
#include "core/rendering/RenderFlowThread.h"
#include "core/rendering/RenderGeometryMap.h"
#include "core/rendering/RenderInline.h"
#include "core/rendering/RenderLayerBacking.h"
#include "core/rendering/RenderLayerCompositor.h"
#include "core/rendering/RenderReplica.h"
#include "core/rendering/RenderScrollbar.h"
#include "core/rendering/RenderScrollbarPart.h"
#include "core/rendering/RenderTreeAsText.h"
#include "core/rendering/RenderView.h"
#include "core/rendering/svg/ReferenceFilterBuilder.h"
#include "core/rendering/svg/RenderSVGResourceClipper.h"
#include "wtf/StdLibExtras.h"
#include "wtf/UnusedParam.h"
#include "wtf/text/CString.h"
#define MIN_INTERSECT_FOR_REVEAL 32
using namespace std;
namespace WebCore {
using namespace HTMLNames;
const int MinimumWidthWhileResizing = 100;
const int MinimumHeightWhileResizing = 40;
const int ResizerControlExpandRatioForTouch = 2;
bool ClipRect::intersects(const HitTestLocation& hitTestLocation) const
{
return hitTestLocation.intersects(m_rect);
}
RenderLayer::RenderLayer(RenderLayerModelObject* renderer)
: m_inResizeMode(false)
, m_normalFlowListDirty(true)
, m_hasSelfPaintingLayerDescendant(false)
, m_hasSelfPaintingLayerDescendantDirty(false)
, m_hasOutOfFlowPositionedDescendant(false)
, m_hasOutOfFlowPositionedDescendantDirty(true)
, m_hasUnclippedDescendant(false)
, m_isUnclippedDescendant(false)
, m_needsCompositedScrolling(false)
, m_canBePromotedToStackingContainer(false)
, m_canBePromotedToStackingContainerDirty(true)
, m_isRootLayer(renderer->isRenderView())
, m_usedTransparency(false)
, m_paintingInsideReflection(false)
, m_inOverflowRelayout(false)
, m_repaintStatus(NeedsNormalRepaint)
, m_visibleContentStatusDirty(true)
, m_hasVisibleContent(false)
, m_visibleDescendantStatusDirty(false)
, m_hasVisibleDescendant(false)
, m_isPaginated(false)
, m_3DTransformedDescendantStatusDirty(true)
, m_has3DTransformedDescendant(false)
, m_containsDirtyOverlayScrollbars(false)
#if !ASSERT_DISABLED
, m_layerListMutationAllowed(true)
#endif
, m_canSkipRepaintRectsUpdateOnScroll(renderer->isTableCell())
, m_hasFilterInfo(false)
, m_blendMode(BlendModeNormal)
, m_renderer(renderer)
, m_parent(0)
, m_previous(0)
, m_next(0)
, m_first(0)
, m_last(0)
, m_staticInlinePosition(0)
, m_staticBlockPosition(0)
, m_reflection(0)
, m_scrollCorner(0)
, m_resizer(0)
, m_enclosingPaginationLayer(0)
, m_forceNeedsCompositedScrolling(DoNotForceCompositedScrolling)
// FIXME: We could lazily allocate our ScrollableArea based on style properties
// ('overflow', ...) but for now, we are always allocating it as it's safer.
, m_scrollableArea(adoptPtr(new RenderLayerScrollableArea(this)))
{
m_isNormalFlowOnly = shouldBeNormalFlowOnly();
m_isSelfPaintingLayer = shouldBeSelfPaintingLayer();
// Non-stacking containers should have empty z-order lists. As this is already the case,
// there is no need to dirty / recompute these lists.
m_zOrderListsDirty = isStackingContainer();
if (!renderer->firstChild() && renderer->style()) {
m_visibleContentStatusDirty = false;
m_hasVisibleContent = renderer->style()->visibility() == VISIBLE;
}
updateResizerAreaSet();
}
RenderLayer::~RenderLayer()
{
if (inResizeMode() && !renderer()->documentBeingDestroyed()) {
if (Frame* frame = renderer()->frame())
frame->eventHandler()->resizeLayerDestroyed();
}
if (Frame* frame = renderer()->frame()) {
if (FrameView* frameView = frame->view())
frameView->removeResizerArea(this);
}
if (!m_renderer->documentBeingDestroyed())
compositor()->removeOutOfFlowPositionedLayer(this);
destroyScrollbar(HorizontalScrollbar);
destroyScrollbar(VerticalScrollbar);
if (renderer()->frame() && renderer()->frame()->page()) {
if (ScrollingCoordinator* scrollingCoordinator = renderer()->frame()->page()->scrollingCoordinator())
scrollingCoordinator->willDestroyRenderLayer(this);
}
if (m_reflection)
removeReflection();
removeFilterInfoIfNeeded();
// Child layers will be deleted by their corresponding render objects, so
// we don't need to delete them ourselves.
clearBacking(true);
if (m_scrollCorner)
m_scrollCorner->destroy();
if (m_resizer)
m_resizer->destroy();
}
String RenderLayer::debugName() const
{
String name = renderer()->debugName();
if (!isReflection())
return name;
return name + " (reflection)";
}
RenderLayerCompositor* RenderLayer::compositor() const
{
if (!renderer()->view())
return 0;
return renderer()->view()->compositor();
}
void RenderLayer::contentChanged(ContentChangeType changeType)
{
// This can get called when video becomes accelerated, so the layers may change.
if ((changeType == CanvasChanged || changeType == VideoChanged || changeType == FullScreenChanged) && compositor()->updateLayerCompositingState(this))
compositor()->setCompositingLayersNeedRebuild();
if (m_backing)
m_backing->contentChanged(changeType);
}
bool RenderLayer::canRender3DTransforms() const
{
return compositor()->canRender3DTransforms();
}
bool RenderLayer::paintsWithFilters() const
{
// FIXME: Eventually there will be more factors than isComposited() to decide whether or not to render the filter
if (!renderer()->hasFilter())
return false;
if (!isComposited())
return true;
if (!m_backing || !m_backing->canCompositeFilters())
return true;
return false;
}
bool RenderLayer::requiresFullLayerImageForFilters() const
{
if (!paintsWithFilters())
return false;
FilterEffectRenderer* filter = filterRenderer();
return filter ? filter->hasFilterThatMovesPixels() : false;
}
LayoutPoint RenderLayer::computeOffsetFromRoot(bool& hasLayerOffset) const
{
hasLayerOffset = true;
if (!parent())
return LayoutPoint();
// This is similar to root() but we check if an ancestor layer would
// prevent the optimization from working.
const RenderLayer* rootLayer = 0;
for (const RenderLayer* parentLayer = parent(); parentLayer; rootLayer = parentLayer, parentLayer = parentLayer->parent()) {
hasLayerOffset = parentLayer->canUseConvertToLayerCoords();
if (!hasLayerOffset)
return LayoutPoint();
}
ASSERT(rootLayer == root());
LayoutPoint offset;
parent()->convertToLayerCoords(rootLayer, offset);
return offset;
}
void RenderLayer::updateLayerPositionsAfterLayout(const RenderLayer* rootLayer, UpdateLayerPositionsFlags flags)
{
RenderGeometryMap geometryMap(UseTransforms);
if (this != rootLayer)
geometryMap.pushMappingsToAncestor(parent(), 0);
updateLayerPositions(&geometryMap, flags);
}
void RenderLayer::updateLayerPositions(RenderGeometryMap* geometryMap, UpdateLayerPositionsFlags flags)
{
updateLayerPosition(); // For relpositioned layers or non-positioned layers,
// we need to keep in sync, since we may have shifted relative
// to our parent layer.
if (geometryMap)
geometryMap->pushMappingsToAncestor(this, parent());
// Clear our cached clip rect information.
clearClipRects();
if (hasOverflowControls()) {
LayoutPoint offsetFromRoot;
if (geometryMap)
offsetFromRoot = LayoutPoint(geometryMap->absolutePoint(FloatPoint()));
else {
// FIXME: It looks suspicious to call convertToLayerCoords here
// as canUseConvertToLayerCoords may be true for an ancestor layer.
convertToLayerCoords(root(), offsetFromRoot);
}
positionOverflowControls(toIntSize(roundedIntPoint(offsetFromRoot)));
}
updateDescendantDependentFlags();
if (flags & UpdatePagination)
updatePagination();
else {
m_isPaginated = false;
m_enclosingPaginationLayer = 0;
}
if (m_hasVisibleContent) {
RenderView* view = renderer()->view();
ASSERT(view);
// FIXME: LayoutState does not work with RenderLayers as there is not a 1-to-1
// mapping between them and the RenderObjects. It would be neat to enable
// LayoutState outside the layout() phase and use it here.
ASSERT(!view->layoutStateEnabled());
RenderLayerModelObject* repaintContainer = renderer()->containerForRepaint();
LayoutRect oldRepaintRect = m_repaintRect;
LayoutRect oldOutlineBox = m_outlineBox;
computeRepaintRects(repaintContainer, geometryMap);
// FIXME: Should ASSERT that value calculated for m_outlineBox using the cached offset is the same
// as the value not using the cached offset, but we can't due to https://bugs.webkit.org/show_bug.cgi?id=37048
if (flags & CheckForRepaint) {
if (view && !view->printing()) {
if (m_repaintStatus & NeedsFullRepaint) {
renderer()->repaintUsingContainer(repaintContainer, pixelSnappedIntRect(oldRepaintRect));
if (m_repaintRect != oldRepaintRect)
renderer()->repaintUsingContainer(repaintContainer, pixelSnappedIntRect(m_repaintRect));
} else if (shouldRepaintAfterLayout())
renderer()->repaintAfterLayoutIfNeeded(repaintContainer, oldRepaintRect, oldOutlineBox, &m_repaintRect, &m_outlineBox);
}
}
} else
clearRepaintRects();
m_repaintStatus = NeedsNormalRepaint;
// Go ahead and update the reflection's position and size.
if (m_reflection)
m_reflection->layout();
// Clear the IsCompositingUpdateRoot flag once we've found the first compositing layer in this update.
bool isUpdateRoot = (flags & IsCompositingUpdateRoot);
if (isComposited())
flags &= ~IsCompositingUpdateRoot;
if (useRegionBasedColumns() && renderer()->isInFlowRenderFlowThread()) {
updatePagination();
flags |= UpdatePagination;
}
if (renderer()->hasColumns())
flags |= UpdatePagination;
for (RenderLayer* child = firstChild(); child; child = child->nextSibling())
child->updateLayerPositions(geometryMap, flags);
if ((flags & UpdateCompositingLayers) && isComposited()) {
RenderLayerBacking::UpdateAfterLayoutFlags updateFlags = RenderLayerBacking::CompositingChildrenOnly;
if (flags & NeedsFullRepaintInBacking)
updateFlags |= RenderLayerBacking::NeedsFullRepaint;
if (isUpdateRoot)
updateFlags |= RenderLayerBacking::IsUpdateRoot;
backing()->updateAfterLayout(updateFlags);
}
if (geometryMap)
geometryMap->popMappingsToAncestor(parent());
}
LayoutRect RenderLayer::repaintRectIncludingNonCompositingDescendants() const
{
LayoutRect repaintRect = m_repaintRect;
for (RenderLayer* child = firstChild(); child; child = child->nextSibling()) {
// Don't include repaint rects for composited child layers; they will paint themselves and have a different origin.
if (child->isComposited())
continue;
repaintRect.unite(child->repaintRectIncludingNonCompositingDescendants());
}
return repaintRect;
}
void RenderLayer::setAncestorChainHasSelfPaintingLayerDescendant()
{
for (RenderLayer* layer = this; layer; layer = layer->parent()) {
if (!layer->m_hasSelfPaintingLayerDescendantDirty && layer->hasSelfPaintingLayerDescendant())
break;
layer->m_hasSelfPaintingLayerDescendantDirty = false;
layer->m_hasSelfPaintingLayerDescendant = true;
}
}
void RenderLayer::dirtyAncestorChainHasSelfPaintingLayerDescendantStatus()
{
for (RenderLayer* layer = this; layer; layer = layer->parent()) {
layer->m_hasSelfPaintingLayerDescendantDirty = true;
// If we have reached a self-painting layer, we know our parent should have a self-painting descendant
// in this case, there is no need to dirty our ancestors further.
if (layer->isSelfPaintingLayer()) {
ASSERT(!parent() || parent()->m_hasSelfPaintingLayerDescendantDirty || parent()->hasSelfPaintingLayerDescendant());
break;
}
}
}
void RenderLayer::setAncestorChainHasOutOfFlowPositionedDescendant()
{
for (RenderLayer* layer = this; layer; layer = layer->parent()) {
if (!layer->m_hasOutOfFlowPositionedDescendantDirty && layer->hasOutOfFlowPositionedDescendant())
break;
layer->setHasOutOfFlowPositionedDescendantDirty(false);
layer->setHasOutOfFlowPositionedDescendant(true);
}
}
void RenderLayer::dirtyAncestorChainHasOutOfFlowPositionedDescendantStatus()
{
for (RenderLayer* layer = this; layer; layer = layer->parent()) {
layer->setHasOutOfFlowPositionedDescendantDirty(true);
// We may or may not have an unclipped descendant. If we do, we'll reset
// this to true the next time composited scrolling state is updated.
layer->setHasUnclippedDescendant(false);
// If we have reached an out of flow positioned layer, we know our parent should have an out-of-flow positioned descendant.
// In this case, there is no need to dirty our ancestors further.
if (layer->renderer()->isOutOfFlowPositioned()) {
ASSERT(!parent() || parent()->m_hasOutOfFlowPositionedDescendantDirty || parent()->hasOutOfFlowPositionedDescendant());
break;
}
}
}
bool RenderLayer::acceleratedCompositingForOverflowScrollEnabled() const
{
const Settings* settings = renderer()->document().settings();
return settings && settings->acceleratedCompositingForOverflowScrollEnabled();
}
// FIXME: This is a temporary flag and should be removed once accelerated
// overflow scroll is ready (crbug.com/254111).
bool RenderLayer::compositorDrivenAcceleratedScrollingEnabled() const
{
if (!acceleratedCompositingForOverflowScrollEnabled())
return false;
const Settings* settings = renderer()->document().settings();
return settings && settings->isCompositorDrivenAcceleratedScrollingEnabled();
}
// Determine whether the current layer can be promoted to a stacking container.
// We do this by computing what positive and negative z-order lists would look
// like before and after promotion, and ensuring that proper stacking order is
// preserved between the two sets of lists.
void RenderLayer::updateCanBeStackingContainer()
{
TRACE_EVENT0("blink_rendering,comp-scroll", "RenderLayer::updateCanBeStackingContainer");
if (isStackingContext() || !m_canBePromotedToStackingContainerDirty || !acceleratedCompositingForOverflowScrollEnabled())
return;
FrameView* frameView = renderer()->view()->frameView();
if (!frameView || !frameView->containsScrollableArea(scrollableArea()))
return;
RenderLayer* ancestorStackingContext = this->ancestorStackingContext();
if (!ancestorStackingContext)
return;
OwnPtr<Vector<RenderLayer*> > posZOrderListBeforePromote = adoptPtr(new Vector<RenderLayer*>);
OwnPtr<Vector<RenderLayer*> > negZOrderListBeforePromote = adoptPtr(new Vector<RenderLayer*>);
OwnPtr<Vector<RenderLayer*> > posZOrderListAfterPromote = adoptPtr(new Vector<RenderLayer*>);
OwnPtr<Vector<RenderLayer*> > negZOrderListAfterPromote = adoptPtr(new Vector<RenderLayer*>);
collectBeforePromotionZOrderList(ancestorStackingContext, posZOrderListBeforePromote, negZOrderListBeforePromote);
collectAfterPromotionZOrderList(ancestorStackingContext, posZOrderListAfterPromote, negZOrderListAfterPromote);
size_t maxIndex = std::min(posZOrderListAfterPromote->size() + negZOrderListAfterPromote->size(), posZOrderListBeforePromote->size() + negZOrderListBeforePromote->size());
m_canBePromotedToStackingContainerDirty = false;
m_canBePromotedToStackingContainer = false;
const RenderLayer* layerAfterPromote = 0;
for (size_t i = 0; i < maxIndex && layerAfterPromote != this; ++i) {
const RenderLayer* layerBeforePromote = i < negZOrderListBeforePromote->size()
? negZOrderListBeforePromote->at(i)
: posZOrderListBeforePromote->at(i - negZOrderListBeforePromote->size());
layerAfterPromote = i < negZOrderListAfterPromote->size()
? negZOrderListAfterPromote->at(i)
: posZOrderListAfterPromote->at(i - negZOrderListAfterPromote->size());
if (layerBeforePromote != layerAfterPromote && (layerAfterPromote != this || renderer()->hasBackground()))
return;
}
layerAfterPromote = 0;
for (size_t i = 0; i < maxIndex && layerAfterPromote != this; ++i) {
const RenderLayer* layerBeforePromote = i < posZOrderListBeforePromote->size()
? posZOrderListBeforePromote->at(posZOrderListBeforePromote->size() - i - 1)
: negZOrderListBeforePromote->at(negZOrderListBeforePromote->size() + posZOrderListBeforePromote->size() - i - 1);
layerAfterPromote = i < posZOrderListAfterPromote->size()
? posZOrderListAfterPromote->at(posZOrderListAfterPromote->size() - i - 1)
: negZOrderListAfterPromote->at(negZOrderListAfterPromote->size() + posZOrderListAfterPromote->size() - i - 1);
if (layerBeforePromote != layerAfterPromote && layerAfterPromote != this)
return;
}
m_canBePromotedToStackingContainer = true;
}
static inline bool isPositionedContainer(const RenderLayer* layer)
{
// FIXME: This is not in sync with containingBlock.
// RenderObject::canContainFixedPositionedObject() should probably be used
// instead.
RenderLayerModelObject* layerRenderer = layer->renderer();
return layer->isRootLayer() || layerRenderer->isPositioned() || layer->hasTransform();
}
void RenderLayer::collectBeforePromotionZOrderList(RenderLayer* ancestorStackingContext, OwnPtr<Vector<RenderLayer*> >& posZOrderListBeforePromote, OwnPtr<Vector<RenderLayer*> >& negZOrderListBeforePromote)
{
ancestorStackingContext->rebuildZOrderLists(posZOrderListBeforePromote, negZOrderListBeforePromote, this, OnlyStackingContextsCanBeStackingContainers);
const RenderLayer* positionedAncestor = parent();
while (positionedAncestor && !isPositionedContainer(positionedAncestor) && !positionedAncestor->isStackingContext())
positionedAncestor = positionedAncestor->parent();
if (positionedAncestor && (!isPositionedContainer(positionedAncestor) || positionedAncestor->isStackingContext()))
positionedAncestor = 0;
if (!posZOrderListBeforePromote)
posZOrderListBeforePromote = adoptPtr(new Vector<RenderLayer*>());
else if (posZOrderListBeforePromote->find(this) != notFound)
return;
// The current layer will appear in the z-order lists after promotion, so
// for a meaningful comparison, we must insert it in the z-order lists
// before promotion if it does not appear there already.
if (!positionedAncestor) {
posZOrderListBeforePromote->prepend(this);
return;
}
for (size_t index = 0; index < posZOrderListBeforePromote->size(); index++) {
if (posZOrderListBeforePromote->at(index) == positionedAncestor) {
posZOrderListBeforePromote->insert(index + 1, this);
return;
}
}
}
void RenderLayer::collectAfterPromotionZOrderList(RenderLayer* ancestorStackingContext, OwnPtr<Vector<RenderLayer*> >& posZOrderListAfterPromote, OwnPtr<Vector<RenderLayer*> >& negZOrderListAfterPromote)
{
ancestorStackingContext->rebuildZOrderLists(posZOrderListAfterPromote, negZOrderListAfterPromote, this, ForceLayerToStackingContainer);
}
// Compute what positive and negative z-order lists would look like before and
// after promotion, so we can later ensure that proper stacking order is
// preserved between the two sets of lists.
//
// A few examples:
// c = currentLayer
// - = negative z-order child of currentLayer
// + = positive z-order child of currentLayer
// a = positioned ancestor of currentLayer
// x = any other RenderLayer in the list
//
// (a) xxxxx-----++a+++x
// (b) xxx-----c++++++xx
//
// Normally the current layer would be painted in the normal flow list if it
// doesn't already appear in the positive z-order list. However, in the case
// that the layer has a positioned ancestor, it will paint directly after the
// positioned ancestor. In example (a), the current layer would be painted in
// the middle of its own positive z-order children, so promoting would cause a
// change in paint order (since a promoted layer will paint all of its positive
// z-order children strictly after it paints itself).
//
// In example (b), it is ok to promote the current layer only if it does not
// have a background. If it has a background, the background gets painted before
// the layer's negative z-order children, so again, a promotion would cause a
// change in paint order (causing the background to get painted after the
// negative z-order children instead of before).
//
void RenderLayer::computePaintOrderList(PaintOrderListType type, Vector<RefPtr<Node> >& list)
{
OwnPtr<Vector<RenderLayer*> > posZOrderList;
OwnPtr<Vector<RenderLayer*> > negZOrderList;
RenderLayer* stackingContext = ancestorStackingContext();
if (!stackingContext)
return;
switch (type) {
case BeforePromote:
collectBeforePromotionZOrderList(stackingContext, posZOrderList, negZOrderList);
break;
case AfterPromote:
collectAfterPromotionZOrderList(stackingContext, posZOrderList, negZOrderList);
break;
}
if (negZOrderList) {
for (size_t index = 0; index < negZOrderList->size(); ++index)
list.append(negZOrderList->at(index)->renderer()->node());
}
if (posZOrderList) {
for (size_t index = 0; index < posZOrderList->size(); ++index)
list.append(posZOrderList->at(index)->renderer()->node());
}
}
bool RenderLayer::scrollsWithRespectTo(const RenderLayer* other) const
{
const EPosition position = renderer()->style()->position();
const EPosition otherPosition = other->renderer()->style()->position();
const RenderObject* containingBlock = renderer()->containingBlock();
const RenderObject* otherContainingBlock = other->renderer()->containingBlock();
const RenderLayer* rootLayer = renderer()->view()->compositor()->rootRenderLayer();
// Fixed-position elements are a special case. They are static with respect
// to the viewport, which is not represented by any RenderObject, and their
// containingBlock() method returns the root HTML element (while its true
// containingBlock should really be the viewport). The real measure for a
// non-transformed fixed-position element is as follows: any fixed position
// element, A, scrolls with respect an element, B, if and only if B is not
// fixed position.
//
// Unfortunately, it gets a bit more complicated - a fixed-position element
// which has a transform acts exactly as an absolute-position element
// (including having a real, non-viewport containing block).
//
// Below, a "root" fixed position element is defined to be one whose
// containing block is the root. These root-fixed-position elements are
// the only ones that need this special case code - other fixed position
// elements, as well as all absolute, relative, and static elements use the
// logic below.
const bool isRootFixedPos = position == FixedPosition && containingBlock->enclosingLayer() == rootLayer;
const bool otherIsRootFixedPos = otherPosition == FixedPosition && otherContainingBlock->enclosingLayer() == rootLayer;
if (isRootFixedPos && otherIsRootFixedPos)
return false;
if (isRootFixedPos || otherIsRootFixedPos)
return true;
FrameView* frameView = renderer()->view()->frameView();
if (containingBlock == otherContainingBlock)
return false;
// Maintain a set of containing blocks between the first layer and its
// closest scrollable ancestor.
HashSet<const RenderObject*> containingBlocks;
while (containingBlock) {
if (frameView && frameView->containsScrollableArea(containingBlock->enclosingLayer()->scrollableArea()))
break;
containingBlocks.add(containingBlock);
containingBlock = containingBlock->containingBlock();
}
// Do the same for the 2nd layer, but if we find a common containing block,
// it means both layers are contained within a single non-scrolling subtree.
// Hence, they will not scroll with respect to each other.
while (otherContainingBlock) {
if (containingBlocks.contains(otherContainingBlock))
return false;
if (frameView && frameView->containsScrollableArea(otherContainingBlock->enclosingLayer()->scrollableArea()))
break;
otherContainingBlock = otherContainingBlock->containingBlock();
}
return true;
}
void RenderLayer::computeRepaintRects(const RenderLayerModelObject* repaintContainer, const RenderGeometryMap* geometryMap)
{
ASSERT(!m_visibleContentStatusDirty);
m_repaintRect = renderer()->clippedOverflowRectForRepaint(repaintContainer);
m_outlineBox = renderer()->outlineBoundsForRepaint(repaintContainer, geometryMap);
}
void RenderLayer::computeRepaintRectsIncludingDescendants()
{
// FIXME: computeRepaintRects() has to walk up the parent chain for every layer to compute the rects.
// We should make this more efficient.
// FIXME: it's wrong to call this when layout is not up-to-date, which we do.
computeRepaintRects(renderer()->containerForRepaint());
for (RenderLayer* layer = firstChild(); layer; layer = layer->nextSibling())
layer->computeRepaintRectsIncludingDescendants();
}
void RenderLayer::clearRepaintRects()
{
ASSERT(!m_hasVisibleContent);
ASSERT(!m_visibleContentStatusDirty);
m_repaintRect = IntRect();
m_outlineBox = IntRect();
}
void RenderLayer::updateLayerPositionsAfterDocumentScroll()
{
ASSERT(this == renderer()->view()->layer());
RenderGeometryMap geometryMap(UseTransforms);
updateLayerPositionsAfterScroll(&geometryMap);
}
void RenderLayer::updateLayerPositionsAfterOverflowScroll()
{
RenderGeometryMap geometryMap(UseTransforms);
RenderView* view = renderer()->view();
if (this != view->layer())
geometryMap.pushMappingsToAncestor(parent(), 0);
// FIXME: why is it OK to not check the ancestors of this layer in order to
// initialize the HasSeenViewportConstrainedAncestor and HasSeenAncestorWithOverflowClip flags?
updateLayerPositionsAfterScroll(&geometryMap, IsOverflowScroll);
}
void RenderLayer::updateLayerPositionsAfterScroll(RenderGeometryMap* geometryMap, UpdateLayerPositionsAfterScrollFlags flags)
{
// FIXME: This shouldn't be needed, but there are some corner cases where
// these flags are still dirty. Update so that the check below is valid.
updateDescendantDependentFlags();
// If we have no visible content and no visible descendants, there is no point recomputing
// our rectangles as they will be empty. If our visibility changes, we are expected to
// recompute all our positions anyway.
if (!m_hasVisibleDescendant && !m_hasVisibleContent)
return;
bool positionChanged = updateLayerPosition();
if (positionChanged)
flags |= HasChangedAncestor;
if (geometryMap)
geometryMap->pushMappingsToAncestor(this, parent());
if (flags & HasChangedAncestor || flags & HasSeenViewportConstrainedAncestor || flags & IsOverflowScroll)
clearClipRects();
if (renderer()->style()->hasViewportConstrainedPosition())
flags |= HasSeenViewportConstrainedAncestor;
if (renderer()->hasOverflowClip())
flags |= HasSeenAncestorWithOverflowClip;
if (flags & HasSeenViewportConstrainedAncestor
|| (flags & IsOverflowScroll && flags & HasSeenAncestorWithOverflowClip && !m_canSkipRepaintRectsUpdateOnScroll)) {
// FIXME: We could track the repaint container as we walk down the tree.
computeRepaintRects(renderer()->containerForRepaint(), geometryMap);
} else {
// Check that our cached rects are correct.
// FIXME: re-enable these assertions when the issue with table cells is resolved: https://bugs.webkit.org/show_bug.cgi?id=103432
// ASSERT(m_repaintRect == renderer()->clippedOverflowRectForRepaint(renderer()->containerForRepaint()));
// ASSERT(m_outlineBox == renderer()->outlineBoundsForRepaint(renderer()->containerForRepaint(), geometryMap));
}
for (RenderLayer* child = firstChild(); child; child = child->nextSibling())
child->updateLayerPositionsAfterScroll(geometryMap, flags);
// We don't update our reflection as scrolling is a translation which does not change the size()
// of an object, thus RenderReplica will still repaint itself properly as the layer position was
// updated above.
if (geometryMap)
geometryMap->popMappingsToAncestor(parent());
}
void RenderLayer::positionNewlyCreatedOverflowControls()
{
if (!backing()->hasUnpositionedOverflowControlsLayers())
return;
RenderGeometryMap geometryMap(UseTransforms);
RenderView* view = renderer()->view();
if (this != view->layer() && parent())
geometryMap.pushMappingsToAncestor(parent(), 0);
LayoutPoint offsetFromRoot = LayoutPoint(geometryMap.absolutePoint(FloatPoint()));
positionOverflowControls(toIntSize(roundedIntPoint(offsetFromRoot)));
}
bool RenderLayer::hasBlendMode() const
{
return RuntimeEnabledFeatures::cssCompositingEnabled() && renderer()->hasBlendMode();
}
void RenderLayer::updateBlendMode()
{
if (!RuntimeEnabledFeatures::cssCompositingEnabled())
return;
BlendMode newBlendMode = renderer()->style()->blendMode();
if (newBlendMode != m_blendMode) {
m_blendMode = newBlendMode;
if (backing())
backing()->setBlendMode(newBlendMode);
}
}
void RenderLayer::updateTransform()
{
// hasTransform() on the renderer is also true when there is transform-style: preserve-3d or perspective set,
// so check style too.
bool hasTransform = renderer()->hasTransform() && renderer()->style()->hasTransform();
bool had3DTransform = has3DTransform();
bool hadTransform = m_transform;
if (hasTransform != hadTransform) {
if (hasTransform)
m_transform = adoptPtr(new TransformationMatrix);
else
m_transform.clear();
// Layers with transforms act as clip rects roots, so clear the cached clip rects here.
clearClipRectsIncludingDescendants();
}
if (hasTransform) {
RenderBox* box = renderBox();
ASSERT(box);
m_transform->makeIdentity();
box->style()->applyTransform(*m_transform, box->pixelSnappedBorderBoxRect().size(), RenderStyle::IncludeTransformOrigin);
makeMatrixRenderable(*m_transform, canRender3DTransforms());
}
if (had3DTransform != has3DTransform())
dirty3DTransformedDescendantStatus();
}
TransformationMatrix RenderLayer::currentTransform(RenderStyle::ApplyTransformOrigin applyOrigin) const
{
if (!m_transform)
return TransformationMatrix();
if (renderer()->style()->isRunningAcceleratedAnimation()) {
TransformationMatrix currTransform;
RefPtr<RenderStyle> style = renderer()->animation()->getAnimatedStyleForRenderer(renderer());
style->applyTransform(currTransform, renderBox()->pixelSnappedBorderBoxRect().size(), applyOrigin);
makeMatrixRenderable(currTransform, canRender3DTransforms());
return currTransform;
}
// m_transform includes transform-origin, so we need to recompute the transform here.
if (applyOrigin == RenderStyle::ExcludeTransformOrigin) {
RenderBox* box = renderBox();
TransformationMatrix currTransform;
box->style()->applyTransform(currTransform, box->pixelSnappedBorderBoxRect().size(), RenderStyle::ExcludeTransformOrigin);
makeMatrixRenderable(currTransform, canRender3DTransforms());
return currTransform;
}
return *m_transform;
}
TransformationMatrix RenderLayer::renderableTransform(PaintBehavior paintBehavior) const
{
if (!m_transform)
return TransformationMatrix();
if (paintBehavior & PaintBehaviorFlattenCompositingLayers) {
TransformationMatrix matrix = *m_transform;
makeMatrixRenderable(matrix, false /* flatten 3d */);
return matrix;
}
return *m_transform;
}
static bool checkContainingBlockChainForPagination(RenderLayerModelObject* renderer, RenderBox* ancestorColumnsRenderer)
{
RenderView* view = renderer->view();
RenderLayerModelObject* prevBlock = renderer;
RenderBlock* containingBlock;
for (containingBlock = renderer->containingBlock();
containingBlock && containingBlock != view && containingBlock != ancestorColumnsRenderer;
containingBlock = containingBlock->containingBlock())
prevBlock = containingBlock;
// If the columns block wasn't in our containing block chain, then we aren't paginated by it.
if (containingBlock != ancestorColumnsRenderer)
return false;
// If the previous block is absolutely positioned, then we can't be paginated by the columns block.
if (prevBlock->isOutOfFlowPositioned())
return false;
// Otherwise we are paginated by the columns block.
return true;
}
bool RenderLayer::useRegionBasedColumns() const
{
const Settings* settings = renderer()->document().settings();
return settings && settings->regionBasedColumnsEnabled();
}
void RenderLayer::updatePagination()
{
m_isPaginated = false;
m_enclosingPaginationLayer = 0;
if (isComposited() || !parent())
return; // FIXME: We will have to deal with paginated compositing layers someday.
// FIXME: For now the RenderView can't be paginated. Eventually printing will move to a model where it is though.
// The main difference between the paginated booleans for the old column code and the new column code
// is that each paginated layer has to paint on its own with the new code. There is no
// recurring into child layers. This means that the m_isPaginated bits for the new column code can't just be set on
// "roots" that get split and paint all their descendants. Instead each layer has to be checked individually and
// genuinely know if it is going to have to split itself up when painting only its contents (and not any other descendant
// layers). We track an enclosingPaginationLayer instead of using a simple bit, since we want to be able to get back
// to that layer easily.
bool regionBasedColumnsUsed = useRegionBasedColumns();
if (regionBasedColumnsUsed && renderer()->isInFlowRenderFlowThread()) {
m_enclosingPaginationLayer = this;
return;
}
if (isNormalFlowOnly()) {
if (regionBasedColumnsUsed) {
// Content inside a transform is not considered to be paginated, since we simply
// paint the transform multiple times in each column, so we don't have to use
// fragments for the transformed content.
m_enclosingPaginationLayer = parent()->enclosingPaginationLayer();
if (m_enclosingPaginationLayer && m_enclosingPaginationLayer->hasTransform())
m_enclosingPaginationLayer = 0;
} else
m_isPaginated = parent()->renderer()->hasColumns();
return;
}
// For the new columns code, we want to walk up our containing block chain looking for an enclosing layer. Once
// we find one, then we just check its pagination status.
if (regionBasedColumnsUsed) {
RenderView* view = renderer()->view();
RenderBlock* containingBlock;
for (containingBlock = renderer()->containingBlock();
containingBlock && containingBlock != view;
containingBlock = containingBlock->containingBlock()) {
if (containingBlock->hasLayer()) {
// Content inside a transform is not considered to be paginated, since we simply
// paint the transform multiple times in each column, so we don't have to use
// fragments for the transformed content.
m_enclosingPaginationLayer = containingBlock->layer()->enclosingPaginationLayer();
if (m_enclosingPaginationLayer && m_enclosingPaginationLayer->hasTransform())
m_enclosingPaginationLayer = 0;
return;
}
}
return;
}
// If we're not normal flow, then we need to look for a multi-column object between us and our stacking container.
RenderLayer* ancestorStackingContainer = this->ancestorStackingContainer();
for (RenderLayer* curr = parent(); curr; curr = curr->parent()) {
if (curr->renderer()->hasColumns()) {
m_isPaginated = checkContainingBlockChainForPagination(renderer(), curr->renderBox());
return;
}
if (curr == ancestorStackingContainer)
return;
}
}
bool RenderLayer::canBeStackingContainer() const
{
if (isStackingContext() || !ancestorStackingContainer())
return true;
ASSERT(!m_canBePromotedToStackingContainerDirty);
return m_canBePromotedToStackingContainer;
}
void RenderLayer::setHasVisibleContent()
{
if (m_hasVisibleContent && !m_visibleContentStatusDirty) {
ASSERT(!parent() || parent()->hasVisibleDescendant());
return;
}
m_visibleContentStatusDirty = false;
m_hasVisibleContent = true;
computeRepaintRects(renderer()->containerForRepaint());
if (!isNormalFlowOnly()) {
// We don't collect invisible layers in z-order lists if we are not in compositing mode.
// As we became visible, we need to dirty our stacking containers ancestors to be properly
// collected. FIXME: When compositing, we could skip this dirtying phase.
for (RenderLayer* sc = ancestorStackingContainer(); sc; sc = sc->ancestorStackingContainer()) {
sc->dirtyZOrderLists();
if (sc->hasVisibleContent())
break;
}
}
if (parent())
parent()->setAncestorChainHasVisibleDescendant();
}
void RenderLayer::dirtyVisibleContentStatus()
{
m_visibleContentStatusDirty = true;
if (parent())
parent()->dirtyAncestorChainVisibleDescendantStatus();
}
void RenderLayer::dirtyAncestorChainVisibleDescendantStatus()
{
for (RenderLayer* layer = this; layer; layer = layer->parent()) {
if (layer->m_visibleDescendantStatusDirty)
break;
layer->m_visibleDescendantStatusDirty = true;
}
}
void RenderLayer::setAncestorChainHasVisibleDescendant()
{
for (RenderLayer* layer = this; layer; layer = layer->parent()) {
if (!layer->m_visibleDescendantStatusDirty && layer->hasVisibleDescendant())
break;
layer->m_hasVisibleDescendant = true;
layer->m_visibleDescendantStatusDirty = false;
}
}
void RenderLayer::updateHasUnclippedDescendant()
{
TRACE_EVENT0("blink_rendering", "RenderLayer::updateHasUnclippedDescendant");
ASSERT(renderer()->isOutOfFlowPositioned());
if (!m_hasVisibleContent && !m_hasVisibleDescendant)
return;
const RenderObject* containingBlock = renderer()->containingBlock();
setIsUnclippedDescendant(false);
for (RenderLayer* ancestor = parent(); ancestor && ancestor->renderer() != containingBlock; ancestor = ancestor->parent()) {
// TODO(vollick): This isn't quite right. Whenever ancestor is composited and clips
// overflow, we're technically unclipped. However, this will currently cause a huge
// number of layers to report that they are unclipped. Eventually, when we've formally
// separated the clipping, transform, opacity, and stacking trees here and in the
// compositor, we will be able to relax this restriction without it being prohibitively
// expensive (currently, we have to do a lot of work in the compositor to honor a
// clip child/parent relationship).
if (ancestor->needsCompositedScrolling())
setIsUnclippedDescendant(true);
ancestor->setHasUnclippedDescendant(true);
}
}
static bool subtreeContainsOutOfFlowPositionedLayer(const RenderLayer* subtreeRoot)
{
return (subtreeRoot->renderer() && subtreeRoot->renderer()->isOutOfFlowPositioned()) || subtreeRoot->hasOutOfFlowPositionedDescendant();
}
void RenderLayer::updateDescendantDependentFlags()
{
if (m_visibleDescendantStatusDirty || m_hasSelfPaintingLayerDescendantDirty || m_hasOutOfFlowPositionedDescendantDirty) {
m_hasVisibleDescendant = false;
m_hasSelfPaintingLayerDescendant = false;
m_hasOutOfFlowPositionedDescendant = false;
for (RenderLayer* child = firstChild(); child; child = child->nextSibling()) {
child->updateDescendantDependentFlags();
bool hasVisibleDescendant = child->m_hasVisibleContent || child->m_hasVisibleDescendant;
bool hasSelfPaintingLayerDescendant = child->isSelfPaintingLayer() || child->hasSelfPaintingLayerDescendant();
bool hasOutOfFlowPositionedDescendant = subtreeContainsOutOfFlowPositionedLayer(child);
m_hasVisibleDescendant |= hasVisibleDescendant;
m_hasSelfPaintingLayerDescendant |= hasSelfPaintingLayerDescendant;
m_hasOutOfFlowPositionedDescendant |= hasOutOfFlowPositionedDescendant;
if (m_hasVisibleDescendant && m_hasSelfPaintingLayerDescendant && hasOutOfFlowPositionedDescendant)
break;
}
m_visibleDescendantStatusDirty = false;
m_hasSelfPaintingLayerDescendantDirty = false;
m_hasOutOfFlowPositionedDescendantDirty = false;
}
if (m_visibleContentStatusDirty) {
if (renderer()->style()->visibility() == VISIBLE)
m_hasVisibleContent = true;
else {
// layer may be hidden but still have some visible content, check for this
m_hasVisibleContent = false;
RenderObject* r = renderer()->firstChild();
while (r) {
if (r->style()->visibility() == VISIBLE && !r->hasLayer()) {
m_hasVisibleContent = true;
break;
}
if (r->firstChild() && !r->hasLayer())
r = r->firstChild();
else if (r->nextSibling())
r = r->nextSibling();
else {
do {
r = r->parent();
if (r == renderer())
r = 0;
} while (r && !r->nextSibling());
if (r)
r = r->nextSibling();
}
}
}
m_visibleContentStatusDirty = false;
}
}
void RenderLayer::dirty3DTransformedDescendantStatus()
{
RenderLayer* curr = ancestorStackingContainer();
if (curr)
curr->m_3DTransformedDescendantStatusDirty = true;
// This propagates up through preserve-3d hierarchies to the enclosing flattening layer.
// Note that preserves3D() creates stacking context, so we can just run up the stacking containers.
while (curr && curr->preserves3D()) {
curr->m_3DTransformedDescendantStatusDirty = true;
curr = curr->ancestorStackingContainer();
}
}
// Return true if this layer or any preserve-3d descendants have 3d.
bool RenderLayer::update3DTransformedDescendantStatus()
{
if (m_3DTransformedDescendantStatusDirty) {
m_has3DTransformedDescendant = false;
updateZOrderLists();
// Transformed or preserve-3d descendants can only be in the z-order lists, not
// in the normal flow list, so we only need to check those.
if (Vector<RenderLayer*>* positiveZOrderList = posZOrderList()) {
for (unsigned i = 0; i < positiveZOrderList->size(); ++i)
m_has3DTransformedDescendant |= positiveZOrderList->at(i)->update3DTransformedDescendantStatus();
}
// Now check our negative z-index children.
if (Vector<RenderLayer*>* negativeZOrderList = negZOrderList()) {
for (unsigned i = 0; i < negativeZOrderList->size(); ++i)
m_has3DTransformedDescendant |= negativeZOrderList->at(i)->update3DTransformedDescendantStatus();
}
m_3DTransformedDescendantStatusDirty = false;
}
// If we live in a 3d hierarchy, then the layer at the root of that hierarchy needs
// the m_has3DTransformedDescendant set.
if (preserves3D())
return has3DTransform() || m_has3DTransformedDescendant;
return has3DTransform();
}
bool RenderLayer::updateLayerPosition()
{
LayoutPoint localPoint;
LayoutSize inlineBoundingBoxOffset; // We don't put this into the RenderLayer x/y for inlines, so we need to subtract it out when done.
if (renderer()->isInline() && renderer()->isRenderInline()) {
RenderInline* inlineFlow = toRenderInline(renderer());
IntRect lineBox = inlineFlow->linesBoundingBox();
setSize(lineBox.size());
inlineBoundingBoxOffset = toSize(lineBox.location());
localPoint += inlineBoundingBoxOffset;
} else if (RenderBox* box = renderBox()) {
// FIXME: Is snapping the size really needed here for the RenderBox case?
setSize(pixelSnappedIntSize(box->size(), box->location()));
localPoint += box->topLeftLocationOffset();
}
if (!renderer()->isOutOfFlowPositioned() && renderer()->parent()) {
// We must adjust our position by walking up the render tree looking for the
// nearest enclosing object with a layer.
RenderObject* curr = renderer()->parent();
while (curr && !curr->hasLayer()) {
if (curr->isBox() && !curr->isTableRow()) {
// Rows and cells share the same coordinate space (that of the section).
// Omit them when computing our xpos/ypos.
localPoint += toRenderBox(curr)->topLeftLocationOffset();
}
curr = curr->parent();
}
if (curr->isBox() && curr->isTableRow()) {
// Put ourselves into the row coordinate space.
localPoint -= toRenderBox(curr)->topLeftLocationOffset();
}
}
// Subtract our parent's scroll offset.
if (renderer()->isOutOfFlowPositioned() && enclosingPositionedAncestor()) {
RenderLayer* positionedParent = enclosingPositionedAncestor();
// For positioned layers, we subtract out the enclosing positioned layer's scroll offset.
if (positionedParent->renderer()->hasOverflowClip()) {
LayoutSize offset = positionedParent->scrolledContentOffset();
localPoint -= offset;
}
if (renderer()->isOutOfFlowPositioned() && positionedParent->renderer()->isInFlowPositioned() && positionedParent->renderer()->isRenderInline()) {
LayoutSize offset = toRenderInline(positionedParent->renderer())->offsetForInFlowPositionedInline(toRenderBox(renderer()));
localPoint += offset;
}
} else if (parent()) {
if (isComposited()) {
// FIXME: Composited layers ignore pagination, so about the best we can do is make sure they're offset into the appropriate column.
// They won't split across columns properly.
LayoutSize columnOffset;
if (!parent()->renderer()->hasColumns() && parent()->renderer()->isRoot() && renderer()->view()->hasColumns())
renderer()->view()->adjustForColumns(columnOffset, localPoint);
else
parent()->renderer()->adjustForColumns(columnOffset, localPoint);
localPoint += columnOffset;
}
if (parent()->renderer()->hasOverflowClip()) {
IntSize scrollOffset = parent()->scrolledContentOffset();
localPoint -= scrollOffset;
}
}
bool positionOrOffsetChanged = false;
if (renderer()->isInFlowPositioned()) {
LayoutSize newOffset = toRenderBoxModelObject(renderer())->offsetForInFlowPosition();
positionOrOffsetChanged = newOffset != m_offsetForInFlowPosition;
m_offsetForInFlowPosition = newOffset;
localPoint.move(m_offsetForInFlowPosition);
} else {
m_offsetForInFlowPosition = LayoutSize();
}
// FIXME: We'd really like to just get rid of the concept of a layer rectangle and rely on the renderers.
localPoint -= inlineBoundingBoxOffset;
positionOrOffsetChanged |= location() != localPoint;
setLocation(localPoint);
return positionOrOffsetChanged;
}
TransformationMatrix RenderLayer::perspectiveTransform() const
{
if (!renderer()->hasTransform())
return TransformationMatrix();
RenderStyle* style = renderer()->style();
if (!style->hasPerspective())
return TransformationMatrix();
// Maybe fetch the perspective from the backing?
const IntRect borderBox = toRenderBox(renderer())->pixelSnappedBorderBoxRect();
const float boxWidth = borderBox.width();
const float boxHeight = borderBox.height();
float perspectiveOriginX = floatValueForLength(style->perspectiveOriginX(), boxWidth);
float perspectiveOriginY = floatValueForLength(style->perspectiveOriginY(), boxHeight);
// A perspective origin of 0,0 makes the vanishing point in the center of the element.
// We want it to be in the top-left, so subtract half the height and width.
perspectiveOriginX -= boxWidth / 2.0f;
perspectiveOriginY -= boxHeight / 2.0f;
TransformationMatrix t;
t.translate(perspectiveOriginX, perspectiveOriginY);
t.applyPerspective(style->perspective());
t.translate(-perspectiveOriginX, -perspectiveOriginY);
return t;
}
FloatPoint RenderLayer::perspectiveOrigin() const
{
if (!renderer()->hasTransform())
return FloatPoint();
const LayoutRect borderBox = toRenderBox(renderer())->borderBoxRect();
RenderStyle* style = renderer()->style();
return FloatPoint(floatValueForLength(style->perspectiveOriginX(), borderBox.width()),
floatValueForLength(style->perspectiveOriginY(), borderBox.height()));
}
RenderLayer* RenderLayer::ancestorStackingContainer() const
{
RenderLayer* ancestor = parent();
while (ancestor && !ancestor->isStackingContainer())
ancestor = ancestor->parent();
return ancestor;
}
RenderLayer* RenderLayer::ancestorStackingContext() const
{
RenderLayer* ancestor = parent();
while (ancestor && !ancestor->isStackingContext())
ancestor = ancestor->parent();
return ancestor;
}
static inline bool isFixedPositionedContainer(RenderLayer* layer)
{
return layer->isRootLayer() || layer->hasTransform();
}
RenderLayer* RenderLayer::enclosingPositionedAncestor() const
{
RenderLayer* curr = parent();
while (curr && !isPositionedContainer(curr))
curr = curr->parent();
return curr;
}
RenderLayer* RenderLayer::enclosingScrollableLayer() const
{
for (RenderObject* nextRenderer = renderer()->parent(); nextRenderer; nextRenderer = nextRenderer->parent()) {
if (nextRenderer->isBox() && toRenderBox(nextRenderer)->canBeScrolledAndHasScrollableArea())
return nextRenderer->enclosingLayer();
}
return 0;
}
IntRect RenderLayer::scrollableAreaBoundingBox() const
{
return renderer()->absoluteBoundingBoxRect();
}
bool RenderLayer::userInputScrollable(ScrollbarOrientation orientation) const
{
RenderBox* box = renderBox();
ASSERT(box);
if (box->isIntristicallyScrollable(orientation))
return true;
EOverflow overflowStyle = (orientation == HorizontalScrollbar) ?
renderer()->style()->overflowX() : renderer()->style()->overflowY();
return (overflowStyle == OSCROLL || overflowStyle == OAUTO || overflowStyle == OOVERLAY);
}
int RenderLayer::pageStep(ScrollbarOrientation orientation) const
{
RenderBox* box = renderBox();
ASSERT(box);
int length = (orientation == HorizontalScrollbar) ?
box->pixelSnappedClientWidth() : box->pixelSnappedClientHeight();
int minPageStep = static_cast<float>(length) * ScrollableArea::minFractionToStepWhenPaging();
int pageStep = max(minPageStep, length - ScrollableArea::maxOverlapBetweenPages());
return max(pageStep, 1);
}
RenderLayer* RenderLayer::enclosingTransformedAncestor() const
{
RenderLayer* curr = parent();
while (curr && !curr->isRootLayer() && !curr->transform())
curr = curr->parent();
return curr;
}
static inline const RenderLayer* compositingContainer(const RenderLayer* layer)
{
return layer->isNormalFlowOnly() ? layer->parent() : layer->ancestorStackingContainer();
}
inline bool RenderLayer::shouldRepaintAfterLayout() const
{
if (m_repaintStatus == NeedsNormalRepaint)
return true;
// Composited layers that were moved during a positioned movement only
// layout, don't need to be repainted. They just need to be recomposited.
ASSERT(m_repaintStatus == NeedsFullRepaintForPositionedMovementLayout);
return !isComposited();
}
RenderLayer* RenderLayer::enclosingCompositingLayer(bool includeSelf) const
{
if (includeSelf && isComposited())
return const_cast<RenderLayer*>(this);
for (const RenderLayer* curr = compositingContainer(this); curr; curr = compositingContainer(curr)) {
if (curr->isComposited())
return const_cast<RenderLayer*>(curr);
}
return 0;
}
RenderLayer* RenderLayer::enclosingCompositingLayerForRepaint(bool includeSelf) const
{
if (includeSelf && isComposited() && !backing()->paintsIntoCompositedAncestor())
return const_cast<RenderLayer*>(this);
for (const RenderLayer* curr = compositingContainer(this); curr; curr = compositingContainer(curr)) {
if (curr->isComposited() && !curr->backing()->paintsIntoCompositedAncestor())
return const_cast<RenderLayer*>(curr);
}
return 0;
}
RenderLayer* RenderLayer::ancestorScrollingLayer() const
{
if (!acceleratedCompositingForOverflowScrollEnabled())
return 0;
RenderObject* containingBlock = renderer()->containingBlock();
if (!containingBlock)
return 0;
for (RenderLayer* ancestorLayer = containingBlock->enclosingLayer(); ancestorLayer; ancestorLayer = ancestorLayer->parent()) {
if (ancestorLayer->needsCompositedScrolling())
return ancestorLayer;
}
return 0;
}
RenderLayer* RenderLayer::enclosingFilterLayer(bool includeSelf) const
{
const RenderLayer* curr = includeSelf ? this : parent();
for (; curr; curr = curr->parent()) {
if (curr->requiresFullLayerImageForFilters())
return const_cast<RenderLayer*>(curr);
}
return 0;
}
RenderLayer* RenderLayer::enclosingFilterRepaintLayer() const
{
for (const RenderLayer* curr = this; curr; curr = curr->parent()) {
if ((curr != this && curr->requiresFullLayerImageForFilters()) || curr->isComposited() || curr->isRootLayer())
return const_cast<RenderLayer*>(curr);
}
return 0;
}
void RenderLayer::setFilterBackendNeedsRepaintingInRect(const LayoutRect& rect)
{
if (rect.isEmpty())
return;
LayoutRect rectForRepaint = rect;
renderer()->style()->filterOutsets().expandRect(rectForRepaint);
RenderLayerFilterInfo* filterInfo = this->filterInfo();
ASSERT(filterInfo);
filterInfo->expandDirtySourceRect(rectForRepaint);
ASSERT(filterInfo->renderer());
if (filterInfo->renderer()->hasCustomShaderFilter()) {
// If we have at least one custom shader, we need to update the whole bounding box of the layer, because the
// shader can address any ouput pixel.
// Note: This is only for output rect, so there's no need to expand the dirty source rect.
rectForRepaint.unite(calculateLayerBounds(this));
}
RenderLayer* parentLayer = enclosingFilterRepaintLayer();
ASSERT(parentLayer);
FloatQuad repaintQuad(rectForRepaint);
LayoutRect parentLayerRect = renderer()->localToContainerQuad(repaintQuad, parentLayer->renderer()).enclosingBoundingBox();
if (parentLayer->isComposited()) {
parentLayer->setBackingNeedsRepaintInRect(parentLayerRect);
return;
}
if (parentLayer->paintsWithFilters()) {
parentLayer->setFilterBackendNeedsRepaintingInRect(parentLayerRect);
return;
}
if (parentLayer->isRootLayer()) {
RenderView* view = toRenderView(parentLayer->renderer());
view->repaintViewRectangle(parentLayerRect);
return;
}
ASSERT_NOT_REACHED();
}
bool RenderLayer::hasAncestorWithFilterOutsets() const
{
for (const RenderLayer* curr = this; curr; curr = curr->parent()) {
RenderLayerModelObject* renderer = curr->renderer();
if (renderer->style()->hasFilterOutsets())
return true;
}
return false;
}
RenderLayer* RenderLayer::clippingRootForPainting() const
{
if (isComposited())
return const_cast<RenderLayer*>(this);
const RenderLayer* current = this;
while (current) {
if (current->isRootLayer())
return const_cast<RenderLayer*>(current);
current = compositingContainer(current);
ASSERT(current);
if (current->transform()
|| (current->isComposited() && !current->backing()->paintsIntoCompositedAncestor())
)
return const_cast<RenderLayer*>(current);
}
ASSERT_NOT_REACHED();
return 0;
}
LayoutPoint RenderLayer::absoluteToContents(const LayoutPoint& absolutePoint) const
{
// We don't use convertToLayerCoords because it doesn't know about transforms
return roundedLayoutPoint(renderer()->absoluteToLocal(absolutePoint, UseTransforms));
}
bool RenderLayer::cannotBlitToWindow() const
{
if (isTransparent() || hasReflection() || hasTransform())
return true;
if (!parent())
return false;
return parent()->cannotBlitToWindow();
}
bool RenderLayer::isTransparent() const
{
// FIXME: This seems incorrect; why would SVG layers be opaque?
if (renderer()->node() && renderer()->node()->namespaceURI() == SVGNames::svgNamespaceURI)
return false;
return renderer()->isTransparent() || renderer()->hasMask();
}
RenderLayer* RenderLayer::transparentPaintingAncestor()
{
if (isComposited())
return 0;
for (RenderLayer* curr = parent(); curr; curr = curr->parent()) {
if (curr->isComposited())
return 0;
if (curr->isTransparent())
return curr;
}
return 0;
}
enum TransparencyClipBoxBehavior {
PaintingTransparencyClipBox,
HitTestingTransparencyClipBox
};
enum TransparencyClipBoxMode {
DescendantsOfTransparencyClipBox,
RootOfTransparencyClipBox
};
static LayoutRect transparencyClipBox(const RenderLayer*, const RenderLayer* rootLayer, TransparencyClipBoxBehavior, TransparencyClipBoxMode, PaintBehavior = 0);
static void expandClipRectForDescendantsAndReflection(LayoutRect& clipRect, const RenderLayer* layer, const RenderLayer* rootLayer,
TransparencyClipBoxBehavior transparencyBehavior, PaintBehavior paintBehavior)
{
// If we have a mask, then the clip is limited to the border box area (and there is
// no need to examine child layers).
if (!layer->renderer()->hasMask()) {
// Note: we don't have to walk z-order lists since transparent elements always establish
// a stacking container. This means we can just walk the layer tree directly.
for (RenderLayer* curr = layer->firstChild(); curr; curr = curr->nextSibling()) {
if (!layer->reflection() || layer->reflectionLayer() != curr)
clipRect.unite(transparencyClipBox(curr, rootLayer, transparencyBehavior, DescendantsOfTransparencyClipBox, paintBehavior));
}
}
// If we have a reflection, then we need to account for that when we push the clip. Reflect our entire
// current transparencyClipBox to catch all child layers.
// FIXME: Accelerated compositing will eventually want to do something smart here to avoid incorporating this
// size into the parent layer.
if (layer->renderer()->hasReflection()) {
LayoutPoint delta;
layer->convertToLayerCoords(rootLayer, delta);
clipRect.move(-delta.x(), -delta.y());
clipRect.unite(layer->renderBox()->reflectedRect(clipRect));
clipRect.moveBy(delta);
}
}
static LayoutRect transparencyClipBox(const RenderLayer* layer, const RenderLayer* rootLayer, TransparencyClipBoxBehavior transparencyBehavior,
TransparencyClipBoxMode transparencyMode, PaintBehavior paintBehavior)
{
// FIXME: Although this function completely ignores CSS-imposed clipping, we did already intersect with the
// paintDirtyRect, and that should cut down on the amount we have to paint. Still it
// would be better to respect clips.
if (rootLayer != layer && ((transparencyBehavior == PaintingTransparencyClipBox && layer->paintsWithTransform(paintBehavior))
|| (transparencyBehavior == HitTestingTransparencyClipBox && layer->hasTransform()))) {
// The best we can do here is to use enclosed bounding boxes to establish a "fuzzy" enough clip to encompass
// the transformed layer and all of its children.
const RenderLayer* paginationLayer = transparencyMode == DescendantsOfTransparencyClipBox ? layer->enclosingPaginationLayer() : 0;
const RenderLayer* rootLayerForTransform = paginationLayer ? paginationLayer : rootLayer;
LayoutPoint delta;
layer->convertToLayerCoords(rootLayerForTransform, delta);
TransformationMatrix transform;
transform.translate(delta.x(), delta.y());
transform = transform * *layer->transform();
// We don't use fragment boxes when collecting a transformed layer's bounding box, since it always
// paints unfragmented.
LayoutRect clipRect = layer->boundingBox(layer);
expandClipRectForDescendantsAndReflection(clipRect, layer, layer, transparencyBehavior, paintBehavior);
layer->renderer()->style()->filterOutsets().expandRect(clipRect);
LayoutRect result = transform.mapRect(clipRect);
if (!paginationLayer)
return result;
// We have to break up the transformed extent across our columns.
// Split our box up into the actual fragment boxes that render in the columns/pages and unite those together to
// get our true bounding box.
RenderFlowThread* enclosingFlowThread = toRenderFlowThread(paginationLayer->renderer());
result = enclosingFlowThread->fragmentsBoundingBox(result);
LayoutPoint rootLayerDelta;
paginationLayer->convertToLayerCoords(rootLayer, rootLayerDelta);
result.moveBy(rootLayerDelta);
return result;
}
LayoutRect clipRect = layer->boundingBox(rootLayer, RenderLayer::UseFragmentBoxes);
expandClipRectForDescendantsAndReflection(clipRect, layer, rootLayer, transparencyBehavior, paintBehavior);
layer->renderer()->style()->filterOutsets().expandRect(clipRect);
return clipRect;
}
LayoutRect RenderLayer::paintingExtent(const RenderLayer* rootLayer, const LayoutRect& paintDirtyRect, PaintBehavior paintBehavior)
{
return intersection(transparencyClipBox(this, rootLayer, PaintingTransparencyClipBox, RootOfTransparencyClipBox, paintBehavior), paintDirtyRect);
}
void RenderLayer::beginTransparencyLayers(GraphicsContext* context, const RenderLayer* rootLayer, const LayoutRect& paintDirtyRect, PaintBehavior paintBehavior)
{
if (context->paintingDisabled() || (paintsWithTransparency(paintBehavior) && m_usedTransparency))
return;
RenderLayer* ancestor = transparentPaintingAncestor();
if (ancestor)
ancestor->beginTransparencyLayers(context, rootLayer, paintDirtyRect, paintBehavior);
if (paintsWithTransparency(paintBehavior)) {
m_usedTransparency = true;
context->save();
LayoutRect clipRect = paintingExtent(rootLayer, paintDirtyRect, paintBehavior);
context->clip(clipRect);
context->beginTransparencyLayer(renderer()->opacity());
#ifdef REVEAL_TRANSPARENCY_LAYERS
context->setFillColor(Color(0.0f, 0.0f, 0.5f, 0.2f));
context->fillRect(clipRect);
#endif
}
}
void* RenderLayer::operator new(size_t sz)
{
return partitionAlloc(Partitions::getRenderingPartition(), sz);
}
void RenderLayer::operator delete(void* ptr)
{
partitionFree(ptr);
}
void RenderLayer::addChild(RenderLayer* child, RenderLayer* beforeChild)
{
RenderLayer* prevSibling = beforeChild ? beforeChild->previousSibling() : lastChild();
if (prevSibling) {
child->setPreviousSibling(prevSibling);
prevSibling->setNextSibling(child);
ASSERT(prevSibling != child);
} else
setFirstChild(child);
if (beforeChild) {
beforeChild->setPreviousSibling(child);
child->setNextSibling(beforeChild);
ASSERT(beforeChild != child);
} else
setLastChild(child);
child->setParent(this);
if (child->isNormalFlowOnly())
dirtyNormalFlowList();
if (!child->isNormalFlowOnly() || child->firstChild()) {
// Dirty the z-order list in which we are contained. The ancestorStackingContainer() can be null in the
// case where we're building up generated content layers. This is ok, since the lists will start
// off dirty in that case anyway.
child->dirtyStackingContainerZOrderLists();
}
child->updateDescendantDependentFlags();
if (child->m_hasVisibleContent || child->m_hasVisibleDescendant)
setAncestorChainHasVisibleDescendant();
if (child->isSelfPaintingLayer() || child->hasSelfPaintingLayerDescendant())
setAncestorChainHasSelfPaintingLayerDescendant();
if (subtreeContainsOutOfFlowPositionedLayer(child)) {
// Now that the out of flow positioned descendant is in the tree, we
// need to tell the compositor to reevaluate the compositing
// requirements since we may be able to mark more layers as having
// an 'unclipped' descendant.
compositor()->setNeedsUpdateCompositingRequirementsState();
setAncestorChainHasOutOfFlowPositionedDescendant();
}
// When we first dirty a layer, we will also dirty all the siblings in that
// layer's stacking context. We need to manually do it here as well, in case
// we're adding this layer after the stacking context has already been
// updated.
child->m_canBePromotedToStackingContainerDirty = true;
compositor()->layerWasAdded(this, child);
}
RenderLayer* RenderLayer::removeChild(RenderLayer* oldChild)
{
if (!renderer()->documentBeingDestroyed())
compositor()->layerWillBeRemoved(this, oldChild);
// remove the child
if (oldChild->previousSibling())
oldChild->previousSibling()->setNextSibling(oldChild->nextSibling());
if (oldChild->nextSibling())
oldChild->nextSibling()->setPreviousSibling(oldChild->previousSibling());
if (m_first == oldChild)
m_first = oldChild->nextSibling();
if (m_last == oldChild)
m_last = oldChild->previousSibling();
if (oldChild->isNormalFlowOnly())
dirtyNormalFlowList();
if (!oldChild->isNormalFlowOnly() || oldChild->firstChild()) {
// Dirty the z-order list in which we are contained. When called via the
// reattachment process in removeOnlyThisLayer, the layer may already be disconnected
// from the main layer tree, so we need to null-check the |stackingContainer| value.
oldChild->dirtyStackingContainerZOrderLists();
}
oldChild->setPreviousSibling(0);
oldChild->setNextSibling(0);
oldChild->setParent(0);
oldChild->updateDescendantDependentFlags();
if (subtreeContainsOutOfFlowPositionedLayer(oldChild)) {
// It may now be the case that a layer no longer has an unclipped
// descendant. Let the compositor know that it needs to reevaluate
// its compositing requirements to check this.
compositor()->setNeedsUpdateCompositingRequirementsState();
dirtyAncestorChainHasOutOfFlowPositionedDescendantStatus();
}
if (oldChild->m_hasVisibleContent || oldChild->m_hasVisibleDescendant)
dirtyAncestorChainVisibleDescendantStatus();
if (oldChild->isSelfPaintingLayer() || oldChild->hasSelfPaintingLayerDescendant())
dirtyAncestorChainHasSelfPaintingLayerDescendantStatus();
return oldChild;
}
void RenderLayer::removeOnlyThisLayer()
{
if (!m_parent)
return;
// Mark that we are about to lose our layer. This makes render tree
// walks ignore this layer while we're removing it.
m_renderer->setHasLayer(false);
compositor()->layerWillBeRemoved(m_parent, this);
// Dirty the clip rects.
clearClipRectsIncludingDescendants();
RenderLayer* nextSib = nextSibling();
// Remove the child reflection layer before moving other child layers.
// The reflection layer should not be moved to the parent.
if (reflection())
removeChild(reflectionLayer());
// Now walk our kids and reattach them to our parent.
RenderLayer* current = m_first;
while (current) {
RenderLayer* next = current->nextSibling();
removeChild(current);
m_parent->addChild(current, nextSib);
current->setRepaintStatus(NeedsFullRepaint);
// updateLayerPositions depends on hasLayer() already being false for proper layout.
ASSERT(!renderer()->hasLayer());
current->updateLayerPositions(0); // FIXME: use geometry map.
current = next;
}
// Remove us from the parent.
m_parent->removeChild(this);
m_renderer->destroyLayer();
}
void RenderLayer::insertOnlyThisLayer()
{
if (!m_parent && renderer()->parent()) {
// We need to connect ourselves when our renderer() has a parent.
// Find our enclosingLayer and add ourselves.
RenderLayer* parentLayer = renderer()->parent()->enclosingLayer();
ASSERT(parentLayer);
RenderLayer* beforeChild = parentLayer->reflectionLayer() != this ? renderer()->parent()->findNextLayer(parentLayer, renderer()) : 0;
parentLayer->addChild(this, beforeChild);
}
// Remove all descendant layers from the hierarchy and add them to the new position.
for (RenderObject* curr = renderer()->firstChild(); curr; curr = curr->nextSibling())
curr->moveLayers(m_parent, this);
// Clear out all the clip rects.
clearClipRectsIncludingDescendants();
}
void RenderLayer::convertToPixelSnappedLayerCoords(const RenderLayer* ancestorLayer, IntPoint& roundedLocation) const
{
LayoutPoint location = roundedLocation;
convertToLayerCoords(ancestorLayer, location);
roundedLocation = roundedIntPoint(location);
}
void RenderLayer::convertToPixelSnappedLayerCoords(const RenderLayer* ancestorLayer, IntRect& roundedRect) const
{
LayoutRect rect = roundedRect;
convertToLayerCoords(ancestorLayer, rect);
roundedRect = pixelSnappedIntRect(rect);
}
// Returns the layer reached on the walk up towards the ancestor.
static inline const RenderLayer* accumulateOffsetTowardsAncestor(const RenderLayer* layer, const RenderLayer* ancestorLayer, LayoutPoint& location)
{
ASSERT(ancestorLayer != layer);
const RenderLayerModelObject* renderer = layer->renderer();
EPosition position = renderer->style()->position();
// FIXME: Special casing RenderFlowThread so much for fixed positioning here is not great.
RenderFlowThread* fixedFlowThreadContainer = position == FixedPosition ? renderer->flowThreadContainingBlock() : 0;
if (fixedFlowThreadContainer && !fixedFlowThreadContainer->isOutOfFlowPositioned())
fixedFlowThreadContainer = 0;
// FIXME: Positioning of out-of-flow(fixed, absolute) elements collected in a RenderFlowThread
// may need to be revisited in a future patch.
// If the fixed renderer is inside a RenderFlowThread, we should not compute location using localToAbsolute,
// since localToAbsolute maps the coordinates from named flow to regions coordinates and regions can be
// positioned in a completely different place in the viewport (RenderView).
if (position == FixedPosition && !fixedFlowThreadContainer && (!ancestorLayer || ancestorLayer == renderer->view()->layer())) {
// If the fixed layer's container is the root, just add in the offset of the view. We can obtain this by calling
// localToAbsolute() on the RenderView.
FloatPoint absPos = renderer->localToAbsolute(FloatPoint(), IsFixed);
location += LayoutSize(absPos.x(), absPos.y());
return ancestorLayer;
}
// For the fixed positioned elements inside a render flow thread, we should also skip the code path below
// Otherwise, for the case of ancestorLayer == rootLayer and fixed positioned element child of a transformed
// element in render flow thread, we will hit the fixed positioned container before hitting the ancestor layer.
if (position == FixedPosition && !fixedFlowThreadContainer) {
// For a fixed layers, we need to walk up to the root to see if there's a fixed position container
// (e.g. a transformed layer). It's an error to call convertToLayerCoords() across a layer with a transform,
// so we should always find the ancestor at or before we find the fixed position container.
RenderLayer* fixedPositionContainerLayer = 0;
bool foundAncestor = false;
for (RenderLayer* currLayer = layer->parent(); currLayer; currLayer = currLayer->parent()) {
if (currLayer == ancestorLayer)
foundAncestor = true;
if (isFixedPositionedContainer(currLayer)) {
fixedPositionContainerLayer = currLayer;
ASSERT_UNUSED(foundAncestor, foundAncestor);
break;
}
}
ASSERT(fixedPositionContainerLayer); // We should have hit the RenderView's layer at least.
if (fixedPositionContainerLayer != ancestorLayer) {
LayoutPoint fixedContainerCoords;
layer->convertToLayerCoords(fixedPositionContainerLayer, fixedContainerCoords);
LayoutPoint ancestorCoords;
ancestorLayer->convertToLayerCoords(fixedPositionContainerLayer, ancestorCoords);
location += (fixedContainerCoords - ancestorCoords);
} else {
location += toSize(layer->location());
}
return ancestorLayer;
}
RenderLayer* parentLayer;
if (position == AbsolutePosition || position == FixedPosition) {
// Do what enclosingPositionedAncestor() does, but check for ancestorLayer along the way.
parentLayer = layer->parent();
bool foundAncestorFirst = false;
while (parentLayer) {
// RenderFlowThread is a positioned container, child of RenderView, positioned at (0,0).
// This implies that, for out-of-flow positioned elements inside a RenderFlowThread,
// we are bailing out before reaching root layer.
if (isPositionedContainer(parentLayer))
break;
if (parentLayer == ancestorLayer) {
foundAncestorFirst = true;
break;
}
parentLayer = parentLayer->parent();
}
// We should not reach RenderView layer past the RenderFlowThread layer for any
// children of the RenderFlowThread.
if (renderer->flowThreadContainingBlock() && !layer->isOutOfFlowRenderFlowThread())
ASSERT(parentLayer != renderer->view()->layer());
if (foundAncestorFirst) {
// Found ancestorLayer before the abs. positioned container, so compute offset of both relative
// to enclosingPositionedAncestor and subtract.
RenderLayer* positionedAncestor = parentLayer->enclosingPositionedAncestor();
LayoutPoint thisCoords;
layer->convertToLayerCoords(positionedAncestor, thisCoords);
LayoutPoint ancestorCoords;
ancestorLayer->convertToLayerCoords(positionedAncestor, ancestorCoords);
location += (thisCoords - ancestorCoords);
return ancestorLayer;
}
} else
parentLayer = layer->parent();
if (!parentLayer)
return 0;
location += toSize(layer->location());
return parentLayer;
}
void RenderLayer::convertToLayerCoords(const RenderLayer* ancestorLayer, LayoutPoint& location) const
{
if (ancestorLayer == this)
return;
const RenderLayer* currLayer = this;
while (currLayer && currLayer != ancestorLayer)
currLayer = accumulateOffsetTowardsAncestor(currLayer, ancestorLayer, location);
}
void RenderLayer::convertToLayerCoords(const RenderLayer* ancestorLayer, LayoutRect& rect) const
{
LayoutPoint delta;
convertToLayerCoords(ancestorLayer, delta);
rect.move(-delta.x(), -delta.y());
}
bool RenderLayer::usesCompositedScrolling() const
{
RenderBox* box = renderBox();
// Scroll form controls on the main thread so they exhibit correct touch scroll event bubbling
if (box && (box->isIntristicallyScrollable(VerticalScrollbar) || box->isIntristicallyScrollable(HorizontalScrollbar)))
return false;
return isComposited() && backing()->scrollingLayer();
}
bool RenderLayer::needsCompositedScrolling() const
{
if (!compositorDrivenAcceleratedScrollingEnabled())
return needsToBeStackingContainer();
if (FrameView* frameView = renderer()->view()->frameView())
return frameView->containsScrollableArea(scrollableArea());
return false;
}
bool RenderLayer::needsToBeStackingContainer() const
{
switch (m_forceNeedsCompositedScrolling) {
case DoNotForceCompositedScrolling:
return m_needsCompositedScrolling;
case CompositedScrollingAlwaysOn:
return true;
case CompositedScrollingAlwaysOff:
return false;
}
ASSERT_NOT_REACHED();
return m_needsCompositedScrolling;
}
void RenderLayer::updateNeedsCompositedScrolling()
{
TRACE_EVENT0("comp-scroll", "RenderLayer::updateNeedsCompositedScrolling");
updateCanBeStackingContainer();
updateDescendantDependentFlags();
ASSERT(renderer()->view()->frameView() && renderer()->view()->frameView()->containsScrollableArea(scrollableArea()));
bool needsCompositedScrolling = acceleratedCompositingForOverflowScrollEnabled()
&& canBeStackingContainer()
&& !hasUnclippedDescendant();
// 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 (acceleratedCompositingForOverflowScrollEnabled())
HistogramSupport::histogramEnumeration("Renderer.NeedsCompositedScrolling", needsCompositedScrolling, 2);
setNeedsCompositedScrolling(needsCompositedScrolling);
}
void RenderLayer::setNeedsCompositedScrolling(bool needsCompositedScrolling)
{
if (m_needsCompositedScrolling == needsCompositedScrolling)
return;
m_needsCompositedScrolling = needsCompositedScrolling;
// 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_canBePromotedToStackingContainer for either us or our stacking context
// or container.
didUpdateNeedsCompositedScrolling();
}
void RenderLayer::setForceNeedsCompositedScrolling(RenderLayer::ForceNeedsCompositedScrollingMode mode)
{
if (m_forceNeedsCompositedScrolling == mode)
return;
m_forceNeedsCompositedScrolling = mode;
didUpdateNeedsCompositedScrolling();
}
void RenderLayer::didUpdateNeedsCompositedScrolling()
{
updateIsNormalFlowOnly();
updateSelfPaintingLayer();
if (isStackingContainer())
dirtyZOrderLists();
else
clearZOrderLists();
dirtyStackingContainerZOrderLists();
compositor()->setShouldReevaluateCompositingAfterLayout();
compositor()->setCompositingLayersNeedRebuild();
}
static inline int adjustedScrollDelta(int beginningDelta) {
// This implemention matches Firefox's.
// http://mxr.mozilla.org/firefox/source/toolkit/content/widgets/browser.xml#856.
const int speedReducer = 12;
int adjustedDelta = beginningDelta / speedReducer;
if (adjustedDelta > 1)
adjustedDelta = static_cast<int>(adjustedDelta * sqrt(static_cast<double>(adjustedDelta))) - 1;
else if (adjustedDelta < -1)
adjustedDelta = static_cast<int>(adjustedDelta * sqrt(static_cast<double>(-adjustedDelta))) + 1;
return adjustedDelta;
}
static inline IntSize adjustedScrollDelta(const IntSize& delta)
{
return IntSize(adjustedScrollDelta(delta.width()), adjustedScrollDelta(delta.height()));
}
void RenderLayer::panScrollFromPoint(const IntPoint& sourcePoint)
{
Frame* frame = renderer()->frame();
if (!frame)
return;
IntPoint lastKnownMousePosition = frame->eventHandler()->lastKnownMousePosition();
// We need to check if the last known mouse position is out of the window. When the mouse is out of the window, the position is incoherent
static IntPoint previousMousePosition;
if (lastKnownMousePosition.x() < 0 || lastKnownMousePosition.y() < 0)
lastKnownMousePosition = previousMousePosition;
else
previousMousePosition = lastKnownMousePosition;
IntSize delta = lastKnownMousePosition - sourcePoint;
if (abs(delta.width()) <= ScrollView::noPanScrollRadius) // at the center we let the space for the icon
delta.setWidth(0);
if (abs(delta.height()) <= ScrollView::noPanScrollRadius)
delta.setHeight(0);
scrollByRecursively(adjustedScrollDelta(delta), ScrollOffsetClamped);
}
void RenderLayer::scrollByRecursively(const IntSize& delta, ScrollOffsetClamping clamp)
{
if (delta.isZero())
return;
bool restrictedByLineClamp = false;
if (renderer()->parent())
restrictedByLineClamp = !renderer()->parent()->style()->lineClamp().isNone();
if (renderer()->hasOverflowClip() && !restrictedByLineClamp) {
IntSize newScrollOffset = adjustedScrollOffset() + delta;
m_scrollableArea->scrollToOffset(newScrollOffset, clamp);
// If this layer can't do the scroll we ask the next layer up that can scroll to try
IntSize remainingScrollOffset = newScrollOffset - adjustedScrollOffset();
if (!remainingScrollOffset.isZero() && renderer()->parent()) {
if (RenderLayer* scrollableLayer = enclosingScrollableLayer())
scrollableLayer->scrollByRecursively(remainingScrollOffset, clamp);
Frame* frame = renderer()->frame();
if (frame && frame->page())
frame->page()->updateAutoscrollRenderer();
}
} else if (renderer()->view()->frameView()) {
// If we are here, we were called on a renderer that can be programmatically scrolled, but doesn't
// have an overflow clip. Which means that it is a document node that can be scrolled.
renderer()->view()->frameView()->scrollBy(delta);
// FIXME: If we didn't scroll the whole way, do we want to try looking at the frames ownerElement?
// https://bugs.webkit.org/show_bug.cgi?id=28237
}
}
void RenderLayer::scrollToOffset(const IntSize& scrollOffset, ScrollOffsetClamping clamp)
{
m_scrollableArea->scrollToOffset(scrollOffset, clamp);
}
static inline bool frameElementAndViewPermitScroll(HTMLFrameElementBase* frameElementBase, FrameView* frameView)
{
// If scrollbars aren't explicitly forbidden, permit scrolling.
if (frameElementBase && frameElementBase->scrollingMode() != ScrollbarAlwaysOff)
return true;
// If scrollbars are forbidden, user initiated scrolls should obviously be ignored.
if (frameView->wasScrolledByUser())
return false;
// Forbid autoscrolls when scrollbars are off, but permits other programmatic scrolls,
// like navigation to an anchor.
Page* page = frameView->frame().page();
if (!page)
return false;
return !page->autoscrollInProgress();
}
void RenderLayer::scrollRectToVisible(const LayoutRect& rect, const ScrollAlignment& alignX, const ScrollAlignment& alignY)
{
RenderLayer* parentLayer = 0;
LayoutRect newRect = rect;
// We may end up propagating a scroll event. It is important that we suspend events until
// the end of the function since they could delete the layer or the layer's renderer().
FrameView* frameView = renderer()->document().view();
if (frameView)
frameView->pauseScheduledEvents();
bool restrictedByLineClamp = false;
if (renderer()->parent()) {
parentLayer = renderer()->parent()->enclosingLayer();
restrictedByLineClamp = !renderer()->parent()->style()->lineClamp().isNone();
}
if (renderer()->hasOverflowClip() && !restrictedByLineClamp) {
// Don't scroll to reveal an overflow layer that is restricted by the -webkit-line-clamp property.
// This will prevent us from revealing text hidden by the slider in Safari RSS.
RenderBox* box = renderBox();
ASSERT(box);
LayoutRect localExposeRect(box->absoluteToLocalQuad(FloatQuad(FloatRect(rect)), UseTransforms).boundingBox());
LayoutRect layerBounds(0, 0, box->clientWidth(), box->clientHeight());
LayoutRect r = getRectToExpose(layerBounds, localExposeRect, alignX, alignY);
IntSize clampedScrollOffset = m_scrollableArea->clampScrollOffset(adjustedScrollOffset() + toIntSize(roundedIntRect(r).location()));
if (clampedScrollOffset != adjustedScrollOffset()) {
IntSize oldScrollOffset = adjustedScrollOffset();
m_scrollableArea->scrollToOffset(clampedScrollOffset);
IntSize scrollOffsetDifference = adjustedScrollOffset() - oldScrollOffset;
localExposeRect.move(-scrollOffsetDifference);
newRect = LayoutRect(box->localToAbsoluteQuad(FloatQuad(FloatRect(localExposeRect)), UseTransforms).boundingBox());
}
} else if (!parentLayer && renderer()->isBox() && renderBox()->canBeProgramaticallyScrolled()) {
if (frameView) {
Element* ownerElement = renderer()->document().ownerElement();
if (ownerElement && ownerElement->renderer()) {
HTMLFrameElementBase* frameElementBase = 0;
if (ownerElement->hasTagName(frameTag) || ownerElement->hasTagName(iframeTag))
frameElementBase = toHTMLFrameElementBase(ownerElement);
if (frameElementAndViewPermitScroll(frameElementBase, frameView)) {
LayoutRect viewRect = frameView->visibleContentRect();
LayoutRect exposeRect = getRectToExpose(viewRect, rect, alignX, alignY);
int xOffset = roundToInt(exposeRect.x());
int yOffset = roundToInt(exposeRect.y());
// Adjust offsets if they're outside of the allowable range.
xOffset = max(0, min(frameView->contentsWidth(), xOffset));
yOffset = max(0, min(frameView->contentsHeight(), yOffset));
frameView->setScrollPosition(IntPoint(xOffset, yOffset));
if (frameView->safeToPropagateScrollToParent()) {
parentLayer = ownerElement->renderer()->enclosingLayer();
// FIXME: This doesn't correctly convert the rect to
// absolute coordinates in the parent.
newRect.setX(rect.x() - frameView->scrollX() + frameView->x());
newRect.setY(rect.y() - frameView->scrollY() + frameView->y());
} else
parentLayer = 0;
}
} else {
LayoutRect viewRect = frameView->visibleContentRect();
LayoutRect r = getRectToExpose(viewRect, rect, alignX, alignY);
frameView->setScrollPosition(roundedIntPoint(r.location()));
}
}
}
if (parentLayer)
parentLayer->scrollRectToVisible(newRect, alignX, alignY);
if (frameView)
frameView->resumeScheduledEvents();
}
void RenderLayer::updateCompositingLayersAfterScroll()
{
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 = ancestorStackingContainer()->enclosingCompositingLayer()) {
if (usesCompositedScrolling())
compositor()->updateCompositingLayers(CompositingUpdateOnCompositedScroll, compositingAncestor);
else
compositor()->updateCompositingLayers(CompositingUpdateOnScroll, compositingAncestor);
}
}
}
LayoutRect RenderLayer::getRectToExpose(const LayoutRect &visibleRect, const LayoutRect &exposeRect, const ScrollAlignment& alignX, const ScrollAlignment& alignY)
{
// Determine the appropriate X behavior.
ScrollBehavior scrollX;
LayoutRect exposeRectX(exposeRect.x(), visibleRect.y(), exposeRect.width(), visibleRect.height());
LayoutUnit intersectWidth = intersection(visibleRect, exposeRectX).width();
if (intersectWidth == exposeRect.width() || intersectWidth >= MIN_INTERSECT_FOR_REVEAL)
// If the rectangle is fully visible, use the specified visible behavior.
// If the rectangle is partially visible, but over a certain threshold,
// then treat it as fully visible to avoid unnecessary horizontal scrolling
scrollX = ScrollAlignment::getVisibleBehavior(alignX);
else if (intersectWidth == visibleRect.width()) {
// If the rect is bigger than the visible area, don't bother trying to center. Other alignments will work.
scrollX = ScrollAlignment::getVisibleBehavior(alignX);
if (scrollX == alignCenter)
scrollX = noScroll;
} else if (intersectWidth > 0)
// If the rectangle is partially visible, but not above the minimum threshold, use the specified partial behavior
scrollX = ScrollAlignment::getPartialBehavior(alignX);
else
scrollX = ScrollAlignment::getHiddenBehavior(alignX);
// If we're trying to align to the closest edge, and the exposeRect is further right
// than the visibleRect, and not bigger than the visible area, then align with the right.
if (scrollX == alignToClosestEdge && exposeRect.maxX() > visibleRect.maxX() && exposeRect.width() < visibleRect.width())
scrollX = alignRight;
// Given the X behavior, compute the X coordinate.
LayoutUnit x;
if (scrollX == noScroll)
x = visibleRect.x();
else if (scrollX == alignRight)
x = exposeRect.maxX() - visibleRect.width();
else if (scrollX == alignCenter)
x = exposeRect.x() + (exposeRect.width() - visibleRect.width()) / 2;
else
x = exposeRect.x();
// Determine the appropriate Y behavior.
ScrollBehavior scrollY;
LayoutRect exposeRectY(visibleRect.x(), exposeRect.y(), visibleRect.width(), exposeRect.height());
LayoutUnit intersectHeight = intersection(visibleRect, exposeRectY).height();
if (intersectHeight == exposeRect.height())
// If the rectangle is fully visible, use the specified visible behavior.
scrollY = ScrollAlignment::getVisibleBehavior(alignY);
else if (intersectHeight == visibleRect.height()) {
// If the rect is bigger than the visible area, don't bother trying to center. Other alignments will work.
scrollY = ScrollAlignment::getVisibleBehavior(alignY);
if (scrollY == alignCenter)
scrollY = noScroll;
} else if (intersectHeight > 0)
// If the rectangle is partially visible, use the specified partial behavior
scrollY = ScrollAlignment::getPartialBehavior(alignY);
else
scrollY = ScrollAlignment::getHiddenBehavior(alignY);
// If we're trying to align to the closest edge, and the exposeRect is further down
// than the visibleRect, and not bigger than the visible area, then align with the bottom.
if (scrollY == alignToClosestEdge && exposeRect.maxY() > visibleRect.maxY() && exposeRect.height() < visibleRect.height())
scrollY = alignBottom;
// Given the Y behavior, compute the Y coordinate.
LayoutUnit y;
if (scrollY == noScroll)
y = visibleRect.y();
else if (scrollY == alignBottom)
y = exposeRect.maxY() - visibleRect.height();
else if (scrollY == alignCenter)
y = exposeRect.y() + (exposeRect.height() - visibleRect.height()) / 2;
else
y = exposeRect.y();
return LayoutRect(LayoutPoint(x, y), visibleRect.size());
}
void RenderLayer::autoscroll(const IntPoint& position)
{
Frame* frame = renderer()->frame();
if (!frame)
return;
FrameView* frameView = frame->view();
if (!frameView)
return;
IntPoint currentDocumentPosition = frameView->windowToContents(position);
scrollRectToVisible(LayoutRect(currentDocumentPosition, LayoutSize(1, 1)), ScrollAlignment::alignToEdgeIfNeeded, ScrollAlignment::alignToEdgeIfNeeded);
}
bool RenderLayer::canResize() const
{
if (!renderer())
return false;
// We need a special case for <iframe> because they never have
// hasOverflowClip(). However, they do "implicitly" clip their contents, so
// we want to allow resizing them also.
return (renderer()->hasOverflowClip() || renderer()->isRenderIFrame()) && renderer()->style()->resize() != RESIZE_NONE;
}
void RenderLayer::resize(const PlatformEvent& evt, const LayoutSize& oldOffset)
{
// FIXME: This should be possible on generated content but is not right now.
if (!inResizeMode() || !canResize() || !renderer()->node())
return;
ASSERT(renderer()->node()->isElementNode());
Element* element = toElement(renderer()->node());
RenderBox* renderer = toRenderBox(element->renderer());
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 = renderer->style()->effectiveZoom();
LayoutSize newOffset = offsetFromResizeCorner(document.view()->windowToContents(pos));
newOffset.setWidth(newOffset.width() / zoomFactor);
newOffset.setHeight(newOffset.height() / zoomFactor);
LayoutSize currentSize = LayoutSize(renderer->width() / zoomFactor, renderer->height() / zoomFactor);
LayoutSize minimumSize = element->minimumSizeForResizing().shrunkTo(currentSize);
element->setMinimumSizeForResizing(minimumSize);
LayoutSize adjustedOldOffset = LayoutSize(oldOffset.width() / zoomFactor, oldOffset.height() / zoomFactor);
if (renderer->style()->shouldPlaceBlockDirectionScrollbarOnLogicalLeft()) {
newOffset.setWidth(-newOffset.width());
adjustedOldOffset.setWidth(-adjustedOldOffset.width());
}
LayoutSize difference = (currentSize + newOffset - adjustedOldOffset).expandedTo(minimumSize) - currentSize;
bool isBoxSizingBorder = renderer->style()->boxSizing() == BORDER_BOX;
EResize resize = renderer->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, renderer->marginLeft() / zoomFactor, CSSPrimitiveValue::CSS_PX);
element->setInlineStyleProperty(CSSPropertyMarginRight, renderer->marginRight() / zoomFactor, CSSPrimitiveValue::CSS_PX);
}
LayoutUnit baseWidth = renderer->width() - (isBoxSizingBorder ? LayoutUnit() : renderer->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, renderer->marginTop() / zoomFactor, CSSPrimitiveValue::CSS_PX);
element->setInlineStyleProperty(CSSPropertyMarginBottom, renderer->marginBottom() / zoomFactor, CSSPrimitiveValue::CSS_PX);
}
LayoutUnit baseHeight = renderer->height() - (isBoxSizingBorder ? LayoutUnit() : renderer->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.
}
int RenderLayer::scrollSize(ScrollbarOrientation orientation) const
{
IntSize scrollDimensions = scrollableArea()->maximumScrollPosition() - scrollableArea()->minimumScrollPosition();
return (orientation == HorizontalScrollbar) ? scrollDimensions.width() : scrollDimensions.height();
}
IntSize RenderLayer::overhangAmount() const
{
return IntSize();
}
bool RenderLayer::isActive() const
{
Page* page = renderer()->frame()->page();
return page && page->focusController().isActive();
}
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 RenderLayer::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 = renderer()->style()->resize() != RESIZE_NONE;
if ((hasHorizontalBar && hasVerticalBar) || (hasResizer && (hasHorizontalBar || hasVerticalBar)))
return cornerRect(renderer()->style(), horizontalScrollbar(), verticalScrollbar(), renderBox()->pixelSnappedBorderBoxRect());
return IntRect();
}
IntRect RenderLayer::resizerCornerRect(const IntRect& bounds, ResizerHitTestType resizerHitTestType) const
{
ASSERT(renderer()->isBox());
if (renderer()->style()->resize() == RESIZE_NONE)
return IntRect();
IntRect corner = cornerRect(renderer()->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 expand_ratio = ResizerControlExpandRatioForTouch - 1;
corner.move(-corner.width() * expand_ratio, -corner.height() * expand_ratio);
corner.expand(corner.width() * expand_ratio, corner.height() * expand_ratio);
}
return corner;
}
IntRect RenderLayer::scrollCornerAndResizerRect() const
{
RenderBox* box = renderBox();
if (!box)
return IntRect();
IntRect scrollCornerAndResizer = scrollCornerRect();
if (scrollCornerAndResizer.isEmpty())
scrollCornerAndResizer = resizerCornerRect(box->pixelSnappedBorderBoxRect(), ResizerForPointer);
return scrollCornerAndResizer;
}
bool RenderLayer::isScrollCornerVisible() const
{
ASSERT(renderer()->isBox());
return !scrollCornerRect().isEmpty();
}
IntRect RenderLayer::convertFromScrollbarToContainingView(const Scrollbar* scrollbar, const IntRect& scrollbarRect) const
{
RenderView* view = renderer()->view();
if (!view)
return scrollbarRect;
IntRect rect = scrollbarRect;
rect.move(scrollbarOffset(scrollbar));
return view->frameView()->convertFromRenderer(renderer(), rect);
}
IntRect RenderLayer::convertFromContainingViewToScrollbar(const Scrollbar* scrollbar, const IntRect& parentRect) const
{
RenderView* view = renderer()->view();
if (!view)
return parentRect;
IntRect rect = view->frameView()->convertToRenderer(renderer(), parentRect);
rect.move(-scrollbarOffset(scrollbar));
return rect;
}
IntPoint RenderLayer::convertFromScrollbarToContainingView(const Scrollbar* scrollbar, const IntPoint& scrollbarPoint) const
{
RenderView* view = renderer()->view();
if (!view)
return scrollbarPoint;
IntPoint point = scrollbarPoint;
point.move(scrollbarOffset(scrollbar));
return view->frameView()->convertFromRenderer(renderer(), point);
}
IntPoint RenderLayer::convertFromContainingViewToScrollbar(const Scrollbar* scrollbar, const IntPoint& parentPoint) const
{
RenderView* view = renderer()->view();
if (!view)
return parentPoint;
IntPoint point = view->frameView()->convertToRenderer(renderer(), parentPoint);
point.move(-scrollbarOffset(scrollbar));
return point;
}
int RenderLayer::visibleHeight() const
{
return m_layerSize.height();
}
int RenderLayer::visibleWidth() const
{
return m_layerSize.width();
}
bool RenderLayer::shouldSuspendScrollAnimations() const
{
RenderView* view = renderer()->view();
if (!view)
return true;
return view->frameView()->shouldSuspendScrollAnimations();
}
bool RenderLayer::scrollbarsCanBeActive() const
{
RenderView* view = renderer()->view();
if (!view)
return false;
return view->frameView()->scrollbarsCanBeActive();
}
IntPoint RenderLayer::lastKnownMousePosition() const
{
return renderer()->frame() ? renderer()->frame()->eventHandler()->lastKnownMousePosition() : IntPoint();
}
IntRect RenderLayer::rectForHorizontalScrollbar(const IntRect& borderBoxRect) const
{
if (!m_hBar)
return IntRect();
const RenderBox* box = renderBox();
const IntRect& scrollCorner = scrollCornerRect();
return IntRect(horizontalScrollbarStart(borderBoxRect.x()),
borderBoxRect.maxY() - box->borderBottom() - m_hBar->height(),
borderBoxRect.width() - (box->borderLeft() + box->borderRight()) - scrollCorner.width(),
m_hBar->height());
}
IntRect RenderLayer::rectForVerticalScrollbar(const IntRect& borderBoxRect) const
{
if (!m_vBar)
return IntRect();
const RenderBox* box = renderBox();
const IntRect& scrollCorner = scrollCornerRect();
return IntRect(verticalScrollbarStart(borderBoxRect.x(), borderBoxRect.maxX()),
borderBoxRect.y() + box->borderTop(),
m_vBar->width(),
borderBoxRect.height() - (box->borderTop() + box->borderBottom()) - scrollCorner.height());
}
LayoutUnit RenderLayer::verticalScrollbarStart(int minX, int maxX) const
{
const RenderBox* box = renderBox();
if (renderer()->style()->shouldPlaceBlockDirectionScrollbarOnLogicalLeft())
return minX + box->borderLeft();
return maxX - box->borderRight() - m_vBar->width();
}
LayoutUnit RenderLayer::horizontalScrollbarStart(int minX) const
{
const RenderBox* box = renderBox();
int x = minX + box->borderLeft();
if (renderer()->style()->shouldPlaceBlockDirectionScrollbarOnLogicalLeft())
x += m_vBar ? m_vBar->width() : resizerCornerRect(box->pixelSnappedBorderBoxRect(), ResizerForPointer).width();
return x;
}
IntSize RenderLayer::scrollbarOffset(const Scrollbar* scrollbar) const
{
RenderBox* box = renderBox();
if (scrollbar == m_vBar.get())
return IntSize(verticalScrollbarStart(0, box->width()), box->borderTop());
if (scrollbar == m_hBar.get())
return IntSize(horizontalScrollbarStart(0), box->height() - box->borderBottom() - scrollbar->height());
ASSERT_NOT_REACHED();
return IntSize();
}
void RenderLayer::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;
RenderBox* box = renderBox();
ASSERT(box);
// If we are not yet inserted into the tree, there is no need to repaint.
if (!box->parent())
return;
if (scrollbar == m_vBar.get())
scrollRect.move(verticalScrollbarStart(0, box->width()), box->borderTop());
else
scrollRect.move(horizontalScrollbarStart(0), box->height() - box->borderBottom() - scrollbar->height());
renderer()->repaintRectangle(scrollRect);
}
void RenderLayer::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);
}
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> RenderLayer::createScrollbar(ScrollbarOrientation orientation)
{
RefPtr<Scrollbar> widget;
RenderObject* actualRenderer = rendererForScrollbar(renderer());
bool hasCustomScrollbarStyle = actualRenderer->isBox() && actualRenderer->style()->hasPseudoStyle(SCROLLBAR);
if (hasCustomScrollbarStyle)
widget = RenderScrollbar::createCustomScrollbar(scrollableArea(), orientation, actualRenderer->node());
else {
widget = Scrollbar::createNativeScrollbar(scrollableArea(), orientation, RegularScrollbar);
if (orientation == HorizontalScrollbar)
scrollableArea()->didAddHorizontalScrollbar(widget.get());
else
scrollableArea()->didAddVerticalScrollbar(widget.get());
}
renderer()->document().view()->addChild(widget.get());
return widget.release();
}
void RenderLayer::destroyScrollbar(ScrollbarOrientation orientation)
{
RefPtr<Scrollbar>& scrollbar = orientation == HorizontalScrollbar ? m_hBar : m_vBar;
if (!scrollbar)
return;
if (!scrollbar->isCustomScrollbar()) {
if (orientation == HorizontalScrollbar)
scrollableArea()->willRemoveHorizontalScrollbar(scrollbar.get());
else
scrollableArea()->willRemoveVerticalScrollbar(scrollbar.get());
}
scrollbar->removeFromParent();
scrollbar->disconnectFromScrollableArea();
scrollbar = 0;
}
void RenderLayer::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 (renderer()->document().hasAnnotatedRegions())
renderer()->document().setAnnotatedRegionsDirty(true);
}
void RenderLayer::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 (renderer()->document().hasAnnotatedRegions())
renderer()->document().setAnnotatedRegionsDirty(true);
}
ScrollableArea* RenderLayer::enclosingScrollableArea() const
{
if (RenderLayer* scrollableLayer = enclosingScrollableLayer())
return scrollableLayer->scrollableArea();
// FIXME: We should return the frame view here (or possibly an ancestor frame view,
// if the frame view isn't scrollable.
return 0;
}
int RenderLayer::verticalScrollbarWidth(OverlayScrollbarSizeRelevancy relevancy) const
{
if (!m_vBar || (m_vBar->isOverlayScrollbar() && (relevancy == IgnoreOverlayScrollbarSize || !m_vBar->shouldParticipateInHitTesting())))
return 0;
return m_vBar->width();
}
int RenderLayer::horizontalScrollbarHeight(OverlayScrollbarSizeRelevancy relevancy) const
{
if (!m_hBar || (m_hBar->isOverlayScrollbar() && (relevancy == IgnoreOverlayScrollbarSize || !m_hBar->shouldParticipateInHitTesting())))
return 0