/*
* Copyright (C) 2006, 2007, 2008, 2009, 2010 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 "RenderLayer.h"
#include "ColumnInfo.h"
#include "CSSPropertyNames.h"
#include "CSSStyleDeclaration.h"
#include "CSSStyleSelector.h"
#include "Chrome.h"
#include "Document.h"
#include "EventHandler.h"
#include "EventQueue.h"
#include "FloatPoint3D.h"
#include "FloatRect.h"
#include "FocusController.h"
#include "Frame.h"
#include "FrameTree.h"
#include "FrameView.h"
#include "Gradient.h"
#include "GraphicsContext.h"
#include "HTMLFrameOwnerElement.h"
#include "HTMLNames.h"
#if ENABLE(ANDROID_OVERFLOW_SCROLL)
#include "HTMLTextAreaElement.h"
#include "GraphicsLayerAndroid.h"
#endif
#include "HitTestRequest.h"
#include "HitTestResult.h"
#include "OverflowEvent.h"
#include "OverlapTestRequestClient.h"
#include "Page.h"
#include "PlatformMouseEvent.h"
#include "RenderArena.h"
#include "RenderInline.h"
#include "RenderMarquee.h"
#include "RenderReplica.h"
#include "RenderScrollbar.h"
#include "RenderScrollbarPart.h"
#include "RenderTheme.h"
#include "RenderTreeAsText.h"
#include "RenderView.h"
#include "ScaleTransformOperation.h"
#include "Scrollbar.h"
#include "ScrollbarTheme.h"
#include "SelectionController.h"
#include "TextStream.h"
#include "TransformState.h"
#include "TransformationMatrix.h"
#include "TranslateTransformOperation.h"
#include <wtf/StdLibExtras.h>
#include <wtf/UnusedParam.h>
#include <wtf/text/CString.h>
#if USE(ACCELERATED_COMPOSITING)
#include "RenderLayerBacking.h"
#include "RenderLayerCompositor.h"
#endif
#if ENABLE(SVG)
#include "SVGNames.h"
#endif
#define MIN_INTERSECT_FOR_REVEAL 32
using namespace std;
namespace WebCore {
using namespace HTMLNames;
const int MinimumWidthWhileResizing = 100;
const int MinimumHeightWhileResizing = 40;
void* ClipRects::operator new(size_t sz, RenderArena* renderArena) throw()
{
return renderArena->allocate(sz);
}
void ClipRects::operator delete(void* ptr, size_t sz)
{
// Stash size where destroy can find it.
*(size_t *)ptr = sz;
}
void ClipRects::destroy(RenderArena* renderArena)
{
delete this;
// Recover the size left there for us by operator delete and free the memory.
renderArena->free(*(size_t *)this, this);
}
RenderLayer::RenderLayer(RenderBoxModelObject* renderer)
: m_renderer(renderer)
, m_parent(0)
, m_previous(0)
, m_next(0)
, m_first(0)
, m_last(0)
, m_relX(0)
, m_relY(0)
, m_x(0)
, m_y(0)
, m_width(0)
, m_height(0)
, m_scrollX(0)
, m_scrollY(0)
, m_scrollLeftOverflow(0)
, m_scrollTopOverflow(0)
, m_scrollWidth(0)
, m_scrollHeight(0)
, m_inResizeMode(false)
, m_posZOrderList(0)
, m_negZOrderList(0)
, m_normalFlowList(0)
, m_clipRects(0)
#ifndef NDEBUG
, m_clipRectsRoot(0)
#endif
, m_scrollDimensionsDirty(true)
, m_zOrderListsDirty(true)
, m_normalFlowListDirty(true)
, m_isNormalFlowOnly(shouldBeNormalFlowOnly())
, m_usedTransparency(false)
, m_paintingInsideReflection(false)
, m_inOverflowRelayout(false)
, m_needsFullRepaint(false)
, m_overflowStatusDirty(true)
, m_visibleContentStatusDirty(true)
, m_hasVisibleContent(false)
, m_visibleDescendantStatusDirty(false)
, m_hasVisibleDescendant(false)
, m_isPaginated(false)
, m_3DTransformedDescendantStatusDirty(true)
, m_has3DTransformedDescendant(false)
#if USE(ACCELERATED_COMPOSITING)
, m_hasCompositingDescendant(false)
, m_mustOverlapCompositedLayers(false)
#if ENABLE(COMPOSITED_FIXED_ELEMENTS)
, m_shouldComposite(false)
#endif
#endif
, m_containsDirtyOverlayScrollbars(false)
#if ENABLE(ANDROID_OVERFLOW_SCROLL)
, m_hasOverflowScroll(false)
#endif
, m_marquee(0)
, m_staticInlinePosition(0)
, m_staticBlockPosition(0)
, m_reflection(0)
, m_scrollCorner(0)
, m_resizer(0)
{
ScrollableArea::setConstrainsScrollingToContentEdge(false);
if (!renderer->firstChild() && renderer->style()) {
m_visibleContentStatusDirty = false;
m_hasVisibleContent = renderer->style()->visibility() == VISIBLE;
}
if (Frame* frame = renderer->frame()) {
if (Page* page = frame->page()) {
m_page = page;
m_page->addScrollableArea(this);
}
}
}
RenderLayer::~RenderLayer()
{
if (inResizeMode() && !renderer()->documentBeingDestroyed()) {
if (Frame* frame = renderer()->frame())
frame->eventHandler()->resizeLayerDestroyed();
}
if (m_page)
m_page->removeScrollableArea(this);
destroyScrollbar(HorizontalScrollbar);
destroyScrollbar(VerticalScrollbar);
if (m_reflection)
removeReflection();
// Child layers will be deleted by their corresponding render objects, so
// we don't need to delete them ourselves.
delete m_posZOrderList;
delete m_negZOrderList;
delete m_normalFlowList;
delete m_marquee;
#if USE(ACCELERATED_COMPOSITING)
clearBacking();
#endif
// Make sure we have no lingering clip rects.
ASSERT(!m_clipRects);
if (m_scrollCorner)
m_scrollCorner->destroy();
if (m_resizer)
m_resizer->destroy();
}
#if USE(ACCELERATED_COMPOSITING)
RenderLayerCompositor* RenderLayer::compositor() const
{
ASSERT(renderer()->view());
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);
}
#endif // USE(ACCELERATED_COMPOSITING)
bool RenderLayer::hasAcceleratedCompositing() const
{
#if USE(ACCELERATED_COMPOSITING)
return compositor()->hasAcceleratedCompositing();
#else
return false;
#endif
}
bool RenderLayer::canRender3DTransforms() const
{
#if USE(ACCELERATED_COMPOSITING)
return compositor()->canRender3DTransforms();
#else
return false;
#endif
}
void RenderLayer::updateLayerPositions(UpdateLayerPositionsFlags flags, IntPoint* cachedOffset)
{
updateLayerPosition(); // For relpositioned layers or non-positioned layers,
// we need to keep in sync, since we may have shifted relative
// to our parent layer.
IntPoint oldCachedOffset;
if (cachedOffset) {
// We can't cache our offset to the repaint container if the mapping is anything more complex than a simple translation
bool disableOffsetCache = renderer()->hasColumns() || renderer()->hasTransform() || isComposited();
#if ENABLE(SVG)
disableOffsetCache = disableOffsetCache || renderer()->isSVGRoot();
#endif
if (disableOffsetCache)
cachedOffset = 0; // If our cached offset is invalid make sure it's not passed to any of our children
else {
oldCachedOffset = *cachedOffset;
// Frequently our parent layer's renderer will be the same as our renderer's containing block. In that case,
// we just update the cache using our offset to our parent (which is m_x / m_y). Otherwise, regenerated cached
// offsets to the root from the render tree.
if (!m_parent || m_parent->renderer() == renderer()->containingBlock())
cachedOffset->move(m_x, m_y); // Fast case
else {
int x = 0;
int y = 0;
convertToLayerCoords(root(), x, y);
*cachedOffset = IntPoint(x, y);
}
}
}
int x = 0;
int y = 0;
if (cachedOffset) {
x += cachedOffset->x();
y += cachedOffset->y();
#ifndef NDEBUG
int nonCachedX = 0;
int nonCachedY = 0;
convertToLayerCoords(root(), nonCachedX, nonCachedY);
ASSERT(x == nonCachedX);
ASSERT(y == nonCachedY);
#endif
} else
convertToLayerCoords(root(), x, y);
positionOverflowControls(x, y);
updateVisibilityStatus();
if (flags & UpdatePagination)
updatePagination();
else
m_isPaginated = false;
if (m_hasVisibleContent) {
RenderView* view = renderer()->view();
ASSERT(view);
// FIXME: Optimize using LayoutState and remove the disableLayoutState() call
// from updateScrollInfoAfterLayout().
ASSERT(!view->layoutStateEnabled());
RenderBoxModelObject* repaintContainer = renderer()->containerForRepaint();
IntRect newRect = renderer()->clippedOverflowRectForRepaint(repaintContainer);
IntRect newOutlineBox = renderer()->outlineBoundsForRepaint(repaintContainer, cachedOffset);
// FIXME: Should ASSERT that value calculated for newOutlineBox 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_needsFullRepaint) {
renderer()->repaintUsingContainer(repaintContainer, m_repaintRect);
if (newRect != m_repaintRect)
renderer()->repaintUsingContainer(repaintContainer, newRect);
} else
renderer()->repaintAfterLayoutIfNeeded(repaintContainer, m_repaintRect, m_outlineBox, &newRect, &newOutlineBox);
}
}
m_repaintRect = newRect;
m_outlineBox = newOutlineBox;
} else {
m_repaintRect = IntRect();
m_outlineBox = IntRect();
}
m_needsFullRepaint = false;
// Go ahead and update the reflection's position and size.
if (m_reflection)
m_reflection->layout();
#if USE(ACCELERATED_COMPOSITING)
// Clear the IsCompositingUpdateRoot flag once we've found the first compositing layer in this update.
bool isUpdateRoot = (flags & IsCompositingUpdateRoot);
if (isComposited())
flags &= ~IsCompositingUpdateRoot;
#endif
if (renderer()->hasColumns())
flags |= UpdatePagination;
for (RenderLayer* child = firstChild(); child; child = child->nextSibling())
child->updateLayerPositions(flags, cachedOffset);
#if USE(ACCELERATED_COMPOSITING)
if ((flags & UpdateCompositingLayers) && isComposited())
backing()->updateAfterLayout(RenderLayerBacking::CompositingChildren, isUpdateRoot);
#endif
// With all our children positioned, now update our marquee if we need to.
if (m_marquee)
m_marquee->updateMarqueePosition();
if (cachedOffset)
*cachedOffset = oldCachedOffset;
}
IntRect RenderLayer::repaintRectIncludingDescendants() const
{
IntRect repaintRect = m_repaintRect;
for (RenderLayer* child = firstChild(); child; child = child->nextSibling())
repaintRect.unite(child->repaintRectIncludingDescendants());
return repaintRect;
}
void RenderLayer::computeRepaintRects()
{
RenderBoxModelObject* repaintContainer = renderer()->containerForRepaint();
m_repaintRect = renderer()->clippedOverflowRectForRepaint(repaintContainer);
m_outlineBox = renderer()->outlineBoundsForRepaint(repaintContainer);
}
void RenderLayer::updateRepaintRectsAfterScroll(bool fixed)
{
if (fixed || renderer()->style()->position() == FixedPosition) {
computeRepaintRects();
fixed = true;
} else if (renderer()->hasTransform() && !renderer()->isRenderView()) {
// Transforms act as fixed position containers, so nothing inside a
// transformed element can be fixed relative to the viewport if the
// transformed element is not fixed itself or child of a fixed element.
return;
}
for (RenderLayer* child = firstChild(); child; child = child->nextSibling())
child->updateRepaintRectsAfterScroll(fixed);
}
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.set(new TransformationMatrix);
else
m_transform.clear();
}
if (hasTransform) {
RenderBox* box = renderBox();
ASSERT(box);
m_transform->makeIdentity();
box->style()->applyTransform(*m_transform, box->borderBoxRect().size(), RenderStyle::IncludeTransformOrigin);
makeMatrixRenderable(*m_transform, canRender3DTransforms());
}
if (had3DTransform != has3DTransform())
dirty3DTransformedDescendantStatus();
}
TransformationMatrix RenderLayer::currentTransform() const
{
if (!m_transform)
return TransformationMatrix();
#if USE(ACCELERATED_COMPOSITING)
if (renderer()->style()->isRunningAcceleratedAnimation()) {
TransformationMatrix currTransform;
RefPtr<RenderStyle> style = renderer()->animation()->getAnimatedStyleForRenderer(renderer());
style->applyTransform(currTransform, renderBox()->borderBoxRect().size(), RenderStyle::IncludeTransformOrigin);
makeMatrixRenderable(currTransform, canRender3DTransforms());
return currTransform;
}
#endif
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(RenderBoxModelObject* renderer, RenderBox* ancestorColumnsRenderer)
{
RenderView* view = renderer->view();
RenderBoxModelObject* 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->isPositioned())
return false;
// Otherwise we are paginated by the columns block.
return true;
}
void RenderLayer::updatePagination()
{
m_isPaginated = false;
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.
if (isNormalFlowOnly()) {
m_isPaginated = parent()->renderer()->hasColumns();
return;
}
// If we're not normal flow, then we need to look for a multi-column object between us and our stacking context.
RenderLayer* ancestorStackingContext = stackingContext();
for (RenderLayer* curr = parent(); curr; curr = curr->parent()) {
if (curr->renderer()->hasColumns()) {
m_isPaginated = checkContainingBlockChainForPagination(renderer(), curr->renderBox());
return;
}
if (curr == ancestorStackingContext)
return;
}
}
void RenderLayer::setHasVisibleContent(bool b)
{
if (m_hasVisibleContent == b && !m_visibleContentStatusDirty)
return;
m_visibleContentStatusDirty = false;
m_hasVisibleContent = b;
if (m_hasVisibleContent) {
RenderBoxModelObject* repaintContainer = renderer()->containerForRepaint();
m_repaintRect = renderer()->clippedOverflowRectForRepaint(repaintContainer);
m_outlineBox = renderer()->outlineBoundsForRepaint(repaintContainer);
if (!isNormalFlowOnly()) {
for (RenderLayer* sc = stackingContext(); sc; sc = sc->stackingContext()) {
sc->dirtyZOrderLists();
if (sc->hasVisibleContent())
break;
}
}
}
if (parent())
parent()->childVisibilityChanged(m_hasVisibleContent);
}
void RenderLayer::dirtyVisibleContentStatus()
{
m_visibleContentStatusDirty = true;
if (parent())
parent()->dirtyVisibleDescendantStatus();
}
void RenderLayer::childVisibilityChanged(bool newVisibility)
{
if (m_hasVisibleDescendant == newVisibility || m_visibleDescendantStatusDirty)
return;
if (newVisibility) {
RenderLayer* l = this;
while (l && !l->m_visibleDescendantStatusDirty && !l->m_hasVisibleDescendant) {
l->m_hasVisibleDescendant = true;
l = l->parent();
}
} else
dirtyVisibleDescendantStatus();
}
void RenderLayer::dirtyVisibleDescendantStatus()
{
RenderLayer* l = this;
while (l && !l->m_visibleDescendantStatusDirty) {
l->m_visibleDescendantStatusDirty = true;
l = l->parent();
}
}
void RenderLayer::updateVisibilityStatus()
{
if (m_visibleDescendantStatusDirty) {
m_hasVisibleDescendant = false;
for (RenderLayer* child = firstChild(); child; child = child->nextSibling()) {
child->updateVisibilityStatus();
if (child->m_hasVisibleContent || child->m_hasVisibleDescendant) {
m_hasVisibleDescendant = true;
break;
}
}
m_visibleDescendantStatusDirty = 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 = stackingContext();
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 contexts.
while (curr && curr->preserves3D()) {
curr->m_3DTransformedDescendantStatusDirty = true;
curr = curr->stackingContext();
}
}
// Return true if this layer or any preserve-3d descendants have 3d.
bool RenderLayer::update3DTransformedDescendantStatus()
{
if (m_3DTransformedDescendantStatusDirty) {
m_has3DTransformedDescendant = false;
// 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 (m_posZOrderList) {
for (unsigned i = 0; i < m_posZOrderList->size(); ++i)
m_has3DTransformedDescendant |= m_posZOrderList->at(i)->update3DTransformedDescendantStatus();
}
// Now check our negative z-index children.
if (m_negZOrderList) {
for (unsigned i = 0; i < m_negZOrderList->size(); ++i)
m_has3DTransformedDescendant |= m_negZOrderList->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();
}
void RenderLayer::updateLayerPosition()
{
IntPoint localPoint;
IntSize inlineBoundingBoxOffset; // We don't put this into the RenderLayer x/y for inlines, so we need to subtract it out when done.
if (renderer()->isRenderInline()) {
RenderInline* inlineFlow = toRenderInline(renderer());
IntRect lineBox = inlineFlow->linesBoundingBox();
setWidth(lineBox.width());
setHeight(lineBox.height());
inlineBoundingBoxOffset = IntSize(lineBox.x(), lineBox.y());
localPoint += inlineBoundingBoxOffset;
} else if (RenderBox* box = renderBox()) {
setWidth(box->width());
setHeight(box->height());
localPoint += box->locationOffsetIncludingFlipping();
}
// Clear our cached clip rect information.
clearClipRects();
if (!renderer()->isPositioned() && 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)->locationOffsetIncludingFlipping();
}
curr = curr->parent();
}
if (curr->isBox() && curr->isTableRow()) {
// Put ourselves into the row coordinate space.
localPoint -= toRenderBox(curr)->locationOffsetIncludingFlipping();
}
}
// Subtract our parent's scroll offset.
if (renderer()->isPositioned() && enclosingPositionedAncestor()) {
RenderLayer* positionedParent = enclosingPositionedAncestor();
// For positioned layers, we subtract out the enclosing positioned layer's scroll offset.
IntSize offset = positionedParent->scrolledContentOffset();
localPoint -= offset;
if (renderer()->isPositioned() && positionedParent->renderer()->isRelPositioned() && positionedParent->renderer()->isRenderInline()) {
IntSize offset = toRenderInline(positionedParent->renderer())->relativePositionedInlineOffset(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.
IntSize columnOffset;
parent()->renderer()->adjustForColumns(columnOffset, localPoint);
localPoint += columnOffset;
}
IntSize scrollOffset = parent()->scrolledContentOffset();
localPoint -= scrollOffset;
}
m_relX = m_relY = 0;
if (renderer()->isRelPositioned()) {
m_relX = renderer()->relativePositionOffsetX();
m_relY = renderer()->relativePositionOffsetY();
localPoint.move(m_relX, m_relY);
}
// FIXME: We'd really like to just get rid of the concept of a layer rectangle and rely on the renderers.
localPoint -= inlineBoundingBoxOffset;
setLocation(localPoint.x(), localPoint.y());
}
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())->borderBoxRect();
const float boxWidth = borderBox.width();
const float boxHeight = borderBox.height();
float perspectiveOriginX = style->perspectiveOriginX().calcFloatValue(boxWidth);
float perspectiveOriginY = style->perspectiveOriginY().calcFloatValue(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 IntRect borderBox = toRenderBox(renderer())->borderBoxRect();
RenderStyle* style = renderer()->style();
return FloatPoint(style->perspectiveOriginX().calcFloatValue(borderBox.width()),
style->perspectiveOriginY().calcFloatValue(borderBox.height()));
}
RenderLayer* RenderLayer::stackingContext() const
{
RenderLayer* layer = parent();
#if ENABLE(COMPOSITED_FIXED_ELEMENTS) || ENABLE(ANDROID_OVERFLOW_SCROLL)
// When using composited fixed elements, they are turned into a stacking
// context and we thus need to return them.
// We can simplify the while loop by using isStackingContext(); with
// composited fixed elements turned on, this will return true for them,
// and is otherwise equivalent to the replaced statements.
while (layer && !layer->renderer()->isRoot() && !layer->isStackingContext())
#else
while (layer && !layer->renderer()->isRenderView() && !layer->renderer()->isRoot() && layer->renderer()->style()->hasAutoZIndex())
#endif
layer = layer->parent();
return layer;
}
static inline bool isPositionedContainer(RenderLayer* layer)
{
RenderObject* o = layer->renderer();
return o->isRenderView() || o->isPositioned() || o->isRelPositioned() || layer->hasTransform();
}
static inline bool isFixedPositionedContainer(RenderLayer* layer)
{
RenderObject* o = layer->renderer();
return o->isRenderView() || layer->hasTransform();
}
RenderLayer* RenderLayer::enclosingPositionedAncestor() const
{
RenderLayer* curr = parent();
while (curr && !isPositionedContainer(curr))
curr = curr->parent();
return curr;
}
RenderLayer* RenderLayer::enclosingTransformedAncestor() const
{
RenderLayer* curr = parent();
while (curr && !curr->renderer()->isRenderView() && !curr->transform())
curr = curr->parent();
return curr;
}
static inline const RenderLayer* compositingContainer(const RenderLayer* layer)
{
return layer->isNormalFlowOnly() ? layer->parent() : layer->stackingContext();
}
#if USE(ACCELERATED_COMPOSITING)
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;
}
#endif
RenderLayer* RenderLayer::clippingRoot() const
{
#if USE(ACCELERATED_COMPOSITING)
if (isComposited())
return const_cast<RenderLayer*>(this);
#endif
const RenderLayer* current = this;
while (current) {
if (current->renderer()->isRenderView())
return const_cast<RenderLayer*>(current);
current = compositingContainer(current);
ASSERT(current);
if (current->transform()
#if USE(ACCELERATED_COMPOSITING)
|| current->isComposited()
#endif
)
return const_cast<RenderLayer*>(current);
}
ASSERT_NOT_REACHED();
return 0;
}
IntPoint RenderLayer::absoluteToContents(const IntPoint& absolutePoint) const
{
// We don't use convertToLayerCoords because it doesn't know about transforms
return roundedIntPoint(renderer()->absoluteToLocal(absolutePoint, false, true));
}
bool RenderLayer::requiresSlowRepaints() const
{
if (isTransparent() || hasReflection() || hasTransform())
return true;
if (!parent())
return false;
return parent()->requiresSlowRepaints();
}
bool RenderLayer::isTransparent() const
{
#if ENABLE(SVG)
if (renderer()->node() && renderer()->node()->namespaceURI() == SVGNames::svgNamespaceURI)
return false;
#endif
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;
}
static IntRect transparencyClipBox(const RenderLayer* l, const RenderLayer* rootLayer, PaintBehavior paintBehavior);
static void expandClipRectForDescendantsAndReflection(IntRect& clipRect, const RenderLayer* l, const RenderLayer* rootLayer, 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 (!l->renderer()->hasMask()) {
// Note: we don't have to walk z-order lists since transparent elements always establish
// a stacking context. This means we can just walk the layer tree directly.
for (RenderLayer* curr = l->firstChild(); curr; curr = curr->nextSibling()) {
if (!l->reflection() || l->reflectionLayer() != curr)
clipRect.unite(transparencyClipBox(curr, rootLayer, 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 (l->renderer()->hasReflection()) {
int deltaX = 0;
int deltaY = 0;
l->convertToLayerCoords(rootLayer, deltaX, deltaY);
clipRect.move(-deltaX, -deltaY);
clipRect.unite(l->renderBox()->reflectedRect(clipRect));
clipRect.move(deltaX, deltaY);
}
}
static IntRect transparencyClipBox(const RenderLayer* l, const RenderLayer* rootLayer, 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 != l && l->paintsWithTransform(paintBehavior)) {
// 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.
int x = 0;
int y = 0;
l->convertToLayerCoords(rootLayer, x, y);
TransformationMatrix transform;
transform.translate(x, y);
transform = transform * *l->transform();
IntRect clipRect = l->boundingBox(l);
expandClipRectForDescendantsAndReflection(clipRect, l, l, paintBehavior);
return transform.mapRect(clipRect);
}
IntRect clipRect = l->boundingBox(rootLayer);
expandClipRectForDescendantsAndReflection(clipRect, l, rootLayer, paintBehavior);
return clipRect;
}
void RenderLayer::beginTransparencyLayers(GraphicsContext* p, const RenderLayer* rootLayer, PaintBehavior paintBehavior)
{
if (p->paintingDisabled() || (paintsWithTransparency(paintBehavior) && m_usedTransparency))
return;
RenderLayer* ancestor = transparentPaintingAncestor();
if (ancestor)
ancestor->beginTransparencyLayers(p, rootLayer, paintBehavior);
if (paintsWithTransparency(paintBehavior)) {
m_usedTransparency = true;
p->save();
IntRect clipRect = transparencyClipBox(this, rootLayer, paintBehavior);
p->clip(clipRect);
p->beginTransparencyLayer(renderer()->opacity());
#ifdef REVEAL_TRANSPARENCY_LAYERS
p->setFillColor(Color(0.0f, 0.0f, 0.5f, 0.2f), ColorSpaceDeviceRGB);
p->fillRect(clipRect);
#endif
}
}
void* RenderLayer::operator new(size_t sz, RenderArena* renderArena) throw()
{
return renderArena->allocate(sz);
}
void RenderLayer::operator delete(void* ptr, size_t sz)
{
// Stash size where destroy can find it.
*(size_t *)ptr = sz;
}
void RenderLayer::destroy(RenderArena* renderArena)
{
delete this;
// Recover the size left there for us by operator delete and free the memory.
renderArena->free(*(size_t *)this, this);
}
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 stackingContext() 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->dirtyStackingContextZOrderLists();
}
child->updateVisibilityStatus();
if (child->m_hasVisibleContent || child->m_hasVisibleDescendant)
childVisibilityChanged(true);
#if USE(ACCELERATED_COMPOSITING)
compositor()->layerWasAdded(this, child);
#endif
}
RenderLayer* RenderLayer::removeChild(RenderLayer* oldChild)
{
#if USE(ACCELERATED_COMPOSITING)
if (!renderer()->documentBeingDestroyed())
compositor()->layerWillBeRemoved(this, oldChild);
#endif
// 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 |stackingContext| value.
oldChild->dirtyStackingContextZOrderLists();
}
oldChild->setPreviousSibling(0);
oldChild->setNextSibling(0);
oldChild->setParent(0);
oldChild->updateVisibilityStatus();
if (oldChild->m_hasVisibleContent || oldChild->m_hasVisibleDescendant)
childVisibilityChanged(false);
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);
#if USE(ACCELERATED_COMPOSITING)
compositor()->layerWillBeRemoved(m_parent, this);
#endif
// Dirty the clip rects.
clearClipRectsIncludingDescendants();
// Remove us from the parent.
RenderLayer* parent = m_parent;
RenderLayer* nextSib = nextSibling();
parent->removeChild(this);
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);
parent->addChild(current, nextSib);
current->setNeedsFullRepaint();
current->updateLayerPositions(); // Depends on hasLayer() already being false for proper layout.
current = next;
}
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::convertToLayerCoords(const RenderLayer* ancestorLayer, int& xPos, int& yPos) const
{
if (ancestorLayer == this)
return;
EPosition position = renderer()->style()->position();
if (position == FixedPosition && (!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(), true);
xPos += absPos.x();
yPos += absPos.y();
return;
}
if (position == FixedPosition) {
// 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 = parent(); currLayer; currLayer = currLayer->parent()) {
if (currLayer == ancestorLayer)
foundAncestor = true;
if (isFixedPositionedContainer(currLayer)) {
fixedPositionContainerLayer = currLayer;
ASSERT(foundAncestor);
break;
}
}
ASSERT(fixedPositionContainerLayer); // We should have hit the RenderView's layer at least.
if (fixedPositionContainerLayer != ancestorLayer) {
int fixedContainerX = 0;
int fixedContainerY = 0;
convertToLayerCoords(fixedPositionContainerLayer, fixedContainerX, fixedContainerY);
int ancestorX = 0;
int ancestorY = 0;
ancestorLayer->convertToLayerCoords(fixedPositionContainerLayer, ancestorX, ancestorY);
xPos += (fixedContainerX - ancestorX);
yPos += (fixedContainerY - ancestorY);
return;
}
}
RenderLayer* parentLayer;
if (position == AbsolutePosition || position == FixedPosition) {
// Do what enclosingPositionedAncestor() does, but check for ancestorLayer along the way.
parentLayer = parent();
bool foundAncestorFirst = false;
while (parentLayer) {
if (isPositionedContainer(parentLayer))
break;
if (parentLayer == ancestorLayer) {
foundAncestorFirst = true;
break;
}
parentLayer = parentLayer->parent();
}
if (foundAncestorFirst) {
// Found ancestorLayer before the abs. positioned container, so compute offset of both relative
// to enclosingPositionedAncestor and subtract.
RenderLayer* positionedAncestor = parentLayer->enclosingPositionedAncestor();
int thisX = 0;
int thisY = 0;
convertToLayerCoords(positionedAncestor, thisX, thisY);
int ancestorX = 0;
int ancestorY = 0;
ancestorLayer->convertToLayerCoords(positionedAncestor, ancestorX, ancestorY);
xPos += (thisX - ancestorX);
yPos += (thisY - ancestorY);
return;
}
} else
parentLayer = parent();
if (!parentLayer)
return;
parentLayer->convertToLayerCoords(ancestorLayer, xPos, yPos);
xPos += x();
yPos += y();
}
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;
}
void RenderLayer::panScrollFromPoint(const IntPoint& sourcePoint)
{
Frame* frame = renderer()->frame();
if (!frame)
return;
IntPoint currentMousePosition = frame->eventHandler()->currentMousePosition();
// We need to check if the current mouse position is out of the window. When the mouse is out of the window, the position is incoherent
static IntPoint previousMousePosition;
if (currentMousePosition.x() < 0 || currentMousePosition.y() < 0)
currentMousePosition = previousMousePosition;
else
previousMousePosition = currentMousePosition;
int xDelta = currentMousePosition.x() - sourcePoint.x();
int yDelta = currentMousePosition.y() - sourcePoint.y();
if (abs(xDelta) <= ScrollView::noPanScrollRadius) // at the center we let the space for the icon
xDelta = 0;
if (abs(yDelta) <= ScrollView::noPanScrollRadius)
yDelta = 0;
scrollByRecursively(adjustedScrollDelta(xDelta), adjustedScrollDelta(yDelta));
}
void RenderLayer::scrollByRecursively(int xDelta, int yDelta)
{
if (!xDelta && !yDelta)
return;
bool restrictedByLineClamp = false;
if (renderer()->parent())
restrictedByLineClamp = !renderer()->parent()->style()->lineClamp().isNone();
if (renderer()->hasOverflowClip() && !restrictedByLineClamp) {
int newOffsetX = scrollXOffset() + xDelta;
int newOffsetY = scrollYOffset() + yDelta;
scrollToOffset(newOffsetX, newOffsetY);
// If this layer can't do the scroll we ask the next layer up that can scroll to try
int leftToScrollX = newOffsetX - scrollXOffset();
int leftToScrollY = newOffsetY - scrollYOffset();
if ((leftToScrollX || leftToScrollY) && renderer()->parent()) {
RenderObject* nextRenderer = renderer()->parent();
while (nextRenderer) {
if (nextRenderer->isBox() && toRenderBox(nextRenderer)->canBeScrolledAndHasScrollableArea()) {
nextRenderer->enclosingLayer()->scrollByRecursively(leftToScrollX, leftToScrollY);
break;
}
nextRenderer = nextRenderer->parent();
}
Frame* frame = renderer()->frame();
if (frame)
frame->eventHandler()->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(IntSize(xDelta, yDelta));
// 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(int x, int y)
{
ScrollableArea::scrollToOffsetWithoutAnimation(IntPoint(x, y));
}
void RenderLayer::scrollTo(int x, int y)
{
RenderBox* box = renderBox();
if (!box)
return;
if (box->style()->overflowX() != OMARQUEE) {
if (x < 0)
x = 0;
if (y < 0)
y = 0;
// Call the scrollWidth/Height functions so that the dimensions will be computed if they need
// to be (for overflow:hidden blocks).
int maxX = scrollWidth() - box->clientWidth();
if (maxX < 0)
maxX = 0;
int maxY = scrollHeight() - box->clientHeight();
if (maxY < 0)
maxY = 0;
if (x > maxX)
x = maxX;
if (y > maxY)
y = maxY;
}
// FIXME: Eventually, we will want to perform a blit. For now never
// blit, since the check for blitting is going to be very
// complicated (since it will involve testing whether our layer
// is either occluded by another layer or clipped by an enclosing
// layer or contains fixed backgrounds, etc.).
int newScrollX = x - m_scrollOrigin.x();
int newScrollY = y - m_scrollOrigin.y();
if (m_scrollY == newScrollY && m_scrollX == newScrollX)
return;
m_scrollX = newScrollX;
m_scrollY = newScrollY;
// Update the positions of our child layers. Don't have updateLayerPositions() update
// compositing layers, because we need to do a deep update from the compositing ancestor.
for (RenderLayer* child = firstChild(); child; child = child->nextSibling())
child->updateLayerPositions(0);
RenderView* view = renderer()->view();
// We should have a RenderView if we're trying to scroll.
ASSERT(view);
if (view) {
#if ENABLE(DASHBOARD_SUPPORT)
// Update dashboard regions, scrolling may change the clip of a
// particular region.
view->frameView()->updateDashboardRegions();
#endif
view->updateWidgetPositions();
}
#if PLATFORM(ANDROID)
GraphicsLayerAndroid* backingLayer = 0;
bool scrollableContent = false;
#endif
#if USE(ACCELERATED_COMPOSITING)
if (compositor()->inCompositingMode()) {
// Our stacking context is guaranteed to contain all of our descendants that may need
// repositioning, so update compositing layers from there.
#if ENABLE(ANDROID_OVERFLOW_SCROLL)
if (view && backing() && backing()->graphicsLayer()) {
backingLayer = static_cast<GraphicsLayerAndroid*>(backing()->graphicsLayer());
scrollableContent = backingLayer->contentLayer()
&& backingLayer->contentLayer()->contentIsScrollable();
}
// If we have a scrollable content, no need to do this
RenderLayer* compositingAncestor = enclosingCompositingLayer();
if (!scrollableContent && compositingAncestor) {
#else
if (RenderLayer* compositingAncestor = stackingContext()->enclosingCompositingLayer()) {
#endif
if (compositor()->compositingConsultsOverlap())
compositor()->updateCompositingLayers(CompositingUpdateOnScroll, compositingAncestor);
else {
bool isUpdateRoot = true;
compositingAncestor->backing()->updateAfterLayout(RenderLayerBacking::AllDescendants, isUpdateRoot);
}
}
}
#endif
RenderBoxModelObject* repaintContainer = renderer()->containerForRepaint();
IntRect rectForRepaint = renderer()->clippedOverflowRectForRepaint(repaintContainer);
Frame* frame = renderer()->frame();
if (frame) {
// The caret rect needs to be invalidated after scrolling
frame->selection()->setCaretRectNeedsUpdate();
#if !ENABLE(ANDROID_OVERFLOW_SCROLL)
FloatQuad quadForFakeMouseMoveEvent = FloatQuad(rectForRepaint);
if (repaintContainer)
quadForFakeMouseMoveEvent = repaintContainer->localToAbsoluteQuad(quadForFakeMouseMoveEvent);
frame->eventHandler()->dispatchFakeMouseMoveEventSoonInQuad(quadForFakeMouseMoveEvent);
#endif
}
// Just schedule a full repaint of our object.
#if ENABLE(ANDROID_OVERFLOW_SCROLL)
// On android, scrollable areas are put on composited layers, so we
// do not need to repaint simply because we are scrolling
if (view && !(hasOverflowScroll() || scrollableContent))
renderer()->repaintUsingContainer(repaintContainer, rectForRepaint);
if (backingLayer && (hasOverflowScroll() || scrollableContent))
backingLayer->updateScrollOffset();
#else
if (view)
renderer()->repaintUsingContainer(repaintContainer, rectForRepaint);
#endif
// Schedule the scroll DOM event.
if (renderer()->node())
renderer()->node()->document()->eventQueue()->enqueueOrDispatchScrollEvent(renderer()->node(), EventQueue::ScrollEventElementTarget);
}
void RenderLayer::scrollRectToVisible(const IntRect& rect, bool scrollToAnchor, const ScrollAlignment& alignX, const ScrollAlignment& alignY)
{
RenderLayer* parentLayer = 0;
IntRect newRect = rect;
int xOffset = 0, yOffset = 0;
// 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);
FloatPoint absPos = box->localToAbsolute();
absPos.move(box->borderLeft(), box->borderTop());
IntRect layerBounds = IntRect(absPos.x() + scrollXOffset(), absPos.y() + scrollYOffset(), box->clientWidth(), box->clientHeight());
IntRect exposeRect = IntRect(rect.x() + scrollXOffset(), rect.y() + scrollYOffset(), rect.width(), rect.height());
IntRect r = getRectToExpose(layerBounds, exposeRect, alignX, alignY);
xOffset = r.x() - absPos.x();
yOffset = r.y() - absPos.y();
// Adjust offsets if they're outside of the allowable range.
xOffset = max(0, min(scrollWidth() - layerBounds.width(), xOffset));
yOffset = max(0, min(scrollHeight() - layerBounds.height(), yOffset));
if (xOffset != scrollXOffset() || yOffset != scrollYOffset()) {
int diffX = scrollXOffset();
int diffY = scrollYOffset();
scrollToOffset(xOffset, yOffset);
diffX = scrollXOffset() - diffX;
diffY = scrollYOffset() - diffY;
newRect.setX(rect.x() - diffX);
newRect.setY(rect.y() - diffY);
}
} else if (!parentLayer && renderer()->isBox() && renderBox()->canBeProgramaticallyScrolled(scrollToAnchor)) {
if (frameView) {
if (renderer()->document() && renderer()->document()->ownerElement() && renderer()->document()->ownerElement()->renderer()) {
IntRect viewRect = frameView->visibleContentRect();
IntRect r = getRectToExpose(viewRect, rect, alignX, alignY);
xOffset = r.x();
yOffset = r.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));
parentLayer = renderer()->document()->ownerElement()->renderer()->enclosingLayer();
newRect.setX(rect.x() - frameView->scrollX() + frameView->x());
newRect.setY(rect.y() - frameView->scrollY() + frameView->y());
} else {
IntRect viewRect = frameView->visibleContentRect();
IntRect r = getRectToExpose(viewRect, rect, alignX, alignY);
frameView->setScrollPosition(r.location());
// This is the outermost view of a web page, so after scrolling this view we
// scroll its container by calling Page::scrollRectIntoView.
// This only has an effect on the Mac platform in applications
// that put web views into scrolling containers, such as Mac OS X Mail.
// The canAutoscroll function in EventHandler also knows about this.
if (Frame* frame = frameView->frame()) {
if (Page* page = frame->page())
page->chrome()->scrollRectIntoView(rect);
}
}
}
}
if (parentLayer)
parentLayer->scrollRectToVisible(newRect, scrollToAnchor, alignX, alignY);
if (frameView)
frameView->resumeScheduledEvents();
}
IntRect RenderLayer::getRectToExpose(const IntRect &visibleRect, const IntRect &exposeRect, const ScrollAlignment& alignX, const ScrollAlignment& alignY)
{
// Determine the appropriate X behavior.
ScrollBehavior scrollX;
IntRect exposeRectX(exposeRect.x(), visibleRect.y(), exposeRect.width(), visibleRect.height());
int 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.
int 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;
IntRect exposeRectY(visibleRect.x(), exposeRect.y(), visibleRect.width(), exposeRect.height());
int 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.
int 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 IntRect(IntPoint(x, y), visibleRect.size());
}
void RenderLayer::autoscroll()
{
Frame* frame = renderer()->frame();
if (!frame)
return;
FrameView* frameView = frame->view();
if (!frameView)
return;
#if ENABLE(DRAG_SUPPORT)
frame->eventHandler()->updateSelectionForMouseDrag();
#endif
IntPoint currentDocumentPosition = frameView->windowToContents(frame->eventHandler()->currentMousePosition());
scrollRectToVisible(IntRect(currentDocumentPosition, IntSize(1, 1)), false, ScrollAlignment::alignToEdgeIfNeeded, ScrollAlignment::alignToEdgeIfNeeded);
}
void RenderLayer::resize(const PlatformMouseEvent& evt, const IntSize& oldOffset)
{
// FIXME: This should be possible on generated content but is not right now.
if (!inResizeMode() || !renderer()->hasOverflowClip() || !renderer()->node())
return;
// Set the width and height of the shadow ancestor node if there is one.
// This is necessary for textarea elements since the resizable layer is in the shadow content.
Element* element = static_cast<Element*>(renderer()->node()->shadowAncestorNode());
RenderBox* renderer = toRenderBox(element->renderer());
EResize resize = renderer->style()->resize();
if (resize == RESIZE_NONE)
return;
Document* document = element->document();
if (!document->frame()->eventHandler()->mousePressed())
return;
float zoomFactor = renderer->style()->effectiveZoom();
IntSize newOffset = offsetFromResizeCorner(document->view()->windowToContents(evt.pos()));
newOffset.setWidth(newOffset.width() / zoomFactor);
newOffset.setHeight(newOffset.height() / zoomFactor);
IntSize currentSize = IntSize(renderer->width() / zoomFactor, renderer->height() / zoomFactor);
IntSize minimumSize = element->minimumSizeForResizing().shrunkTo(currentSize);
element->setMinimumSizeForResizing(minimumSize);
IntSize adjustedOldOffset = IntSize(oldOffset.width() / zoomFactor, oldOffset.height() / zoomFactor);
IntSize difference = (currentSize + newOffset - adjustedOldOffset).expandedTo(minimumSize) - currentSize;
CSSStyleDeclaration* style = element->style();
bool isBoxSizingBorder = renderer->style()->boxSizing() == BORDER_BOX;
ExceptionCode ec;
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>).
style->setProperty(CSSPropertyMarginLeft, String::number(renderer->marginLeft() / zoomFactor) + "px", false, ec);
style->setProperty(CSSPropertyMarginRight, String::number(renderer->marginRight() / zoomFactor) + "px", false, ec);
}
int baseWidth = renderer->width() - (isBoxSizingBorder ? 0 : renderer->borderAndPaddingWidth());
baseWidth = baseWidth / zoomFactor;
style->setProperty(CSSPropertyWidth, String::number(baseWidth + difference.width()) + "px", false, ec);
}
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>).
style->setProperty(CSSPropertyMarginTop, String::number(renderer->marginTop() / zoomFactor) + "px", false, ec);
style->setProperty(CSSPropertyMarginBottom, String::number(renderer->marginBottom() / zoomFactor) + "px", false, ec);
}
int baseHeight = renderer->height() - (isBoxSizingBorder ? 0 : renderer->borderAndPaddingHeight());
baseHeight = baseHeight / zoomFactor;
style->setProperty(CSSPropertyHeight, String::number(baseHeight + difference.height()) + "px", false, ec);
}
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
{
Scrollbar* scrollbar = ((orientation == HorizontalScrollbar) ? m_hBar : m_vBar).get();
return scrollbar ? (scrollbar->totalSize() - scrollbar->visibleSize()) : 0;
}
void RenderLayer::setScrollOffset(const IntPoint& offset)
{
scrollTo(offset.x(), offset.y());
}
int RenderLayer::scrollPosition(Scrollbar* scrollbar) const
{
if (scrollbar->orientation() == HorizontalScrollbar)
return scrollXOffset();
if (scrollbar->orientation() == VerticalScrollbar)
return scrollYOffset();
return 0;
}
bool RenderLayer::isActive() const
{
Page* page = renderer()->frame()->page();
return page && page->focusController()->isActive();
}
static IntRect cornerRect(const RenderLayer* layer, const IntRect& bounds)
{
int horizontalThickness;
int verticalThickness;
if (!layer->verticalScrollbar() && !layer->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::nativeTheme()->scrollbarThickness();
verticalThickness = horizontalThickness;
} else if (layer->verticalScrollbar() && !layer->horizontalScrollbar()) {
horizontalThickness = layer->verticalScrollbar()->width();
verticalThickness = horizontalThickness;
} else if (layer->horizontalScrollbar() && !layer->verticalScrollbar()) {
verticalThickness = layer->horizontalScrollbar()->height();
horizontalThickness = verticalThickness;
} else {
horizontalThickness = layer->verticalScrollbar()->width();
verticalThickness = layer->horizontalScrollbar()->height();
}
return IntRect(bounds.maxX() - horizontalThickness - layer->renderer()->style()->borderRightWidth(),
bounds.maxY() - verticalThickness - layer->renderer()->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(this, renderBox()->borderBoxRect());
return IntRect();
}
static IntRect resizerCornerRect(const RenderLayer* layer, const IntRect& bounds)
{
ASSERT(layer->renderer()->isBox());
if (layer->renderer()->style()->resize() == RESIZE_NONE)
return IntRect();
return cornerRect(layer, bounds);
}
IntRect RenderLayer::scrollCornerAndResizerRect() const
{
RenderBox* box = renderBox();
if (!box)
return IntRect();
IntRect scrollCornerAndResizer = scrollCornerRect();
if (scrollCornerAndResizer.isEmpty())
scrollCornerAndResizer = resizerCornerRect(this, box->borderBoxRect());
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;
}
IntSize RenderLayer::contentsSize() const
{
return IntSize(const_cast<RenderLayer*>(this)->scrollWidth(), const_cast<RenderLayer*>(this)->scrollHeight());
}
int RenderLayer::visibleHeight() const
{
return m_height;
}
int RenderLayer::visibleWidth() const
{
return m_width;
}
bool RenderLayer::shouldSuspendScrollAnimations() const
{
RenderView* view = renderer()->view();
if (!view)
return true;
return view->frameView()->shouldSuspendScrollAnimations();
}
IntPoint RenderLayer::currentMousePosition() const
{
return renderer()->frame() ? renderer()->frame()->eventHandler()->currentMousePosition() : IntPoint();
}
IntSize RenderLayer::scrollbarOffset(const Scrollbar* scrollbar) const
{
RenderBox* box = renderBox();
if (scrollbar == m_vBar.get())
return IntSize(box->width() - box->borderRight() - scrollbar->width(), box->borderTop());
if (scrollbar == m_hBar.get())
return IntSize(box->borderLeft(), box->height() - box->borderBottom() - scrollbar->height());
ASSERT_NOT_REACHED();
return IntSize();
}
void RenderLayer::invalidateScrollbarRect(Scrollbar* scrollbar, const IntRect& rect)
{
#if USE(ACCELERATED_COMPOSITING)
if (scrollbar == m_vBar.get()) {
if (GraphicsLayer* layer = layerForVerticalScrollbar()) {
layer->setNeedsDisplayInRect(rect);
return;
}
} else {
if (GraphicsLayer* layer = layerForHorizontalScrollbar()) {
layer->setNeedsDisplayInRect(rect);
return;
}
}
#endif
IntRect scrollRect = rect;
RenderBox* box = renderBox();
ASSERT(box);
if (scrollbar == m_vBar.get())
scrollRect.move(box->width() - box->borderRight() - scrollbar->width(), box->borderTop());
else
scrollRect.move(box->borderLeft(), box->height() - box->borderBottom() - scrollbar->height());
renderer()->repaintRectangle(scrollRect);
}
void RenderLayer::invalidateScrollCornerRect(const IntRect& rect)
{
#if USE(ACCELERATED_COMPOSITING)
if (GraphicsLayer* layer = layerForScrollCorner()) {
layer->setNeedsDisplayInRect(rect);
return;
}
#endif
if (m_scrollCorner)
m_scrollCorner->repaintRectangle(rect);
if (m_resizer)
m_resizer->repaintRectangle(rect);
}
PassRefPtr<Scrollbar> RenderLayer::createScrollbar(ScrollbarOrientation orientation)
{
RefPtr<Scrollbar> widget;
RenderObject* actualRenderer = renderer()->node() ? renderer()->node()->shadowAncestorNode()->renderer() : renderer();
bool hasCustomScrollbarStyle = actualRenderer->isBox() && actualRenderer->style()->hasPseudoStyle(SCROLLBAR);
if (hasCustomScrollbarStyle)
widget = RenderScrollbar::createCustomScrollbar(this, orientation, toRenderBox(actualRenderer));
else {
widget = Scrollbar::createNativeScrollbar(this, orientation, RegularScrollbar);
if (orientation == HorizontalScrollbar)
didAddHorizontalScrollbar(widget.get());
else
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) {
if (scrollbar->isCustomScrollbar())
static_cast<RenderScrollbar*>(scrollbar.get())->clearOwningRenderer();
else {
if (orientation == HorizontalScrollbar)
willRemoveHorizontalScrollbar(scrollbar.get());
else
willRemoveVerticalScrollbar(scrollbar.get());
}
scrollbar->removeFromParent();
scrollbar->disconnectFromScrollableArea();
scrollbar = 0;
}
}
void RenderLayer::setHasHorizontalScrollbar(bool hasScrollbar)
{
if (hasScrollbar == (m_hBar != 0))
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();
#if ENABLE(DASHBOARD_SUPPORT)
// Force an update since we know the scrollbars have changed things.
if (renderer()->document()->hasDashboardRegions())
renderer()->document()->setDashboardRegionsDirty(true);
#endif
}
void RenderLayer::setHasVerticalScrollbar(bool hasScrollbar)
{
if (hasScrollbar == (m_vBar != 0))
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();
#if ENABLE(DASHBOARD_SUPPORT)
// Force an update since we know the scrollbars have changed things.
if (renderer()->document()->hasDashboardRegions())
renderer()->document()->setDashboardRegionsDirty(true);
#endif
}
int RenderLayer::verticalScrollbarWidth(OverlayScrollbarSizeRelevancy relevancy) const
{
if (!m_vBar || (m_vBar->isOverlayScrollbar() && relevancy == IgnoreOverlayScrollbarSize))
return 0;
return m_vBar->width();
}
int RenderLayer::horizontalScrollbarHeight(OverlayScrollbarSizeRelevancy relevancy) const
{
if (!m_hBar || (m_hBar->isOverlayScrollbar() && relevancy == IgnoreOverlayScrollbarSize))
return 0;
return m_hBar->height();
}
IntSize RenderLayer::offsetFromResizeCorner(const IntPoint& absolutePoint) const
{
// Currently the resize corner is always the bottom right corner
IntPoint bottomRight(width(), height());
IntPoint localPoint = absoluteToContents(absolutePoint);
return localPoint - bottomRight;
}
bool RenderLayer::hasOverflowControls() const
{
return m_hBar || m_vBar || m_scrollCorner || renderer()->style()->resize() != RESIZE_NONE;
}
#if ENABLE(ANDROID_OVERFLOW_SCROLL)
bool RenderLayer::hasOverflowParent() const
{
const RenderLayer* layer = this;
while (layer && !layer->hasOverflowScroll())
layer = layer->parent();
return layer;
}
#endif
void RenderLayer::positionOverflowControls(int tx, int ty)
{
if (!m_hBar && !m_vBar && (!renderer()->hasOverflowClip() || renderer()->style()->resize() == RESIZE_NONE))
return;
RenderBox* box = renderBox();
if (!box)
return;
const IntRect& borderBox = box->borderBoxRect();
const IntRect& scrollCorner = scrollCornerRect();
IntRect absBounds(borderBox.x() + tx, borderBox.y() + ty, borderBox.width(), borderBox.height());
if (m_vBar)
m_vBar->setFrameRect(IntRect(absBounds.maxX() - box->borderRight() - m_vBar->width(),
absBounds.y() + box->borderTop(),
m_vBar->width(),
absBounds.height() - (box->borderTop() + box->borderBottom()) - scrollCorner.height()));
if (m_hBar)
m_hBar->setFrameRect(IntRect(absBounds.x() + box->borderLeft(),
absBounds.maxY() - box->borderBottom() - m_hBar->height(),
absBounds.width() - (box->borderLeft() + box->borderRight()) - scrollCorner.width(),
m_hBar->height()));
#if USE(ACCELERATED_COMPOSITING)
if (GraphicsLayer* layer = layerForHorizontalScrollbar()) {
if (m_hBar) {
layer->setPosition(IntPoint(m_hBar->frameRect().x() - tx, m_hBar->frameRect().y() - ty));
layer->setSize(m_hBar->frameRect().size());
}
layer->setDrawsContent(m_hBar);
}
if (GraphicsLayer* layer = layerForVerticalScrollbar()) {
if (m_vBar) {
layer->setPosition(IntPoint(m_vBar->frameRect().x() - tx, m_vBar->frameRect().y() - ty));
layer->setSize(m_vBar->frameRect().size());
}
layer->setDrawsContent(m_vBar);
}
if (GraphicsLayer* layer = layerForScrollCorner()) {
const IntRect& scrollCornerAndResizer = scrollCornerAndResizerRect();
layer->setPosition(scrollCornerAndResizer.location());
layer->setSize(scrollCornerAndResizer.size());
layer->setDrawsContent(!scrollCornerAndResizer.isEmpty());
}
#endif
if (m_scrollCorner)
m_scrollCorner->setFrameRect(scrollCorner);
if (m_resizer)
m_resizer->setFrameRect(resizerCornerRect(this, borderBox));
}
#if PLATFORM(ANDROID)
// When width/height change, the scrollWidth/scrollHeight should be dirty.
// And this should be upstreamed to webkit.
void RenderLayer::setWidth(int w)
{
if (m_width != w) {
m_scrollDimensionsDirty = true;
m_width = w;
}
}
void RenderLayer::setHeight(int h)
{
if (m_height != h) {
m_scrollDimensionsDirty = true;
m_height = h;
}
}
#endif
int RenderLayer::scrollWidth()
{
if (m_scrollDimensionsDirty)
computeScrollDimensions();
return m_scrollWidth;
}
int RenderLayer::scrollHeight()
{
if (m_scrollDimensionsDirty)
computeScrollDimensions();
return m_scrollHeight;
}
int RenderLayer::overflowTop() const
{
RenderBox* box = renderBox();
IntRect overflowRect(box->layoutOverflowRect());
box->flipForWritingMode(overflowRect);
return overflowRect.y();
}
int RenderLayer::overflowBottom() const
{
RenderBox* box = renderBox();
IntRect overflowRect(box->layoutOverflowRect());
box->flipForWritingMode(overflowRect);
return overflowRect.maxY();
}
int RenderLayer::overflowLeft() const
{
RenderBox* box = renderBox();
IntRect overflowRect(box->layoutOverflowRect());
box->flipForWritingMode(overflowRect);
return overflowRect.x();
}
int RenderLayer::overflowRight() const
{
RenderBox* box = renderBox();
IntRect overflowRect(box->layoutOverflowRect());
box->flipForWritingMode(overflowRect);
return overflowRect.maxX();
}
void RenderLayer::computeScrollDimensions(bool* needHBar, bool* needVBar)
{
RenderBox* box = renderBox();
ASSERT(box);
m_scrollDimensionsDirty = false;
m_scrollLeftOverflow = overflowLeft() - box->borderLeft();
m_scrollTopOverflow = overflowTop() - box->borderTop();
m_scrollWidth = overflowRight() - overflowLeft();
m_scrollHeight = overflowBottom() - overflowTop();
m_scrollOrigin = IntPoint(-m_scrollLeftOverflow, -m_scrollTopOverflow);
if (needHBar)
*needHBar = m_scrollWidth > box->clientWidth();
if (needVBar)
*needVBar = m_scrollHeight > box->clientHeight();
}
void RenderLayer::updateOverflowStatus(bool horizontalOverflow, bool verticalOverflow)
{
if (m_overflowStatusDirty) {
m_horizontalOverflow = horizontalOverflow;
m_verticalOverflow = verticalOverflow;
m_overflowStatusDirty = false;
return;
}
bool horizontalOverflowChanged = (m_horizontalOverflow != horizontalOverflow);
bool verticalOverflowChanged = (m_verticalOverflow != verticalOverflow);
if (horizontalOverflowChanged || verticalOverflowChanged) {
m_horizontalOverflow = horizontalOverflow;
m_verticalOverflow = verticalOverflow;
if (FrameView* frameView = renderer()->document()->view()) {
frameView->scheduleEvent(OverflowEvent::create(horizontalOverflowChanged, horizontalOverflow, verticalOverflowChanged, verticalOverflow),
renderer()->node());
}
}
}
void RenderLayer::updateScrollInfoAfterLayout()
{
RenderBox* box = renderBox();
if (!box)
return;
m_scrollDimensionsDirty = true;
bool horizontalOverflow, verticalOverflow;
computeScrollDimensions(&horizontalOverflow, &verticalOverflow);
if (box->style()->overflowX() != OMARQUEE) {
// Layout may cause us to be in an invalid scroll position. In this case we need
// to pull our scroll offsets back to the max (or push them up to the min).
int newX = max(0, min(scrollXOffset(), scrollWidth() - box->clientWidth()));
int newY = max(0, min(scrollYOffset(), scrollHeight() - box->clientHeight()));
if (newX != scrollXOffset() || newY != scrollYOffset()) {
RenderView* view = renderer()->view();
ASSERT(view);
// scrollToOffset() may call updateLayerPositions(), which doesn't work
// with LayoutState.
// FIXME: Remove the disableLayoutState/enableLayoutState if the above changes.
if (view)
view->disableLayoutState();
scrollToOffset(newX, newY);
if (view)
view->enableLayoutState();
}
}
bool haveHorizontalBar = m_hBar;
bool haveVerticalBar = m_vBar;
// overflow:scroll should just enable/disable.
if (renderer()->style()->overflowX() == OSCROLL)
m_hBar->setEnabled(horizontalOverflow);
if (renderer()->style()->overflowY() == OSCROLL)
m_vBar->setEnabled(verticalOverflow);
// A dynamic change from a scrolling overflow to overflow:hidden means we need to get rid of any
// scrollbars that may be present.
if (renderer()->style()->overflowX() == OHIDDEN && haveHorizontalBar)
setHasHorizontalScrollbar(false);
if (renderer()->style()->overflowY() == OHIDDEN && haveVerticalBar)
setHasVerticalScrollbar(false);
// overflow:auto may need to lay out again if scrollbars got added/removed.
bool scrollbarsChanged = (box->hasAutoHorizontalScrollbar() && haveHorizontalBar != horizontalOverflow) ||
(box->hasAutoVerticalScrollbar() && haveVerticalBar != verticalOverflow);
if (scrollbarsChanged) {
if (box->hasAutoHorizontalScrollbar())
setHasHorizontalScrollbar(horizontalOverflow);
if (box->hasAutoVerticalScrollbar())
setHasVerticalScrollbar(verticalOverflow);
#if ENABLE(DASHBOARD_SUPPORT)
// Force an update since we know the scrollbars have changed things.
if (renderer()->document()->hasDashboardRegions())
renderer()->document()->setDashboardRegionsDirty(true);
#endif
renderer()->repaint();
if (renderer()->style()->overflowX() == OAUTO || renderer()->style()->overflowY() == OAUTO) {
if (!m_inOverflowRelayout) {
// Our proprietary overflow: overlay value doesn't trigger a layout.
m_inOverflowRelayout = true;
renderer()->setNeedsLayout(true, false);
if (renderer()->isRenderBlock()) {
RenderBlock* block = toRenderBlock(renderer());
block->scrollbarsChanged(box->hasAutoHorizontalScrollbar() && haveHorizontalBar != horizontalOverflow,
box->hasAutoVerticalScrollbar() && haveVerticalBar != verticalOverflow);
block->layoutBlock(true);
} else
renderer()->layout();
m_inOverflowRelayout = false;
}
}
}
// If overflow:scroll is turned into overflow:auto a bar might still be disabled (Bug 11985).
if (m_hBar && box->hasAutoHorizontalScrollbar())
m_hBar->setEnabled(true);
if (m_vBar && box->hasAutoVerticalScrollbar())
m_vBar->setEnabled(true);
// Set up the range (and page step/line step).
if (m_hBar) {
int clientWidth = box->clientWidth();
int pageStep = max(max<int>(clientWidth * Scrollbar::minFractionToStepWhenPaging(), clientWidth - Scrollbar::maxOverlapBetweenPages()), 1);
m_hBar->setSteps(Scrollbar::pixelsPerLineStep(), pageStep);
m_hBar->setProportion(clientWidth, m_scrollWidth);
}
if (m_vBar) {
int clientHeight = box->clientHeight();
int pageStep = max(max<int>(clientHeight * Scrollbar::minFractionToStepWhenPaging(), clientHeight - Scrollbar::maxOverlapBetweenPages()), 1);
m_vBar->setSteps(Scrollbar::pixelsPerLineStep(), pageStep);
m_vBar->setProportion(clientHeight, m_scrollHeight);
}
RenderView* view = renderer()->view();
view->disableLayoutState();
scrollToOffset(scrollXOffset(), scrollYOffset());
view->enableLayoutState();
if (renderer()->node() && renderer()->document()->hasListenerType(Document::OVERFLOWCHANGED_LISTENER))
updateOverflowStatus(horizontalOverflow, verticalOverflow);
#if ENABLE(ANDROID_OVERFLOW_SCROLL)
bool hasOverflowScroll = ((horizontalOverflow && m_hBar) || (verticalOverflow && m_vBar));
if (hasOverflowScroll) {
// Disable UI side scrolling for non-readonly textareas.
if (renderer()->isTextArea() && (!renderer()->node()
|| !static_cast<HTMLTextAreaElement*>(renderer()->node())->readOnly()))
hasOverflowScroll = false;
}
if (hasOverflowScroll != m_hasOverflowScroll) {
m_hasOverflowScroll = hasOverflowScroll;
dirtyZOrderLists();
dirtyStackingContextZOrderLists();
if (renderer()->node())
renderer()->node()->setNeedsStyleRecalc(SyntheticStyleChange);
}
#endif
}
void RenderLayer::paintOverflowControls(GraphicsContext* context, int tx, int ty, const IntRect& damageRect, bool paintingOverlayControls)
{
// Don't do anything if we have no overflow.
if (!renderer()->hasOverflowClip())
return;
// Overlay scrollbars paint in a second pass through the layer tree so that they will paint
// on top of everything else. If this is the normal painting pass, paintingOverlayControls
// will be false, and we should just tell the root layer that there are overlay scrollbars
// that need to be painted. That will cause the second pass through the layer tree to run,
// and we'll paint the scrollbars then. In the meantime, cache tx and ty so that the
// second pass doesn't need to re-enter the RenderTree to get it right.
if (hasOverlayScrollbars() && !paintingOverlayControls) {
RenderView* renderView = renderer()->view();
renderView->layer()->setContainsDirtyOverlayScrollbars(true);
m_cachedOverlayScrollbarOffset = IntPoint(tx, ty);
renderView->frameView()->setContainsScrollableAreaWithOverlayScrollbars(true);
return;
}
int offsetX = tx;
int offsetY = ty;
if (paintingOverlayControls) {
offsetX = m_cachedOverlayScrollbarOffset.x();
offsetY = m_cachedOverlayScrollbarOffset.y();
}
// Move the scrollbar widgets if necessary. We normally move and resize widgets during layout, but sometimes
// widgets can move without layout occurring (most notably when you scroll a document that
// contains fixed positioned elements).
positionOverflowControls(offsetX, offsetY);
// Now that we're sure the scrollbars are in the right place, paint them.
if (m_hBar
#if USE(ACCELERATED_COMPOSITING)
&& !layerForHorizontalScrollbar()
#endif
)
m_hBar->paint(context, damageRect);
if (m_vBar
#if USE(ACCELERATED_COMPOSITING)
&& !layerForVerticalScrollbar()
#endif
)
m_vBar->paint(context, damageRect);
#if USE(ACCELERATED_COMPOSITING)
if (layerForScrollCorner())
return;
#endif
// We fill our scroll corner with white if we have a scrollbar that doesn't run all the way up to the
// edge of the box.
paintScrollCorner(context, offsetX, offsetY, damageRect);
// Paint our resizer last, since it sits on top of the scroll corner.
paintResizer(context, offsetX, offsetY, damageRect);
}
void RenderLayer::paintScrollCorner(GraphicsContext* context, int tx, int ty, const IntRect& damageRect)
{
RenderBox* box = renderBox();
ASSERT(box);
IntRect cornerRect = scrollCornerRect();
IntRect absRect = IntRect(cornerRect.x() + tx, cornerRect.y() + ty, cornerRect.width(), cornerRect.height());
if (!absRect.intersects(damageRect))
return;
if (context->updatingControlTints()) {
updateScrollCornerStyle();
return;
}
if (m_scrollCorner) {
m_scrollCorner->paintIntoRect(context, tx, ty, absRect);
return;
}
// We don't want to paint white if we have overlay scrollbars, since we need
// to see what is behind it.
if (!hasOverlayScrollbars())
context->fillRect(absRect, Color::white, box->style()->colorSpace());
}
void RenderLayer::paintResizer(GraphicsContext* context, int tx, int ty, const IntRect& damageRect)
{
if (renderer()->style()->resize() == RESIZE_NONE)
return;
RenderBox* box = renderBox();
ASSERT(box);
IntRect cornerRect = resizerCornerRect(this, box->borderBoxRect());
IntRect absRect = IntRect(cornerRect.x() + tx, cornerRect.y() + ty, cornerRect.width(), cornerRect.height());
if (!absRect.intersects(damageRect))
return;
if (context->updatingControlTints()) {
updateResizerStyle();
return;
}
if (m_resizer) {
m_resizer->paintIntoRect(context, tx, ty, absRect);
return;
}
// Paint the resizer control.
DEFINE_STATIC_LOCAL(RefPtr<Image>, resizeCornerImage, (Image::loadPlatformResource("textAreaResizeCorner")));
IntPoint imagePoint(absRect.maxX() - resizeCornerImage->width(), absRect.maxY() - resizeCornerImage->height());
context->drawImage(resizeCornerImage.get(), box->style()->colorSpace(), imagePoint);
// Draw a frame around the resizer (1px grey line) if there are any scrollbars present.
// Clipping will exclude the right and bottom edges of this frame.
if (!hasOverlayScrollbars() && (m_vBar || m_hBar)) {
context->save();
context->clip(absRect);
IntRect largerCorner = absRect;
largerCorner.setSize(IntSize(largerCorner.width() + 1, largerCorner.height() + 1));
context->setStrokeColor(Color(makeRGB(217, 217, 217)), ColorSpaceDeviceRGB);
context->setStrokeThickness(1.0f);
context->setFillColor(Color::transparent, ColorSpaceDeviceRGB);
context->drawRect(largerCorner);
context->restore();
}
}
bool RenderLayer::isPointInResizeControl(const IntPoint& absolutePoint) const
{
if (!renderer()->hasOverflowClip() || renderer()->style()->resize() == RESIZE_NONE)
return false;
RenderBox* box = renderBox();
ASSERT(box);
IntPoint localPoint = absoluteToContents(absolutePoint);
IntRect localBounds(0, 0, box->width(), box->height());
return resizerCornerRect(this, localBounds).contains(localPoint);
}
bool RenderLayer::hitTestOverflowControls(HitTestResult& result, const IntPoint& localPoint)
{
if (!m_hBar && !m_vBar && (!renderer()->hasOverflowClip() || renderer()->style()->resize() == RESIZE_NONE))
return false;
RenderBox* box = renderBox();
ASSERT(box);
IntRect resizeControlRect;
if (renderer()->style()->resize() != RESIZE_NONE) {
resizeControlRect = resizerCornerRect(this, box->borderBoxRect());
if (resizeControlRect.contains(localPoint))
return true;
}
int resizeControlSize = max(resizeControlRect.height(), 0);
if (m_vBar) {
IntRect vBarRect(box->width() - box->borderRight() - m_vBar->width(),
box->borderTop(),
m_vBar->width(),
box->height() - (box->borderTop() + box->borderBottom()) - (m_hBar ? m_hBar->height() : resizeControlSize));
if (vBarRect.contains(localPoint)) {
result.setScrollbar(m_vBar.get());
return true;
}
}
resizeControlSize = max(resizeControlRect.width(), 0);
if (m_hBar) {
IntRect hBarRect(box->borderLeft(),
box->height() - box->borderBottom() - m_hBar->height(),
box->width() - (box->borderLeft() + box->borderRight()) - (m_vBar ? m_vBar->width() : resizeControlSize),
m_hBar->height());
if (hBarRect.contains(localPoint)) {
result.setScrollbar(m_hBar.get());
return true;
}
}
return false;
}
bool RenderLayer::scroll(ScrollDirection direction, ScrollGranularity granularity, float multiplier)
{
return ScrollableArea::scroll(direction, granularity, multiplier);
}
void RenderLayer::paint(GraphicsContext* p, const IntRect& damageRect, PaintBehavior paintBehavior, RenderObject *paintingRoot)
{
OverlapTestRequestMap overlapTestRequests;
paintLayer(this, p, damageRect, paintBehavior, paintingRoot, &overlapTestRequests);
OverlapTestRequestMap::iterator end = overlapTestRequests.end();
for (OverlapTestRequestMap::iterator it = overlapTestRequests.begin(); it != end; ++it)
it->first->setOverlapTestResult(false);
}
void RenderLayer::paintOverlayScrollbars(GraphicsContext* p, const IntRect& damageRect, PaintBehavior paintBehavior, RenderObject *paintingRoot)
{
if (!m_containsDirtyOverlayScrollbars)
return;
paintLayer(this, p, damageRect, paintBehavior, paintingRoot, 0, PaintLayerHaveTransparency | PaintLayerTemporaryClipRects
| PaintLayerPaintingOverlayScrollbars);
m_containsDirtyOverlayScrollbars = false;
}
static void setClip(GraphicsContext* p, const IntRect& paintDirtyRect, const IntRect& clipRect)
{
if (paintDirtyRect == clipRect)
return;
p->save();
p->clip(clipRect);
}
static void restoreClip(GraphicsContext* p, const IntRect& paintDirtyRect, const IntRect& clipRect)
{
if (paintDirtyRect == clipRect)
return;
p->restore();
}
static void performOverlapTests(OverlapTestRequestMap& overlapTestRequests, const RenderLayer* rootLayer, const RenderLayer* layer)
{
Vector<OverlapTestRequestClient*> overlappedRequestClients;
OverlapTestRequestMap::iterator end = overlapTestRequests.end();
IntRect boundingBox = layer->boundingBox(rootLayer);
for (OverlapTestRequestMap::iterator it = overlapTestRequests.begin(); it != end; ++it) {
if (!boundingBox.intersects(it->second))
continue;
it->first->setOverlapTestResult(true);
overlappedRequestClients.append(it->first);
}
for (size_t i = 0; i < overlappedRequestClients.size(); ++i)
overlapTestRequests.remove(overlappedRequestClients[i]);
}
#if USE(ACCELERATED_COMPOSITING)
static bool shouldDoSoftwarePaint(const RenderLayer* layer, bool paintingReflection)
{
return paintingReflection && !layer->has3DTransform();
}
#endif
void RenderLayer::paintLayer(RenderLayer* rootLayer, GraphicsContext* p,
const IntRect& paintDirtyRect, PaintBehavior paintBehavior,
RenderObject* paintingRoot, OverlapTestRequestMap* overlapTestRequests,
PaintLayerFlags paintFlags)
{
#if USE(ACCELERATED_COMPOSITING)
if (isComposited()) {
// The updatingControlTints() painting pass goes through compositing layers,
// but we need to ensure that we don't cache clip rects computed with the wrong root in this case.
if (p->updatingControlTints() || (paintBehavior & PaintBehaviorFlattenCompositingLayers))
paintFlags |= PaintLayerTemporaryClipRects;
else if (!backing()->paintingGoesToWindow() && !shouldDoSoftwarePaint(this, paintFlags & PaintLayerPaintingReflection)) {
// If this RenderLayer should paint into its backing, that will be done via RenderLayerBacking::paintIntoLayer().
return;
}
}
#endif
// Avoid painting layers when stylesheets haven't loaded. This eliminates FOUC.
// It's ok not to draw, because later on, when all the stylesheets do load, updateStyleSelector on the Document
// will do a full repaint().
if (renderer()->document()->didLayoutWithPendingStylesheets() && !renderer()->isRenderView() && !renderer()->isRoot())
return;
// If this layer is totally invisible then there is nothing to paint.
if (!renderer()->opacity())
return;
if (paintsWithTransparency(paintBehavior))
paintFlags |= PaintLayerHaveTransparency;
// Apply a transform if we have one. A reflection is considered to be a transform, since it is a flip and a translate.
if (paintsWithTransform(paintBehavior) && !(paintFlags & PaintLayerAppliedTransform)) {
TransformationMatrix layerTransform = renderableTransform(paintBehavior);
// If the transform can't be inverted, then don't paint anything.
if (!layerTransform.isInvertible())
return;
// If we have a transparency layer enclosing us and we are the root of a transform, then we need to establish the transparency
// layer from the parent now.
if (paintFlags & PaintLayerHaveTransparency)
parent()->beginTransparencyLayers(p, rootLayer, paintBehavior);
// Make sure the parent's clip rects have been calculated.
IntRect clipRect = paintDirtyRect;
if (parent()) {
clipRect = backgroundClipRect(rootLayer, paintFlags & PaintLayerTemporaryClipRects);
clipRect.intersect(paintDirtyRect);
}
// Push the parent coordinate space's clip.
setClip(p, paintDirtyRect, clipRect);
// Adjust the transform such that the renderer's upper left corner will paint at (0,0) in user space.
// This involves subtracting out the position of the layer in our current coordinate space.
int x = 0;
int y = 0;
convertToLayerCoords(rootLayer, x, y);
TransformationMatrix transform(layerTransform);
transform.translateRight(x, y);
// Apply the transform.
p->save();
p->concatCTM(transform.toAffineTransform());
// Now do a paint with the root layer shifted to be us.
paintLayer(this, p, transform.inverse().mapRect(paintDirtyRect), paintBehavior, paintingRoot, overlapTestRequests, paintFlags | PaintLayerAppliedTransform);
p->restore();
// Restore the clip.
restoreClip(p, paintDirtyRect, clipRect);
return;
}
PaintLayerFlags localPaintFlags = paintFlags & ~PaintLayerAppliedTransform;
bool haveTransparency = localPaintFlags & PaintLayerHaveTransparency;
// Paint the reflection first if we have one.
if (m_reflection && !m_paintingInsideReflection) {
// Mark that we are now inside replica painting.
m_paintingInsideReflection = true;
reflectionLayer()->paintLayer(rootLayer, p, paintDirtyRect, paintBehavior, paintingRoot, overlapTestRequests, localPaintFlags | PaintLayerPaintingReflection);
m_paintingInsideReflection = false;
}
// Calculate the clip rects we should use.
IntRect layerBounds, damageRect, clipRectToApply, outlineRect;
calculateRects(rootLayer, paintDirtyRect, layerBounds, damageRect, clipRectToApply, outlineRect, localPaintFlags & PaintLayerTemporaryClipRects);
int x = layerBounds.x();
int y = layerBounds.y();
int tx = x - renderBoxX();
int ty = y - renderBoxY();
// Ensure our lists are up-to-date.
updateCompositingAndLayerListsIfNeeded();
bool forceBlackText = paintBehavior & PaintBehaviorForceBlackText;
bool selectionOnly = paintBehavior & PaintBehaviorSelectionOnly;
// If this layer's renderer is a child of the paintingRoot, we render unconditionally, which
// is done by passing a nil paintingRoot down to our renderer (as if no paintingRoot was ever set).
// Else, our renderer tree may or may not contain the painting root, so we pass that root along
// so it will be tested against as we descend through the renderers.
RenderObject* paintingRootForRenderer = 0;
if (paintingRoot && !renderer()->isDescendantOf(paintingRoot))
paintingRootForRenderer = paintingRoot;
if (overlapTestRequests && isSelfPaintingLayer())
performOverlapTests(*overlapTestRequests, rootLayer, this);
bool paintingOverlayScrollbars = paintFlags & PaintLayerPaintingOverlayScrollbars;
// We want to paint our layer, but only if we intersect the damage rect.
bool shouldPaint = intersectsDamageRect(layerBounds, damageRect, rootLayer) && m_hasVisibleContent && isSelfPaintingLayer();
if (shouldPaint && !selectionOnly && !damageRect.isEmpty() && !paintingOverlayScrollbars) {
// Begin transparency layers lazily now that we know we have to paint something.
if (haveTransparency)
beginTransparencyLayers(p, rootLayer, paintBehavior);
// Paint our background first, before painting any child layers.
// Establish the clip used to paint our background.
setClip(p, paintDirtyRect, damageRect);
// Paint the background.
PaintInfo paintInfo(p, damageRect, PaintPhaseBlockBackground, false, paintingRootForRenderer, 0);
renderer()->paint(paintInfo, tx, ty);
// Restore the clip.
restoreClip(p, paintDirtyRect, damageRect);
}
// Now walk the sorted list of children with negative z-indices.
paintList(m_negZOrderList, rootLayer, p, paintDirtyRect, paintBehavior, paintingRoot, overlapTestRequests, localPaintFlags);
// Now establish the appropriate clip and paint our child RenderObjects.
if (shouldPaint && !clipRectToApply.isEmpty() && !paintingOverlayScrollbars) {
// Begin transparency layers lazily now that we know we have to paint something.
if (haveTransparency)
beginTransparencyLayers(p, rootLayer, paintBehavior);
// Set up the clip used when painting our children.
setClip(p, paintDirtyRect, clipRectToApply);
PaintInfo paintInfo(p, clipRectToApply,
selectionOnly ? PaintPhaseSelection : PaintPhaseChildBlockBackgrounds,
forceBlackText, paintingRootForRenderer, 0);
renderer()->paint(paintInfo, tx, ty);
if (!selectionOnly) {
paintInfo.phase = PaintPhaseFloat;
renderer()->paint(paintInfo, tx, ty);
paintInfo.phase = PaintPhaseForeground;
paintInfo.overlapTestRequests = overlapTestRequests;
renderer()->paint(paintInfo, tx, ty);
paintInfo.phase = PaintPhaseChildOutlines;
renderer()->paint(paintInfo, tx, ty);
}
// Now restore our clip.
restoreClip(p, paintDirtyRect, clipRectToApply);
}
if (!outlineRect.isEmpty() && isSelfPaintingLayer() && !paintingOverlayScrollbars) {
// Paint our own outline
PaintInfo paintInfo(p, outlineRect, PaintPhaseSelfOutline, false, paintingRootForRenderer, 0);
setClip(p, paintDirtyRect, outlineRect);
renderer()->paint(paintInfo, tx, ty);
restoreClip(p, paintDirtyRect, outlineRect);
}
// Paint any child layers that have overflow.
paintList(m_normalFlowList, rootLayer, p, paintDirtyRect, paintBehavior, paintingRoot, overlapTestRequests, localPaintFlags);
// Now walk the sorted list of children with positive z-indices.
paintList(m_posZOrderList, rootLayer, p, paintDirtyRect, paintBehavior, paintingRoot, overlapTestRequests, localPaintFlags);
if (renderer()->hasMask() && shouldPaint && !selectionOnly && !damageRect.isEmpty() && !paintingOverlayScrollbars) {
setClip(p, paintDirtyRect, damageRect);
// Paint the mask.
PaintInfo paintInfo(p, damageRect, PaintPhaseMask, false, paintingRootForRenderer, 0);
renderer()->paint(paintInfo, tx, ty);
// Restore the clip.
restoreClip(p, paintDirtyRect, damageRect);
}
if (paintingOverlayScrollbars) {
setClip(p, paintDirtyRect, damageRect);
paintOverflowControls(p, tx, ty, damageRect, true);
restoreClip(p, paintDirtyRect, damageRect);
}
// End our transparency layer
if (haveTransparency && m_usedTransparency && !m_paintingInsideReflection) {
p->endTransparencyLayer();
p->restore();
m_usedTransparency = false;
}
}
void RenderLayer::paintList(Vector<RenderLayer*>* list, RenderLayer* rootLayer, GraphicsContext* p,
const IntRect& paintDirtyRect, PaintBehavior paintBehavior,
RenderObject* paintingRoot, OverlapTestRequestMap* overlapTestRequests,
PaintLayerFlags paintFlags)
{
if (!list)
return;
for (size_t i = 0; i < list->size(); ++i) {
RenderLayer* childLayer = list->at(i);
if (!childLayer->isPaginated())
childLayer->paintLayer(rootLayer, p, paintDirtyRect, paintBehavior, paintingRoot, overlapTestRequests, paintFlags);
else
paintPaginatedChildLayer(childLayer, rootLayer, p, paintDirtyRect, paintBehavior, paintingRoot, overlapTestRequests, paintFlags);
}
}
void RenderLayer::paintPaginatedChildLayer(RenderLayer* childLayer, RenderLayer* rootLayer, GraphicsContext* context,
const IntRect& paintDirtyRect, PaintBehavior paintBehavior,
RenderObject* paintingRoot, OverlapTestRequestMap* overlapTestRequests,
PaintLayerFlags paintFlags)
{
// We need to do multiple passes, breaking up our child layer into strips.
Vector<RenderLayer*> columnLayers;
RenderLayer* ancestorLayer = isNormalFlowOnly() ? parent() : stackingContext();
for (RenderLayer* curr = childLayer->parent(); curr; curr = curr->parent()) {
if (curr->renderer()->hasColumns() && checkContainingBlockChainForPagination(childLayer->renderer(), curr->renderBox()))
columnLayers.append(curr);
if (curr == ancestorLayer)
break;
}
ASSERT(columnLayers.size());
paintChildLayerIntoColumns(childLayer, rootLayer, context, paintDirtyRect, paintBehavior, paintingRoot, overlapTestRequests, paintFlags, columnLayers, columnLayers.size() - 1);
}
void RenderLayer::paintChildLayerIntoColumns(RenderLayer* childLayer, RenderLayer* rootLayer, GraphicsContext* context,
const IntRect& paintDirtyRect, PaintBehavior paintBehavior,
RenderObject* paintingRoot, OverlapTestRequestMap* overlapTestRequests,
PaintLayerFlags paintFlags, const Vector<RenderLayer*>& columnLayers, size_t colIndex)
{
RenderBlock* columnBlock = toRenderBlock(columnLayers[colIndex]->renderer());
ASSERT(columnBlock && columnBlock->hasColumns());
if (!columnBlock || !columnBlock->hasColumns())
return;
int layerX = 0;
int layerY = 0;
columnBlock->layer()->convertToLayerCoords(rootLayer, layerX, layerY);
bool isHorizontal = columnBlock->style()->isHorizontalWritingMode();
ColumnInfo* colInfo = columnBlock->columnInfo();
unsigned colCount = columnBlock->columnCount(colInfo);
int currLogicalTopOffset = 0;
for (unsigned i = 0; i < colCount; i++) {
// For each rect, we clip to the rect, and then we adjust our coords.
IntRect colRect = columnBlock->columnRectAt(colInfo, i);
columnBlock->flipForWritingMode(colRect);
int logicalLeftOffset = (isHorizontal ? colRect.x() : colRect.y()) - columnBlock->logicalLeftOffsetForContent();
IntSize offset = isHorizontal ? IntSize(logicalLeftOffset, currLogicalTopOffset) : IntSize(currLogicalTopOffset, logicalLeftOffset);
colRect.move(layerX, layerY);
IntRect localDirtyRect(paintDirtyRect);
localDirtyRect.intersect(colRect);
if (!localDirtyRect.isEmpty()) {
context->save();
// Each strip pushes a clip, since column boxes are specified as being
// like overflow:hidden.
context->clip(colRect);
if (!colIndex) {
// Apply a translation transform to change where the layer paints.
TransformationMatrix oldTransform;
bool oldHasTransform = childLayer->transform();
if (oldHasTransform)
oldTransform = *childLayer->transform();
TransformationMatrix newTransform(oldTransform);
newTransform.translateRight(offset.width(), offset.height());
childLayer->m_transform.set(new TransformationMatrix(newTransform));
childLayer->paintLayer(rootLayer, context, localDirtyRect, paintBehavior, paintingRoot, overlapTestRequests, paintFlags);
if (oldHasTransform)
childLayer->m_transform.set(new TransformationMatrix(oldTransform));
else
childLayer->m_transform.clear();
} else {
// Adjust the transform such that the renderer's upper left corner will paint at (0,0) in user space.
// This involves subtracting out the position of the layer in our current coordinate space.
int childX = 0;
int childY = 0;
columnLayers[colIndex - 1]->convertToLayerCoords(rootLayer, childX, childY);
TransformationMatrix transform;
transform.translateRight(childX + offset.width(), childY + offset.height());
// Apply the transform.
context->concatCTM(transform.toAffineTransform());
// Now do a paint with the root layer shifted to be the next multicol block.
paintChildLayerIntoColumns(childLayer, columnLayers[colIndex - 1], context, transform.inverse().mapRect(localDirtyRect), paintBehavior,
paintingRoot, overlapTestRequests, paintFlags,
columnLayers, colIndex - 1);
}
context->restore();
}
// Move to the next position.
int blockDelta = isHorizontal ? colRect.height() : colRect.width();
if (columnBlock->style()->isFlippedBlocksWritingMode())
currLogicalTopOffset += blockDelta;
else
currLogicalTopOffset -= blockDelta;
}
}
static inline IntRect frameVisibleRect(RenderObject* renderer)
{
FrameView* frameView = renderer->document()->view();
if (!frameView)
return IntRect();
return frameView->visibleContentRect();
}
bool RenderLayer::hitTest(const HitTestRequest& request, HitTestResult& result)
{
renderer()->document()->updateLayout();
IntRect hitTestArea = renderer()->view()->documentRect();
if (!request.ignoreClipping())
hitTestArea.intersect(frameVisibleRect(renderer()));
RenderLayer* insideLayer = hitTestLayer(this, 0, request, result, hitTestArea, result.point(), false);
if (!insideLayer) {
// We didn't hit any layer. If we are the root layer and the mouse is -- or just was -- down,
// return ourselves. We do this so mouse events continue getting delivered after a drag has
// exited the WebView, and so hit testing over a scrollbar hits the content document.
if ((request.active() || request.mouseUp()) && renderer()->isRenderView()) {
renderer()->updateHitTestResult(result, result.point());
insideLayer = this;
}
}
// Now determine if the result is inside an anchor - if the urlElement isn't already set.
Node* node = result.innerNode();
if (node && !result.URLElement())
result.setURLElement(static_cast<Element*>(node->enclosingLinkEventParentOrSelf()));
// Next set up the correct :hover/:active state along the new chain.
updateHoverActiveState(request, result);
// Now return whether we were inside this layer (this will always be true for the root
// layer).
return insideLayer;
}
Node* RenderLayer::enclosingElement() const
{
for (RenderObject* r = renderer(); r; r = r->parent()) {
if (Node* e = r->node())
return e;
}
ASSERT_NOT_REACHED();
return 0;
}
// Compute the z-offset of the point in the transformState.
// This is effectively projecting a ray normal to the plane of ancestor, finding where that
// ray intersects target, and computing the z delta between those two points.
static double computeZOffset(const HitTestingTransformState& transformState)
{
// We got an affine transform, so no z-offset
if (transformState.m_accumulatedTransform.isAffine())
return 0;
// Flatten the point into the target plane
FloatPoint targetPoint = transformState.mappedPoint();
// Now map the point back through the transform, which computes Z.
FloatPoint3D backmappedPoint = transformState.m_accumulatedTransform.mapPoint(FloatPoint3D(targetPoint));
return backmappedPoint.z();
}
PassRefPtr<HitTestingTransformState> RenderLayer::createLocalTransformState(RenderLayer* rootLayer, RenderLayer* containerLayer,
const IntRect& hitTestRect, const IntPoint& hitTestPoint,
const HitTestingTransformState* containerTransformState) const
{
RefPtr<HitTestingTransformState> transformState;
int offsetX = 0;
int offsetY = 0;
if (containerTransformState) {
// If we're already computing transform state, then it's relative to the container (which we know is non-null).
transformState = HitTestingTransformState::create(*containerTransformState);
convertToLayerCoords(containerLayer, offsetX, offsetY);
} else {
// If this is the first time we need to make transform state, then base it off of hitTestPoint,
// which is relative to rootLayer.
transformState = HitTestingTransformState::create(hitTestPoint, FloatQuad(hitTestRect));
convertToLayerCoords(rootLayer, offsetX, offsetY);
}
RenderObject* containerRenderer = containerLayer ? containerLayer->renderer() : 0;
if (renderer()->shouldUseTransformFromContainer(containerRenderer)) {
TransformationMatrix containerTransform;
renderer()->getTransformFromContainer(containerRenderer, IntSize(offsetX, offsetY), containerTransform);
transformState->applyTransform(containerTransform, HitTestingTransformState::AccumulateTransform);
} else {
transformState->translate(offsetX, offsetY, HitTestingTransformState::AccumulateTransform);
}
return transformState;
}
static bool isHitCandidate(const RenderLayer* hitLayer, bool canDepthSort, double* zOffset, const HitTestingTransformState* transformState)
{
if (!hitLayer)
return false;
// The hit layer is depth-sorting with other layers, so just say that it was hit.
if (canDepthSort)
return true;
// We need to look at z-depth to decide if this layer was hit.
if (zOffset) {
ASSERT(transformState);
// This is actually computing our z, but that's OK because the hitLayer is coplanar with us.
double childZOffset = computeZOffset(*transformState);
if (childZOffset > *zOffset) {
*zOffset = childZOffset;
return true;
}
return false;
}
return true;
}
// hitTestPoint and hitTestRect are relative to rootLayer.
// A 'flattening' layer is one preserves3D() == false.
// transformState.m_accumulatedTransform holds the transform from the containing flattening layer.
// transformState.m_lastPlanarPoint is the hitTestPoint in the plane of the containing flattening layer.
// transformState.m_lastPlanarQuad is the hitTestRect as a quad in the plane of the containing flattening layer.
//
// If zOffset is non-null (which indicates that the caller wants z offset information),
// *zOffset on return is the z offset of the hit point relative to the containing flattening layer.
RenderLayer* RenderLayer::hitTestLayer(RenderLayer* rootLayer, RenderLayer* containerLayer, const HitTestRequest& request, HitTestResult& result,
const IntRect& hitTestRect, const IntPoint& hitTestPoint, bool appliedTransform,
const HitTestingTransformState* transformState, double* zOffset)
{
// The natural thing would be to keep HitTestingTransformState on the stack, but it's big, so we heap-allocate.
bool useTemporaryClipRects = false;
#if USE(ACCELERATED_COMPOSITING)
useTemporaryClipRects = compositor()->inCompositingMode();
#endif
useTemporaryClipRects |= renderer()->view()->frameView()->containsScrollableAreaWithOverlayScrollbars();
IntRect hitTestArea = result.rectForPoint(hitTestPoint);
// Apply a transform if we have one.
if (transform() && !appliedTransform) {
// Make sure the parent's clip rects have been calculated.
if (parent()) {
IntRect clipRect = backgroundClipRect(rootLayer, useTemporaryClipRects, IncludeOverlayScrollbarSize);
// Go ahead and test the enclosing clip now.
if (!clipRect.intersects(hitTestArea))
return 0;
}
// Create a transform state to accumulate this transform.
RefPtr<HitTestingTransformState> newTransformState = createLocalTransformState(rootLayer, containerLayer, hitTestRect, hitTestPoint, transformState);
// If the transform can't be inverted, then don't hit test this layer at all.
if (!newTransformState->m_accumulatedTransform.isInvertible())
return 0;
// Compute the point and the hit test rect in the coords of this layer by using the values
// from the transformState, which store the point and quad in the coords of the last flattened
// layer, and the accumulated transform which lets up map through preserve-3d layers.
//
// We can't just map hitTestPoint and hitTestRect because they may have been flattened (losing z)
// by our container.
IntPoint localPoint = roundedIntPoint(newTransformState->mappedPoint());
IntRect localHitTestRect;
#if USE(ACCELERATED_COMPOSITING)
if (isComposited()) {
// It doesn't make sense to project hitTestRect into the plane of this layer, so use the same bounds we use for painting.
localHitTestRect = backing()->compositedBounds();
} else
#endif
localHitTestRect = newTransformState->mappedQuad().enclosingBoundingBox();
// Now do a hit test with the root layer shifted to be us.
return hitTestLayer(this, containerLayer, request, result, localHitTestRect, localPoint, true, newTransformState.get(), zOffset);
}
// Ensure our lists and 3d status are up-to-date.
updateCompositingAndLayerListsIfNeeded();
update3DTransformedDescendantStatus();
RefPtr<HitTestingTransformState> localTransformState;
if (appliedTransform) {
// We computed the correct state in the caller (above code), so just reference it.
ASSERT(transformState);
localTransformState = const_cast<HitTestingTransformState*>(transformState);
} else if (transformState || m_has3DTransformedDescendant || preserves3D()) {
// We need transform state for the first time, or to offset the container state, so create it here.
localTransformState = createLocalTransformState(rootLayer, containerLayer, hitTestRect, hitTestPoint, transformState);
}
// Check for hit test on backface if backface-visibility is 'hidden'
if (localTransformState && renderer()->style()->backfaceVisibility() == BackfaceVisibilityHidden) {
TransformationMatrix invertedMatrix = localTransformState->m_accumulatedTransform.inverse();
// If the z-vector of the matrix is negative, the back is facing towards the viewer.
if (invertedMatrix.m33() < 0)
return 0;
}
RefPtr<HitTestingTransformState> unflattenedTransformState = localTransformState;
if (localTransformState && !preserves3D()) {
// Keep a copy of the pre-flattening state, for computing z-offsets for the container
unflattenedTransformState = HitTestingTransformState::create(*localTransformState);
// This layer is flattening, so flatten the state passed to descendants.
localTransformState->flatten();
}
// Calculate the clip rects we should use.
IntRect layerBounds;
IntRect bgRect;
IntRect fgRect;
IntRect outlineRect;
calculateRects(rootLayer, hitTestRect, layerBounds, bgRect, fgRect, outlineRect, useTemporaryClipRects, IncludeOverlayScrollbarSize);
// The following are used for keeping track of the z-depth of the hit point of 3d-transformed
// descendants.
double localZOffset = -numeric_limits<double>::infinity();
double* zOffsetForDescendantsPtr = 0;
double* zOffsetForContentsPtr = 0;
bool depthSortDescendants = false;
if (preserves3D()) {
depthSortDescendants = true;
// Our layers can depth-test with our container, so share the z depth pointer with the container, if it passed one down.
zOffsetForDescendantsPtr = zOffset ? zOffset : &localZOffset;
zOffsetForContentsPtr = zOffset ? zOffset : &localZOffset;
} else if (m_has3DTransformedDescendant) {
// Flattening layer with 3d children; use a local zOffset pointer to depth-test children and foreground.
depthSortDescendants = true;
zOffsetForDescendantsPtr = zOffset ? zOffset : &localZOffset;
zOffsetForContentsPtr = zOffset ? zOffset : &localZOffset;
} else if (zOffset) {
zOffsetForDescendantsPtr = 0;
// Container needs us to give back a z offset for the hit layer.
zOffsetForContentsPtr = zOffset;
}
// This variable tracks which layer the mouse ends up being inside.
RenderLayer* candidateLayer = 0;
// Begin by walking our list of positive layers from highest z-index down to the lowest z-index.
RenderLayer* hitLayer = hitTestList(m_posZOrderList, rootLayer, request, result, hitTestRect, hitTestPoint,
localTransformState.get(), zOffsetForDescendantsPtr, zOffset, unflattenedTransformState.get(), depthSortDescendants);
if (hitLayer) {
if (!depthSortDescendants)
return hitLayer;
candidateLayer = hitLayer;
}
// Now check our overflow objects.
hitLayer = hitTestList(m_normalFlowList, rootLayer, request, result, hitTestRect, hitTestPoint,
localTransformState.get(), zOffsetForDescendantsPtr, zOffset, unflattenedTransformState.get(), depthSortDescendants);
if (hitLayer) {
if (!depthSortDescendants)
return hitLayer;
candidateLayer = hitLayer;
}
// Next we want to see if the mouse pos is inside the child RenderObjects of the layer.
if (fgRect.intersects(hitTestArea) && isSelfPaintingLayer()) {
// Hit test with a temporary HitTestResult, because we only want to commit to 'result' if we know we're frontmost.
HitTestResult tempResult(result.point(), result.topPadding(), result.rightPadding(), result.bottomPadding(), result.leftPadding());
if (hitTestContents(request, tempResult, layerBounds, hitTestPoint, HitTestDescendants) &&
isHitCandidate(this, false, zOffsetForContentsPtr, unflattenedTransformState.get())) {
if (result.isRectBasedTest())
result.append(tempResult);
else
result = tempResult;
if (!depthSortDescendants)
return this;
// Foreground can depth-sort with descendant layers, so keep this as a candidate.
candidateLayer = this;
} else if (result.isRectBasedTest())
result.append(tempResult);
}
// Now check our negative z-index children.
hitLayer = hitTestList(m_negZOrderList, rootLayer, request, result, hitTestRect, hitTestPoint,
localTransformState.get(), zOffsetForDescendantsPtr, zOffset, unflattenedTransformState.get(), depthSortDescendants);
if (hitLayer) {
if (!depthSortDescendants)
return hitLayer;
candidateLayer = hitLayer;
}
// If we found a layer, return. Child layers, and foreground always render in front of background.
if (candidateLayer)
return candidateLayer;
if (bgRect.intersects(hitTestArea) && isSelfPaintingLayer()) {
HitTestResult tempResult(result.point(), result.topPadding(), result.rightPadding(), result.bottomPadding(), result.leftPadding());
if (hitTestContents(request, tempResult, layerBounds, hitTestPoint, HitTestSelf) &&
isHitCandidate(this, false, zOffsetForContentsPtr, unflattenedTransformState.get())) {
if (result.isRectBasedTest())
result.append(tempResult);
else
result = tempResult;
return this;
} else if (result.isRectBasedTest())
result.append(tempResult);
}
return 0;
}
bool RenderLayer::hitTestContents(const HitTestRequest& request, HitTestResult& result, const IntRect& layerBounds, const IntPoint& hitTestPoint, HitTestFilter hitTestFilter) const
{
if (!renderer()->hitTest(request, result, hitTestPoint,
layerBounds.x() - renderBoxX(),
layerBounds.y() - renderBoxY(),
hitTestFilter)) {
// It's wrong to set innerNode, but then claim that you didn't hit anything, unless it is
// a rect-based test.
ASSERT(!result.innerNode() || (result.isRectBasedTest() && result.rectBasedTestResult().size()));
return false;
}
// For positioned generated content, we might still not have a
// node by the time we get to the layer level, since none of
// the content in the layer has an element. So just walk up
// the tree.
if (!result.innerNode() || !result.innerNonSharedNode()) {
Node* e = enclosingElement();
if (!result.innerNode())
result.setInnerNode(e);
if (!result.innerNonSharedNode())
result.setInnerNonSharedNode(e);
}
return true;
}
RenderLayer* RenderLayer::hitTestList(Vector<RenderLayer*>* list, RenderLayer* rootLayer,
const HitTestRequest& request, HitTestResult& result,
const IntRect& hitTestRect, const IntPoint& hitTestPoint,
const HitTestingTransformState* transformState,
double* zOffsetForDescendants, double* zOffset,
const HitTestingTransformState* unflattenedTransformState,
bool depthSortDescendants)
{
if (!list)
return 0;
RenderLayer* resultLayer = 0;
for (int i = list->size() - 1; i >= 0; --i) {
RenderLayer* childLayer = list->at(i);
RenderLayer* hitLayer = 0;
HitTestResult tempResult(result.point(), result.topPadding(), result.rightPadding(), result.bottomPadding(), result.leftPadding());
if (childLayer->isPaginated())
hitLayer = hitTestPaginatedChildLayer(childLayer, rootLayer, request, tempResult, hitTestRect, hitTestPoint, transformState, zOffsetForDescendants);
else
hitLayer = childLayer->hitTestLayer(rootLayer, this, request, tempResult, hitTestRect, hitTestPoint, false, transformState, zOffsetForDescendants);
// If it a rect-based test, we can safely append the temporary result since it might had hit
// nodes but not necesserily had hitLayer set.
if (result.isRectBasedTest())
result.append(tempResult);
if (isHitCandidate(hitLayer, depthSortDescendants, zOffset, unflattenedTransformState)) {
resultLayer = hitLayer;
if (!result.isRectBasedTest())
result = tempResult;
if (!depthSortDescendants)
break;
}
}
return resultLayer;
}
RenderLayer* RenderLayer::hitTestPaginatedChildLayer(RenderLayer* childLayer, RenderLayer* rootLayer, const HitTestRequest& request, HitTestResult& result,
const IntRect& hitTestRect, const IntPoint& hitTestPoint, const HitTestingTransformState* transformState, double* zOffset)
{
Vector<RenderLayer*> columnLayers;
RenderLayer* ancestorLayer = isNormalFlowOnly() ? parent() : stackingContext();
for (RenderLayer* curr = childLayer->parent(); curr; curr = curr->parent()) {
if (curr->renderer()->hasColumns() && checkContainingBlockChainForPagination(childLayer->renderer(), curr->renderBox()))
columnLayers.append(curr);
if (curr == ancestorLayer)
break;
}
ASSERT(columnLayers.size());
return hitTestChildLayerColumns(childLayer, rootLayer, request, result, hitTestRect, hitTestPoint, transformState, zOffset,
columnLayers, columnLayers.size() - 1);
}
RenderLayer* RenderLayer::hitTestChildLayerColumns(RenderLayer* childLayer, RenderLayer* rootLayer, const HitTestRequest& request, HitTestResult& result,
const IntRect& hitTestRect, const IntPoint& hitTestPoint, const HitTestingTransformState* transformState, double* zOffset,
const Vector<RenderLayer*>& columnLayers, size_t columnIndex)
{
RenderBlock* columnBlock = toRenderBlock(columnLayers[columnIndex]->renderer());
ASSERT(columnBlock && columnBlock->hasColumns());
if (!columnBlock || !columnBlock->hasColumns())
return 0;
int layerX = 0;
int layerY = 0;
columnBlock->layer()->convertToLayerCoords(rootLayer, layerX, layerY);
ColumnInfo* colInfo = columnBlock->columnInfo();
int colCount = columnBlock->columnCount(colInfo);
// We have to go backwards from the last column to the first.
bool isHorizontal = columnBlock->style()->isHorizontalWritingMode();
int logicalLeft = columnBlock->logicalLeftOffsetForContent();
int currLogicalTopOffset = 0;
int i;
for (i = 0; i < colCount; i++) {
IntRect colRect = columnBlock->columnRectAt(colInfo, i);
int blockDelta = (isHorizontal ? colRect.height() : colRect.width());
if (columnBlock->style()->isFlippedBlocksWritingMode())
currLogicalTopOffset += blockDelta;
else
currLogicalTopOffset -= blockDelta;
}
for (i = colCount - 1; i >= 0; i--) {
// For each rect, we clip to the rect, and then we adjust our coords.
IntRect colRect = columnBlock->columnRectAt(colInfo, i);
columnBlock->flipForWritingMode(colRect);
int currLogicalLeftOffset = (isHorizontal ? colRect.x() : colRect.y()) - logicalLeft;
int blockDelta = (isHorizontal ? colRect.height() : colRect.width());
if (columnBlock->style()->isFlippedBlocksWritingMode())
currLogicalTopOffset -= blockDelta;
else
currLogicalTopOffset += blockDelta;
colRect.move(layerX, layerY);
IntRect localClipRect(hitTestRect);
localClipRect.intersect(colRect);
IntSize offset = isHorizontal ? IntSize(currLogicalLeftOffset, currLogicalTopOffset) : IntSize(currLogicalTopOffset, currLogicalLeftOffset);
if (!localClipRect.isEmpty() && localClipRect.intersects(result.rectForPoint(hitTestPoint))) {
RenderLayer* hitLayer = 0;
if (!columnIndex) {
// Apply a translation transform to change where the layer paints.
TransformationMatrix oldTransform;
bool oldHasTransform = childLayer->transform();
if (oldHasTransform)
oldTransform = *childLayer->transform();
TransformationMatrix newTransform(oldTransform);
newTransform.translateRight(offset.width(), offset.height());
childLayer->m_transform.set(new TransformationMatrix(newTransform));
hitLayer = childLayer->hitTestLayer(rootLayer, columnLayers[0], request, result, localClipRect, hitTestPoint, false, transformState, zOffset);
if (oldHasTransform)
childLayer->m_transform.set(new TransformationMatrix(oldTransform));
else
childLayer->m_transform.clear();
} else {
// Adjust the transform such that the renderer's upper left corner will be at (0,0) in user space.
// This involves subtracting out the position of the layer in our current coordinate space.
RenderLayer* nextLayer = columnLayers[columnIndex - 1];
RefPtr<HitTestingTransformState> newTransformState = nextLayer->createLocalTransformState(rootLayer, nextLayer, localClipRect, hitTestPoint, transformState);
newTransformState->translate(offset.width(), offset.height(), HitTestingTransformState::AccumulateTransform);
IntPoint localPoint = roundedIntPoint(newTransformState->mappedPoint());
IntRect localHitTestRect = newTransformState->mappedQuad().enclosingBoundingBox();
newTransformState->flatten();
hitLayer = hitTestChildLayerColumns(childLayer, columnLayers[columnIndex - 1], request, result, localHitTestRect, localPoint,
newTransformState.get(), zOffset, columnLayers, columnIndex - 1);
}
if (hitLayer)
return hitLayer;
}
}
return 0;
}
void RenderLayer::updateClipRects(const RenderLayer* rootLayer, OverlayScrollbarSizeRelevancy relevancy)
{
if (m_clipRects) {
ASSERT(rootLayer == m_clipRectsRoot);
return; // We have the correct cached value.
}
// For transformed layers, the root layer was shifted to be us, so there is no need to
// examine the parent. We want to cache clip rects with us as the root.
RenderLayer* parentLayer = rootLayer != this ? parent() : 0;
if (parentLayer)
parentLayer->updateClipRects(rootLayer, relevancy);
ClipRects clipRects;
calculateClipRects(rootLayer, clipRects, true, relevancy);
if (parentLayer && parentLayer->clipRects() && clipRects == *parentLayer->clipRects())
m_clipRects = parentLayer->clipRects();
else
m_clipRects = new (renderer()->renderArena()) ClipRects(clipRects);
m_clipRects->ref();
#ifndef NDEBUG
m_clipRectsRoot = rootLayer;
#endif
}
void RenderLayer::calculateClipRects(const RenderLayer* rootLayer, ClipRects& clipRects, bool useCached, OverlayScrollbarSizeRelevancy relevancy) const
{
if (!parent()) {
// The root layer's clip rect is always infinite.
clipRects.reset(PaintInfo::infiniteRect());
return;
}
// For transformed layers, the root layer was shifted to be us, so there is no need to
// examine the parent. We want to cache clip rects with us as the root.
RenderLayer* parentLayer = rootLayer != this ? parent() : 0;
// Ensure that our parent's clip has been calculated so that we can examine the values.
if (parentLayer) {
if (useCached && parentLayer->clipRects())
clipRects = *parentLayer->clipRects();
else
parentLayer->calculateClipRects(rootLayer, clipRects);
}
else
clipRects.reset(PaintInfo::infiniteRect());
// A fixed object is essentially the root of its containing block hierarchy, so when
// we encounter such an object, we reset our clip rects to the fixedClipRect.
if (renderer()->style()->position() == FixedPosition) {
clipRects.setPosClipRect(clipRects.fixedClipRect());
clipRects.setOverflowClipRect(clipRects.fixedClipRect());
clipRects.setFixed(true);
}
else if (renderer()->style()->position() == RelativePosition)
clipRects.setPosClipRect(clipRects.overflowClipRect());
else if (renderer()->style()->position() == AbsolutePosition)
clipRects.setOverflowClipRect(clipRects.posClipRect());
// Update the clip rects that will be passed to child layers.
if (renderer()->hasOverflowClip() || renderer()->hasClip()) {
// This layer establishes a clip of some kind.
int x = 0;
int y = 0;
convertToLayerCoords(rootLayer, x, y);
RenderView* view = renderer()->view();
ASSERT(view);
if (view && clipRects.fixed() && rootLayer->renderer() == view) {
x -= view->frameView()->scrollXForFixedPosition();
y -= view->frameView()->scrollYForFixedPosition();
}
if (renderer()->hasOverflowClip()) {
IntRect newOverflowClip = toRenderBox(renderer())->overflowClipRect(x, y, relevancy);
#if ENABLE(ANDROID_OVERFLOW_SCROLL)
if (hasOverflowScroll()) {
RenderBox* box = toRenderBox(renderer());
newOverflowClip =
IntRect(x + box->borderLeft(), y + box->borderTop(),
m_scrollWidth, m_scrollHeight);
}
#endif
clipRects.setOverflowClipRect(intersection(newOverflowClip, clipRects.overflowClipRect()));
if (renderer()->isPositioned() || renderer()->isRelPositioned())
clipRects.setPosClipRect(intersection(newOverflowClip, clipRects.posClipRect()));
}
if (renderer()->hasClip()) {
IntRect newPosClip = toRenderBox(renderer())->clipRect(x, y);
clipRects.setPosClipRect(intersection(newPosClip, clipRects.posClipRect()));
clipRects.setOverflowClipRect(intersection(newPosClip, clipRects.overflowClipRect()));
clipRects.setFixedClipRect(intersection(newPosClip, clipRects.fixedClipRect()));
}
}
}
void RenderLayer::parentClipRects(const RenderLayer* rootLayer, ClipRects& clipRects, bool temporaryClipRects, OverlayScrollbarSizeRelevancy relevancy) const
{
ASSERT(parent());
if (temporaryClipRects) {
parent()->calculateClipRects(rootLayer, clipRects, false, relevancy);
return;
}
parent()->updateClipRects(rootLayer, relevancy);
clipRects = *parent()->clipRects();
}
IntRect RenderLayer::backgroundClipRect(const RenderLayer* rootLayer, bool temporaryClipRects, OverlayScrollbarSizeRelevancy relevancy) const
{
IntRect backgroundRect;
if (parent()) {
ClipRects parentRects;
parentClipRects(rootLayer, parentRects, temporaryClipRects, relevancy);
backgroundRect = renderer()->style()->position() == FixedPosition ? parentRects.fixedClipRect() :
(renderer()->isPositioned() ? parentRects.posClipRect() :
parentRects.overflowClipRect());
RenderView* view = renderer()->view();
ASSERT(view);
if (view && parentRects.fixed() && rootLayer->renderer() == view)
backgroundRect.move(view->frameView()->scrollXForFixedPosition(), view->frameView()->scrollYForFixedPosition());
}
return backgroundRect;
}
void RenderLayer::calculateRects(const RenderLayer* rootLayer, const IntRect& paintDirtyRect, IntRect& layerBounds,
IntRect& backgroundRect, IntRect& foregroundRect, IntRect& outlineRect, bool temporaryClipRects,
OverlayScrollbarSizeRelevancy relevancy) const
{
if (rootLayer != this && parent()) {
backgroundRect = backgroundClipRect(rootLayer, temporaryClipRects, relevancy);
backgroundRect.intersect(paintDirtyRect);
} else
backgroundRect = paintDirtyRect;
foregroundRect = backgroundRect;
outlineRect = backgroundRect;
int x = 0;
int y = 0;
convertToLayerCoords(rootLayer, x, y);
layerBounds = IntRect(x, y, width(), height());
// Update the clip rects that will be passed to child layers.
if (renderer()->hasOverflowClip() || renderer()->hasClip()) {
// This layer establishes a clip of some kind.
#if ENABLE(ANDROID_OVERFLOW_SCROLL)
if (hasOverflowScroll()) {
// Use the entire foreground rectangle to record the contents.
RenderBox* box = toRenderBox(renderer());
foregroundRect =
IntRect(x + box->borderLeft(), y + box->borderTop(),
m_scrollWidth, m_scrollHeight);
} else
#endif
if (renderer()->hasOverflowClip())
foregroundRect.intersect(toRenderBox(renderer())->overflowClipRect(x, y, relevancy));
if (renderer()->hasClip()) {
// Clip applies to *us* as well, so go ahead and update the damageRect.
IntRect newPosClip = toRenderBox(renderer())->clipRect(x, y);
backgroundRect.intersect(newPosClip);
foregroundRect.intersect(newPosClip);
outlineRect.intersect(newPosClip);
}
// If we establish a clip at all, then go ahead and make sure our background
// rect is intersected with our layer's bounds.
// FIXME: This could be changed to just use generic visual overflow.
// See https://bugs.webkit.org/show_bug.cgi?id=37467 for more information.
if (const ShadowData* boxShadow = renderer()->style()->boxShadow()) {
IntRect overflow = layerBounds;
do {
if (boxShadow->style() == Normal) {
IntRect shadowRect = layerBounds;
shadowRect.move(boxShadow->x(), boxShadow->y());
shadowRect.inflate(boxShadow->blur() + boxShadow->spread());
overflow.unite(shadowRect);
}
boxShadow = boxShadow->next();
} while (boxShadow);
backgroundRect.intersect(overflow);
} else
backgroundRect.intersect(layerBounds);
}
}
IntRect RenderLayer::childrenClipRect() const
{
RenderView* renderView = renderer()->view();
RenderLayer* clippingRootLayer = clippingRoot();
IntRect layerBounds, backgroundRect, foregroundRect, outlineRect;
calculateRects(clippingRootLayer, renderView->documentRect(), layerBounds, backgroundRect, foregroundRect, outlineRect);
return clippingRootLayer->renderer()->localToAbsoluteQuad(FloatQuad(foregroundRect)).enclosingBoundingBox();
}
IntRect RenderLayer::selfClipRect() const
{
RenderView* renderView = renderer()->view();
RenderLayer* clippingRootLayer = clippingRoot();
IntRect layerBounds, backgroundRect, foregroundRect, outlineRect;
calculateRects(clippingRootLayer, renderView->documentRect(), layerBounds, backgroundRect, foregroundRect, outlineRect);
return clippingRootLayer->renderer()->localToAbsoluteQuad(FloatQuad(backgroundRect)).enclosingBoundingBox();
}
void RenderLayer::addBlockSelectionGapsBounds(const IntRect& bounds)
{
m_blockSelectionGapsBounds.unite(bounds);
}
void RenderLayer::clearBlockSelectionGapsBounds()
{
m_blockSelectionGapsBounds = IntRect();
for (RenderLayer* child = firstChild(); child; child = child->nextSibling())
child->clearBlockSelectionGapsBounds();
}
void RenderLayer::repaintBlockSelectionGaps()
{
for (RenderLayer* child = firstChild(); child; child = child->nextSibling())
child->repaintBlockSelectionGaps();
if (m_blockSelectionGapsBounds.isEmpty())
return;
IntRect rect = m_blockSelectionGapsBounds;
rect.move(-scrolledContentOffset());
if (renderer()->hasOverflowClip())
rect.intersect(toRenderBox(renderer())->overflowClipRect(0, 0));
if (renderer()->hasClip())
rect.intersect(toRenderBox(renderer())->clipRect(0, 0));
if (!rect.isEmpty())
renderer()->repaintRectangle(rect);
}
bool RenderLayer::intersectsDamageRect(const IntRect& layerBounds, const IntRect& damageRect, const RenderLayer* rootLayer) const
{
// Always examine the canvas and the root.
// FIXME: Could eliminate the isRoot() check if we fix background painting so that the RenderView
// paints the root's background.
if (renderer()->isRenderView() || renderer()->isRoot())
return true;
// If we aren't an inline flow, and our layer bounds do intersect the damage rect, then we
// can go ahead and return true.
RenderView* view = renderer()->view();
ASSERT(view);
if (view && !renderer()->isRenderInline()) {
IntRect b = layerBounds;
b.inflate(view->maximalOutlineSize());
if (b.intersects(damageRect))
return true;
}
// Otherwise we need to compute the bounding box of this single layer and see if it intersects
// the damage rect.
return boundingBox(rootLayer).intersects(damageRect);
}
IntRect RenderLayer::localBoundingBox() const
{
// There are three special cases we need to consider.
// (1) Inline Flows. For inline flows we will create a bounding box that fully encompasses all of the lines occupied by the
// inline. In other words, if some <span> wraps to three lines, we'll create a bounding box that fully encloses the
// line boxes of all three lines (including overflow on those lines).
// (2) Left/Top Overflow. The width/height of layers already includes right/bottom overflow. However, in the case of left/top
// overflow, we have to create a bounding box that will extend to include this overflow.
// (3) Floats. When a layer has overhanging floats that it paints, we need to make sure to include these overhanging floats
// as part of our bounding box. We do this because we are the responsible layer for both hit testing and painting those
// floats.
IntRect result;
if (renderer()->isRenderInline())
result = toRenderInline(renderer())->linesVisualOverflowBoundingBox();
else if (renderer()->isTableRow()) {
// Our bounding box is just the union of all of our cells' border/overflow rects.
for (RenderObject* child = renderer()->firstChild(); child; child = child->nextSibling()) {
if (child->isTableCell()) {
IntRect bbox = toRenderBox(child)->borderBoxRect();
result.unite(bbox);
IntRect overflowRect = renderBox()->visualOverflowRect();
if (bbox != overflowRect)
result.unite(overflowRect);
}
}
} else {
RenderBox* box = renderBox();
ASSERT(box);
if (box->hasMask())
result = box->maskClipRect();
else {
IntRect bbox = box->borderBoxRect();
result = bbox;
IntRect overflowRect = box->visualOverflowRect();
if (bbox != overflowRect)
result.unite(overflowRect);
}
}
RenderView* view = renderer()->view();
ASSERT(view);
if (view)
result.inflate(view->maximalOutlineSize()); // Used to apply a fudge factor to dirty-rect checks on blocks/tables.
return result;
}
IntRect RenderLayer::boundingBox(const RenderLayer* ancestorLayer) const
{
IntRect result = localBoundingBox();
if (renderer()->isBox())
renderBox()->flipForWritingMode(result);
else
renderer()->containingBlock()->flipForWritingMode(result);
int deltaX = 0, deltaY = 0;
convertToLayerCoords(ancestorLayer, deltaX, deltaY);
result.move(deltaX, deltaY);
return result;
}
IntRect RenderLayer::absoluteBoundingBox() const
{
return boundingBox(root());
}
void RenderLayer::clearClipRectsIncludingDescendants()
{
if (!m_clipRects)
return;
clearClipRects();
for (RenderLayer* l = firstChild(); l; l = l->nextSibling())
l->clearClipRectsIncludingDescendants();
}
void RenderLayer::clearClipRects()
{
if (m_clipRects) {
m_clipRects->deref(renderer()->renderArena());
m_clipRects = 0;
#ifndef NDEBUG
m_clipRectsRoot = 0;
#endif
}
}
#if USE(ACCELERATED_COMPOSITING)
RenderLayerBacking* RenderLayer::ensureBacking()
{
if (!m_backing)
m_backing.set(new RenderLayerBacking(this));
return m_backing.get();
}
void RenderLayer::clearBacking()
{
m_backing.clear();
}
bool RenderLayer::hasCompositedMask() const
{
return m_backing && m_backing->hasMaskLayer();
}
GraphicsLayer* RenderLayer::layerForHorizontalScrollbar() const
{
return m_backing ? m_backing->layerForHorizontalScrollbar() : 0;
}
GraphicsLayer* RenderLayer::layerForVerticalScrollbar() const
{
return m_backing ? m_backing->layerForVerticalScrollbar() : 0;
}
GraphicsLayer* RenderLayer::layerForScrollCorner() const
{
return m_backing ? m_backing->layerForScrollCorner() : 0;
}
#endif
bool RenderLayer::paintsWithTransform(PaintBehavior paintBehavior) const
{
#if USE(ACCELERATED_COMPOSITING)
bool paintsToWindow = !isComposited() || backing()->paintingGoesToWindow();
#else
bool paintsToWindow = true;
#endif
return transform() && ((paintBehavior & PaintBehaviorFlattenCompositingLayers) || paintsToWindow);
}
void RenderLayer::setParent(RenderLayer* parent)
{
if (parent == m_parent)
return;
#if USE(ACCELERATED_COMPOSITING)
if (m_parent && !renderer()->documentBeingDestroyed())
compositor()->layerWillBeRemoved(m_parent, this);
#endif
m_parent = parent;
#if USE(ACCELERATED_COMPOSITING)
if (m_parent && !renderer()->documentBeingDestroyed())
compositor()->layerWasAdded(m_parent, this);
#endif
}
static RenderObject* commonAncestor(RenderObject* obj1, RenderObject* obj2)
{
if (!obj1 || !obj2)
return 0;
for (RenderObject* currObj1 = obj1; currObj1; currObj1 = currObj1->hoverAncestor())
for (RenderObject* currObj2 = obj2; currObj2; currObj2 = currObj2->hoverAncestor())
if (currObj1 == currObj2)
return currObj1;
return 0;
}
void RenderLayer::updateHoverActiveState(const HitTestRequest& request, HitTestResult& result)
{
// We don't update :hover/:active state when the result is marked as readOnly.
if (request.readOnly())
return;
Document* doc = renderer()->document();
Node* activeNode = doc->activeNode();
if (activeNode && !request.active()) {
// We are clearing the :active chain because the mouse has been released.
for (RenderObject* curr = activeNode->renderer(); curr; curr = curr->parent()) {
if (curr->node() && !curr->isText())
curr->node()->clearInActiveChain();
}
doc->setActiveNode(0);
} else {
Node* newActiveNode = result.innerNode();
if (!activeNode && newActiveNode && request.active()) {
// We are setting the :active chain and freezing it. If future moves happen, they
// will need to reference this chain.
for (RenderObject* curr = newActiveNode->renderer(); curr; curr = curr->parent()) {
if (curr->node() && !curr->isText()) {
curr->node()->setInActiveChain();
}
}
doc->setActiveNode(newActiveNode);
}
}
// If the mouse is down and if this is a mouse move event, we want to restrict changes in
// :hover/:active to only apply to elements that are in the :active chain that we froze
// at the time the mouse went down.
bool mustBeInActiveChain = request.active() && request.mouseMove();
// Check to see if the hovered node has changed. If not, then we don't need to
// do anything.
RefPtr<Node> oldHoverNode = doc->hoverNode();
Node* newHoverNode = result.innerNode();
// Update our current hover node.
doc->setHoverNode(newHoverNode);
// We have two different objects. Fetch their renderers.
RenderObject* oldHoverObj = oldHoverNode ? oldHoverNode->renderer() : 0;
RenderObject* newHoverObj = newHoverNode ? newHoverNode->renderer() : 0;
// Locate the common ancestor render object for the two renderers.
RenderObject* ancestor = commonAncestor(oldHoverObj, newHoverObj);
Vector<RefPtr<Node>, 32> nodesToRemoveFromChain;
Vector<RefPtr<Node>, 32> nodesToAddToChain;
if (oldHoverObj != newHoverObj) {
// The old hover path only needs to be cleared up to (and not including) the common ancestor;
for (RenderObject* curr = oldHoverObj; curr && curr != ancestor; curr = curr->hoverAncestor()) {
if (curr->node() && !curr->isText() && (!mustBeInActiveChain || curr->node()->inActiveChain()))
nodesToRemoveFromChain.append(curr->node());
}
}
// Now set the hover state for our new object up to the root.
for (RenderObject* curr = newHoverObj; curr; curr = curr->hoverAncestor()) {
if (curr->node() && !curr->isText() && (!mustBeInActiveChain || curr->node()->inActiveChain()))
nodesToAddToChain.append(curr->node());
}
size_t removeCount = nodesToRemoveFromChain.size();
for (size_t i = 0; i < removeCount; ++i) {
nodesToRemoveFromChain[i]->setActive(false);
nodesToRemoveFromChain[i]->setHovered(false);
}
size_t addCount = nodesToAddToChain.size();
for (size_t i = 0; i < addCount; ++i) {
nodesToAddToChain[i]->setActive(request.active());
nodesToAddToChain[i]->setHovered(true);
}
}
// Helper for the sorting of layers by z-index.
static inline bool compareZIndex(RenderLayer* first, RenderLayer* second)
{
return first->zIndex() < second->zIndex();
}
void RenderLayer::dirtyZOrderLists()
{
if (m_posZOrderList)
m_posZOrderList->clear();
if (m_negZOrderList)
m_negZOrderList->clear();
m_zOrderListsDirty = true;
#if USE(ACCELERATED_COMPOSITING)
if (!renderer()->documentBeingDestroyed())
compositor()->setCompositingLayersNeedRebuild();
#endif
}
void RenderLayer::dirtyStackingContextZOrderLists()
{
RenderLayer* sc = stackingContext();
if (sc)
sc->dirtyZOrderLists();
}
void RenderLayer::dirtyNormalFlowList()
{
if (m_normalFlowList)
m_normalFlowList->clear();
m_normalFlowListDirty = true;
#if USE(ACCELERATED_COMPOSITING)
if (!renderer()->documentBeingDestroyed())
compositor()->setCompositingLayersNeedRebuild();
#endif
}
void RenderLayer::updateZOrderLists()
{
if (!isStackingContext() || !m_zOrderListsDirty)
return;
for (RenderLayer* child = firstChild(); child; child = child->nextSibling())
if (!m_reflection || reflectionLayer() != child)
child->collectLayers(m_posZOrderList, m_negZOrderList);
// Sort the two lists.
if (m_posZOrderList)
std::stable_sort(m_posZOrderList->begin(), m_posZOrderList->end(), compareZIndex);
if (m_negZOrderList)
std::stable_sort(m_negZOrderList->begin(), m_negZOrderList->end(), compareZIndex);
m_zOrderListsDirty = false;
}
void RenderLayer::updateNormalFlowList()
{
if (!m_normalFlowListDirty)
return;
for (RenderLayer* child = firstChild(); child; child = child->nextSibling()) {
// Ignore non-overflow layers and reflections.
if (child->isNormalFlowOnly() && (!m_reflection || reflectionLayer() != child)) {
if (!m_normalFlowList)
m_normalFlowList = new Vector<RenderLayer*>;
m_normalFlowList->append(child);
}
}
m_normalFlowListDirty = false;
}
void RenderLayer::collectLayers(Vector<RenderLayer*>*& posBuffer, Vector<RenderLayer*>*& negBuffer)
{
updateVisibilityStatus();
// Overflow layers are just painted by their enclosing layers, so they don't get put in zorder lists.
if ((m_hasVisibleContent || (m_hasVisibleDescendant && isStackingContext())) && !isNormalFlowOnly()) {
// Determine which buffer the child should be in.
Vector<RenderLayer*>*& buffer = (zIndex() >= 0) ? posBuffer : negBuffer;
// Create the buffer if it doesn't exist yet.
if (!buffer)
buffer = new Vector<RenderLayer*>;
// Append ourselves at the end of the appropriate buffer.
buffer->append(this);
}
// Recur into our children to collect more layers, but only if we don't establish
// a stacking context.
if (m_hasVisibleDescendant && !isStackingContext()) {
for (RenderLayer* child = firstChild(); child; child = child->nextSibling()) {
// Ignore reflections.
if (!m_reflection || reflectionLayer() != child)
child->collectLayers(posBuffer, negBuffer);
}
}
}
void RenderLayer::updateLayerListsIfNeeded()
{
updateZOrderLists();
updateNormalFlowList();
}
void RenderLayer::updateCompositingAndLayerListsIfNeeded()
{
#if USE(ACCELERATED_COMPOSITING)
if (compositor()->inCompositingMode()) {
if ((isStackingContext() && m_zOrderListsDirty) || m_normalFlowListDirty)
compositor()->updateCompositingLayers(CompositingUpdateOnPaitingOrHitTest, this);
return;
}
#endif
updateLayerListsIfNeeded();
}
void RenderLayer::repaintIncludingDescendants()
{
renderer()->repaint();
for (RenderLayer* curr = firstChild(); curr; curr = curr->nextSibling())
curr->repaintIncludingDescendants();
}
#if USE(ACCELERATED_COMPOSITING)
void RenderLayer::setBackingNeedsRepaint()
{
ASSERT(isComposited());
if (backing()->paintingGoesToWindow()) {
// If we're trying to repaint the placeholder document layer, propagate the
// repaint to the native view system.
RenderView* view = renderer()->view();
if (view)
view->repaintViewRectangle(absoluteBoundingBox());
} else
backing()->setContentsNeedDisplay();
}
void RenderLayer::setBackingNeedsRepaintInRect(const IntRect& r)
{
ASSERT(isComposited());
if (backing()->paintingGoesToWindow()) {
// If we're trying to repaint the placeholder document layer, propagate the
// repaint to the native view system.
IntRect absRect(r);
int x = 0;
int y = 0;
convertToLayerCoords(root(), x, y);
absRect.move(x, y);
RenderView* view = renderer()->view();
if (view)
view->repaintViewRectangle(absRect);
} else
backing()->setContentsNeedDisplayInRect(r);
}
// Since we're only painting non-composited layers, we know that they all share the same repaintContainer.
void RenderLayer::repaintIncludingNonCompositingDescendants(RenderBoxModelObject* repaintContainer)
{
renderer()->repaintUsingContainer(repaintContainer, renderer()->clippedOverflowRectForRepaint(repaintContainer));
for (RenderLayer* curr = firstChild(); curr; curr = curr->nextSibling()) {
if (!curr->isComposited())
curr->repaintIncludingNonCompositingDescendants(repaintContainer);
}
}
#endif
bool RenderLayer::shouldBeNormalFlowOnly() const
{
return (renderer()->hasOverflowClip()
|| renderer()->hasReflection()
|| renderer()->hasMask()
|| renderer()->isVideo()
|| renderer()->isEmbeddedObject()
|| renderer()->isApplet()
|| renderer()->isRenderIFrame()
|| renderer()->style()->specifiesColumns())
&& !renderer()->isPositioned()
&& !renderer()->isRelPositioned()
&& !renderer()->hasTransform()
&& !isTransparent();
}
bool RenderLayer::isSelfPaintingLayer() const
{
#if ENABLE(ANDROID_OVERFLOW_SCROLL)
if (hasOverflowScroll())
return true;
#endif
return !isNormalFlowOnly()
|| renderer()->hasReflection()
|| renderer()->hasMask()
|| renderer()->isTableRow()
|| renderer()->isVideo()
|| renderer()->isEmbeddedObject()
|| renderer()->isApplet()
|| renderer()->isRenderIFrame();
}
void RenderLayer::styleChanged(StyleDifference diff, const RenderStyle* oldStyle)
{
bool isNormalFlowOnly = shouldBeNormalFlowOnly();
if (isNormalFlowOnly != m_isNormalFlowOnly) {
m_isNormalFlowOnly = isNormalFlowOnly;
RenderLayer* p = parent();
if (p)
p->dirtyNormalFlowList();
dirtyStackingContextZOrderLists();
}
if (renderer()->style()->overflowX() == OMARQUEE && renderer()->style()->marqueeBehavior() != MNONE && renderer()->isBox()) {
if (!m_marquee)
m_marquee = new RenderMarquee(this);
m_marquee->updateMarqueeStyle();
}
else if (m_marquee) {
delete m_marquee;
m_marquee = 0;
}
if (!hasReflection() && m_reflection)
removeReflection();
else if (hasReflection()) {
if (!m_reflection)
createReflection();
updateReflectionStyle();
}
// FIXME: Need to detect a swap from custom to native scrollbars (and vice versa).
if (m_hBar)
m_hBar->styleChanged();
if (m_vBar)
m_vBar->styleChanged();
updateScrollCornerStyle();
updateResizerStyle();
#if USE(ACCELERATED_COMPOSITING)
updateTransform();
if (compositor()->updateLayerCompositingState(this))
compositor()->setCompositingLayersNeedRebuild();
else if (m_backing)
m_backing->updateGraphicsLayerGeometry();
else if (oldStyle && oldStyle->overflowX() != renderer()->style()->overflowX()) {
if (stackingContext()->hasCompositingDescendant())
compositor()->setCompositingLayersNeedRebuild();
}
if (m_backing && diff >= StyleDifferenceRepaint)
m_backing->setContentsNeedDisplay();
#else
UNUSED_PARAM(diff);
#endif
}
void RenderLayer::updateScrollCornerStyle()
{
RenderObject* actualRenderer = renderer()->node() ? renderer()->node()->shadowAncestorNode()->renderer() : renderer();
RefPtr<RenderStyle> corner = renderer()->hasOverflowClip() ? actualRenderer->getUncachedPseudoStyle(SCROLLBAR_CORNER, actualRenderer->style()) : 0;
if (corner) {
if (!m_scrollCorner) {
m_scrollCorner = new (renderer()->renderArena()) RenderScrollbarPart(renderer()->document());
m_scrollCorner->setParent(renderer());
}
m_scrollCorner->setStyle(corner.release());
} else if (m_scrollCorner) {
m_scrollCorner->destroy();
m_scrollCorner = 0;
}
}
void RenderLayer::updateResizerStyle()
{
RenderObject* actualRenderer = renderer()->node() ? renderer()->node()->shadowAncestorNode()->renderer() : renderer();
RefPtr<RenderStyle> resizer = renderer()->hasOverflowClip() ? actualRenderer->getUncachedPseudoStyle(RESIZER, actualRenderer->style()) : 0;
if (resizer) {
if (!m_resizer) {
m_resizer = new (renderer()->renderArena()) RenderScrollbarPart(renderer()->document());
m_resizer->setParent(renderer());
}
m_resizer->setStyle(resizer.release());
} else if (m_resizer) {
m_resizer->destroy();
m_resizer = 0;
}
}
RenderLayer* RenderLayer::reflectionLayer() const
{
return m_reflection ? m_reflection->layer() : 0;
}
void RenderLayer::createReflection()
{
ASSERT(!m_reflection);
m_reflection = new (renderer()->renderArena()) RenderReplica(renderer()->document());
m_reflection->setParent(renderer()); // We create a 1-way connection.
}
void RenderLayer::removeReflection()
{
if (!m_reflection->documentBeingDestroyed())
m_reflection->removeLayers(this);
m_reflection->setParent(0);
m_reflection->destroy();
m_reflection = 0;
}
void RenderLayer::updateReflectionStyle()
{
RefPtr<RenderStyle> newStyle = RenderStyle::create();
newStyle->inheritFrom(renderer()->style());
// Map in our transform.
TransformOperations transform;
switch (renderer()->style()->boxReflect()->direction()) {
case ReflectionBelow:
transform.operations().append(TranslateTransformOperation::create(Length(0, Fixed), Length(100., Percent), TransformOperation::TRANSLATE));
transform.operations().append(TranslateTransformOperation::create(Length(0, Fixed), renderer()->style()->boxReflect()->offset(), TransformOperation::TRANSLATE));
transform.operations().append(ScaleTransformOperation::create(1.0, -1.0, ScaleTransformOperation::SCALE));
break;
case ReflectionAbove:
transform.operations().append(ScaleTransformOperation::create(1.0, -1.0, ScaleTransformOperation::SCALE));
transform.operations().append(TranslateTransformOperation::create(Length(0, Fixed), Length(100., Percent), TransformOperation::TRANSLATE));
transform.operations().append(TranslateTransformOperation::create(Length(0, Fixed), renderer()->style()->boxReflect()->offset(), TransformOperation::TRANSLATE));
break;
case ReflectionRight:
transform.operations().append(TranslateTransformOperation::create(Length(100., Percent), Length(0, Fixed), TransformOperation::TRANSLATE));
transform.operations().append(TranslateTransformOperation::create(renderer()->style()->boxReflect()->offset(), Length(0, Fixed), TransformOperation::TRANSLATE));
transform.operations().append(ScaleTransformOperation::create(-1.0, 1.0, ScaleTransformOperation::SCALE));
break;
case ReflectionLeft:
transform.operations().append(ScaleTransformOperation::create(-1.0, 1.0, ScaleTransformOperation::SCALE));
transform.operations().append(TranslateTransformOperation::create(Length(100., Percent), Length(0, Fixed), TransformOperation::TRANSLATE));
transform.operations().append(TranslateTransformOperation::create(renderer()->style()->boxReflect()->offset(), Length(0, Fixed), TransformOperation::TRANSLATE));
break;
}
newStyle->setTransform(transform);
// Map in our mask.
newStyle->setMaskBoxImage(renderer()->style()->boxReflect()->mask());
m_reflection->setStyle(newStyle.release());
}
void RenderLayer::updateContentsScale(float scale)
{
#if USE(ACCELERATED_COMPOSITING)
if (m_backing)
m_backing->updateContentsScale(scale);
#endif
}
} // namespace WebCore
#ifndef NDEBUG
void showLayerTree(const WebCore::RenderLayer* layer)
{
if (!layer)
return;
if (WebCore::Frame* frame = layer->renderer()->frame()) {
WTF::String output = externalRepresentation(frame, WebCore::RenderAsTextShowAllLayers | WebCore::RenderAsTextShowLayerNesting | WebCore::RenderAsTextShowCompositedLayers | WebCore::RenderAsTextShowAddresses | WebCore::RenderAsTextShowIDAndClass | WebCore::RenderAsTextDontUpdateLayout | WebCore::RenderAsTextShowLayoutState);
fprintf(stderr, "%s\n", output.utf8().data());
}
}
void showLayerTree(const WebCore::RenderObject* renderer)
{
if (!renderer)
return;
showLayerTree(renderer->enclosingLayer());
}
#endif