blob: 1c66659ef4cb1902659c5aa9527b4fb1485621cd [file] [log] [blame]
/*
* Copyright (C) 1999 Lars Knoll (knoll@kde.org)
* (C) 1999 Antti Koivisto (koivisto@kde.org)
* (C) 2007 David Smith (catfish.man@gmail.com)
* Copyright (C) 2003, 2004, 2005, 2006, 2007, 2008, 2009, 2010, 2011 Apple Inc. All rights reserved.
* Copyright (C) Research In Motion Limited 2010. All rights reserved.
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Library General Public
* License as published by the Free Software Foundation; either
* version 2 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
* Library General Public License for more details.
*
* You should have received a copy of the GNU Library General Public License
* along with this library; see the file COPYING.LIB. If not, write to
* the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
* Boston, MA 02110-1301, USA.
*/
#include "sky/engine/config.h"
#include "sky/engine/core/rendering/RenderBlock.h"
#include "sky/engine/core/dom/Document.h"
#include "sky/engine/core/dom/Element.h"
#include "sky/engine/core/dom/StyleEngine.h"
#include "sky/engine/core/dom/shadow/ShadowRoot.h"
#include "sky/engine/core/editing/Editor.h"
#include "sky/engine/core/editing/FrameSelection.h"
#include "sky/engine/core/frame/FrameView.h"
#include "sky/engine/core/frame/LocalFrame.h"
#include "sky/engine/core/frame/Settings.h"
#include "sky/engine/core/page/Page.h"
#include "sky/engine/core/rendering/GraphicsContextAnnotator.h"
#include "sky/engine/core/rendering/HitTestLocation.h"
#include "sky/engine/core/rendering/HitTestResult.h"
#include "sky/engine/core/rendering/InlineIterator.h"
#include "sky/engine/core/rendering/InlineTextBox.h"
#include "sky/engine/core/rendering/PaintInfo.h"
#include "sky/engine/core/rendering/RenderFlexibleBox.h"
#include "sky/engine/core/rendering/RenderInline.h"
#include "sky/engine/core/rendering/RenderLayer.h"
#include "sky/engine/core/rendering/RenderObjectInlines.h"
#include "sky/engine/core/rendering/RenderParagraph.h"
#include "sky/engine/core/rendering/RenderView.h"
#include "sky/engine/core/rendering/style/RenderStyle.h"
#include "sky/engine/platform/geometry/FloatQuad.h"
#include "sky/engine/platform/geometry/TransformState.h"
#include "sky/engine/platform/graphics/GraphicsContextStateSaver.h"
#include "sky/engine/wtf/StdLibExtras.h"
#include "sky/engine/wtf/TemporaryChange.h"
using namespace WTF;
using namespace Unicode;
namespace blink {
struct SameSizeAsRenderBlock : public RenderBox {
RenderObjectChildList children;
RenderLineBoxList lineBoxes;
int pageLogicalOffset;
uint32_t bitfields;
};
COMPILE_ASSERT(sizeof(RenderBlock) == sizeof(SameSizeAsRenderBlock), RenderBlock_should_stay_small);
static TrackedDescendantsMap* gPositionedDescendantsMap = 0;
static TrackedDescendantsMap* gPercentHeightDescendantsMap = 0;
static TrackedContainerMap* gPositionedContainerMap = 0;
static TrackedContainerMap* gPercentHeightContainerMap = 0;
typedef WTF::HashSet<RenderBlock*> DelayedUpdateScrollInfoSet;
static int gDelayUpdateScrollInfo = 0;
static DelayedUpdateScrollInfoSet* gDelayedUpdateScrollInfoSet = 0;
RenderBlock::RenderBlock(ContainerNode* node)
: RenderBox(node)
, m_hasMarginBeforeQuirk(false)
, m_hasMarginAfterQuirk(false)
, m_beingDestroyed(false)
, m_hasBorderOrPaddingLogicalWidthChanged(false)
{
}
static void removeBlockFromDescendantAndContainerMaps(RenderBlock* block, TrackedDescendantsMap*& descendantMap, TrackedContainerMap*& containerMap)
{
if (OwnPtr<TrackedRendererListHashSet> descendantSet = descendantMap->take(block)) {
TrackedRendererListHashSet::iterator end = descendantSet->end();
for (TrackedRendererListHashSet::iterator descendant = descendantSet->begin(); descendant != end; ++descendant) {
TrackedContainerMap::iterator it = containerMap->find(*descendant);
ASSERT(it != containerMap->end());
if (it == containerMap->end())
continue;
HashSet<RenderBlock*>* containerSet = it->value.get();
ASSERT(containerSet->contains(block));
containerSet->remove(block);
if (containerSet->isEmpty())
containerMap->remove(it);
}
}
}
void RenderBlock::removeFromGlobalMaps()
{
if (gPercentHeightDescendantsMap)
removeBlockFromDescendantAndContainerMaps(this, gPercentHeightDescendantsMap, gPercentHeightContainerMap);
if (gPositionedDescendantsMap)
removeBlockFromDescendantAndContainerMaps(this, gPositionedDescendantsMap, gPositionedContainerMap);
}
RenderBlock::~RenderBlock()
{
#if !ENABLE(OILPAN)
removeFromGlobalMaps();
#endif
}
void RenderBlock::destroy()
{
RenderBox::destroy();
#if ENABLE(OILPAN)
removeFromGlobalMaps();
#endif
}
void RenderBlock::willBeDestroyed()
{
// Mark as being destroyed to avoid trouble with merges in removeChild().
m_beingDestroyed = true;
// Make sure to destroy anonymous children first while they are still connected to the rest of the tree, so that they will
// properly dirty line boxes that they are removed from. Effects that do :before/:after only on hover could crash otherwise.
children()->destroyLeftoverChildren();
if (!documentBeingDestroyed()) {
if (firstLineBox()) {
// We can't wait for RenderBox::destroy to clear the selection,
// because by then we will have nuked the line boxes.
// FIXME: The FrameSelection should be responsible for this when it
// is notified of DOM mutations.
if (isSelectionBorder())
view()->clearSelection();
// If we are an anonymous block, then our line boxes might have children
// that will outlast this block. In the non-anonymous block case those
// children will be destroyed by the time we return from this function.
if (isAnonymousBlock()) {
for (InlineFlowBox* box = firstLineBox(); box; box = box->nextLineBox()) {
while (InlineBox* childBox = box->firstChild())
childBox->remove();
}
}
} else if (parent())
parent()->dirtyLinesFromChangedChild(this);
}
m_lineBoxes.deleteLineBoxes();
if (UNLIKELY(gDelayedUpdateScrollInfoSet != 0))
gDelayedUpdateScrollInfoSet->remove(this);
RenderBox::willBeDestroyed();
}
void RenderBlock::styleWillChange(StyleDifference diff, const RenderStyle& newStyle)
{
RenderStyle* oldStyle = style();
setReplaced(newStyle.isDisplayInlineType());
if (oldStyle && parent()) {
bool oldStyleIsContainer = oldStyle->position() != StaticPosition || oldStyle->hasTransformRelatedProperty();
bool newStyleIsContainer = newStyle.position() != StaticPosition || newStyle.hasTransformRelatedProperty();
if (oldStyleIsContainer && !newStyleIsContainer) {
// Clear our positioned objects list. Our absolutely positioned descendants will be
// inserted into our containing block's positioned objects list during layout.
removePositionedObjects(0, NewContainingBlock);
} else if (!oldStyleIsContainer && newStyleIsContainer) {
// Remove our absolutely positioned descendants from their current containing block.
// They will be inserted into our positioned objects list during layout.
RenderObject* cb = parent();
while (cb && (cb->style()->position() == StaticPosition || (cb->isInline() && !cb->isReplaced())) && !cb->isRenderView()) {
if (cb->style()->position() == RelativePosition && cb->isInline() && !cb->isReplaced()) {
cb = cb->containingBlock();
break;
}
cb = cb->parent();
}
if (cb->isRenderBlock())
toRenderBlock(cb)->removePositionedObjects(this, NewContainingBlock);
}
}
RenderBox::styleWillChange(diff, newStyle);
}
static bool borderOrPaddingLogicalWidthChanged(const RenderStyle* oldStyle, const RenderStyle* newStyle)
{
return oldStyle->borderLeftWidth() != newStyle->borderLeftWidth()
|| oldStyle->borderRightWidth() != newStyle->borderRightWidth()
|| oldStyle->paddingLeft() != newStyle->paddingLeft()
|| oldStyle->paddingRight() != newStyle->paddingRight();
}
void RenderBlock::styleDidChange(StyleDifference diff, const RenderStyle* oldStyle)
{
RenderBox::styleDidChange(diff, oldStyle);
RenderStyle* newStyle = style();
propagateStyleToAnonymousChildren(true);
// It's possible for our border/padding to change, but for the overall logical width of the block to
// end up being the same. We keep track of this change so in layoutBlock, we can know to set relayoutChildren=true.
m_hasBorderOrPaddingLogicalWidthChanged = oldStyle && diff.needsFullLayout() && needsLayout() && borderOrPaddingLogicalWidthChanged(oldStyle, newStyle);
}
void RenderBlock::invalidateTreeIfNeeded(const PaintInvalidationState& paintInvalidationState)
{
// Note, we don't want to early out here using shouldCheckForInvalidationAfterLayout as
// we have to make sure we go through any positioned objects as they won't be seen in
// the normal tree walk.
RenderBox::invalidateTreeIfNeeded(paintInvalidationState);
// Take care of positioned objects. This is required as PaintInvalidationState keeps a single clip rect.
if (TrackedRendererListHashSet* positionedObjects = this->positionedObjects()) {
TrackedRendererListHashSet::iterator end = positionedObjects->end();
const RenderLayerModelObject& newPaintInvalidationContainer = *adjustCompositedContainerForSpecialAncestors(&paintInvalidationState.paintInvalidationContainer());
PaintInvalidationState childPaintInvalidationState(paintInvalidationState, *this, newPaintInvalidationContainer);
for (TrackedRendererListHashSet::iterator it = positionedObjects->begin(); it != end; ++it) {
RenderBox* box = *it;
// One of the renderers we're skipping over here may be the child's paint invalidation container,
// so we can't pass our own paint invalidation container along.
const RenderLayerModelObject& paintInvalidationContainerForChild = *box->containerForPaintInvalidation();
// If it's a new paint invalidation container, we won't have properly accumulated the offset into the
// PaintInvalidationState.
// FIXME: Teach PaintInvalidationState to handle this case. crbug.com/371485
if (&paintInvalidationContainerForChild != newPaintInvalidationContainer) {
ForceHorriblySlowRectMapping slowRectMapping(&childPaintInvalidationState);
PaintInvalidationState disabledPaintInvalidationState(childPaintInvalidationState, *this, paintInvalidationContainerForChild);
box->invalidateTreeIfNeeded(disabledPaintInvalidationState);
continue;
}
// If the positioned renderer is absolutely positioned and it is inside
// a relatively positioned inline element, we need to account for
// the inline elements position in PaintInvalidationState.
if (box->style()->position() == AbsolutePosition) {
RenderObject* container = box->container(&paintInvalidationContainerForChild, 0);
if (container->isRelPositioned() && container->isRenderInline()) {
// FIXME: We should be able to use PaintInvalidationState for this.
// Currently, we will place absolutely positioned elements inside
// relatively positioned inline blocks in the wrong location. crbug.com/371485
ForceHorriblySlowRectMapping slowRectMapping(&childPaintInvalidationState);
PaintInvalidationState disabledPaintInvalidationState(childPaintInvalidationState, *this, paintInvalidationContainerForChild);
box->invalidateTreeIfNeeded(disabledPaintInvalidationState);
continue;
}
}
box->invalidateTreeIfNeeded(childPaintInvalidationState);
}
}
}
void RenderBlock::addChildIgnoringAnonymousColumnBlocks(RenderObject* newChild, RenderObject* beforeChild)
{
if (beforeChild && beforeChild->parent() != this) {
RenderObject* beforeChildContainer = beforeChild->parent();
ASSERT(beforeChildContainer->parent() == this);
ASSERT(beforeChildContainer->isAnonymousBlock());
addChild(newChild, beforeChildContainer);
return;
}
// TODO(ojan): What should we do in this case? For now we insert an anonymous paragraph.
// This only happens if we have a text node directly inside a non-paragraph.
if (!isRenderParagraph() && newChild->isInline()) {
RenderBlock* newBox = createAnonymousBlock();
ASSERT(newBox->isRenderParagraph());
RenderBox::addChild(newBox, beforeChild);
newBox->addChild(newChild);
return;
}
RenderBox::addChild(newChild, beforeChild);
}
void RenderBlock::addChild(RenderObject* newChild, RenderObject* beforeChild)
{
addChildIgnoringAnonymousColumnBlocks(newChild, beforeChild);
}
void RenderBlock::deleteLineBoxTree()
{
ASSERT(!m_lineBoxes.firstLineBox());
}
void RenderBlock::removeChild(RenderObject* oldChild)
{
RenderBox::removeChild(oldChild);
// No need to waste time deleting the line box tree if we're getting destroyed.
if (documentBeingDestroyed())
return;
// If this was our last child be sure to clear out our line boxes.
if (!firstChild() && isRenderParagraph())
deleteLineBoxTree();
}
void RenderBlock::startDelayUpdateScrollInfo()
{
if (gDelayUpdateScrollInfo == 0) {
ASSERT(!gDelayedUpdateScrollInfoSet);
gDelayedUpdateScrollInfoSet = new DelayedUpdateScrollInfoSet;
}
ASSERT(gDelayedUpdateScrollInfoSet);
++gDelayUpdateScrollInfo;
}
void RenderBlock::finishDelayUpdateScrollInfo()
{
--gDelayUpdateScrollInfo;
ASSERT(gDelayUpdateScrollInfo >= 0);
if (gDelayUpdateScrollInfo == 0) {
ASSERT(gDelayedUpdateScrollInfoSet);
OwnPtr<DelayedUpdateScrollInfoSet> infoSet(adoptPtr(gDelayedUpdateScrollInfoSet));
gDelayedUpdateScrollInfoSet = 0;
for (DelayedUpdateScrollInfoSet::iterator it = infoSet->begin(); it != infoSet->end(); ++it) {
RenderBlock* block = *it;
if (block->hasOverflowClip()) {
block->layer()->scrollableArea()->updateAfterLayout();
}
}
}
}
void RenderBlock::updateScrollInfoAfterLayout()
{
if (hasOverflowClip()) {
if (gDelayUpdateScrollInfo)
gDelayedUpdateScrollInfoSet->add(this);
else
layer()->scrollableArea()->updateAfterLayout();
}
}
void RenderBlock::layout()
{
// Table cells call layoutBlock directly, so don't add any logic here. Put code into
// layoutBlock().
layoutBlock(false);
// It's safe to check for control clip here, since controls can never be table cells.
// If we have a lightweight clip, there can never be any overflow from children.
if (hasControlClip() && m_overflow)
clearLayoutOverflow();
invalidateBackgroundObscurationStatus();
}
bool RenderBlock::widthAvailableToChildrenHasChanged()
{
bool widthAvailableToChildrenHasChanged = m_hasBorderOrPaddingLogicalWidthChanged;
m_hasBorderOrPaddingLogicalWidthChanged = false;
// If we use border-box sizing, have percentage padding, and our parent has changed width then the width available to our children has changed even
// though our own width has remained the same.
widthAvailableToChildrenHasChanged |= style()->boxSizing() == BORDER_BOX && needsPreferredWidthsRecalculation() && view()->layoutState()->containingBlockLogicalWidthChanged();
return widthAvailableToChildrenHasChanged;
}
bool RenderBlock::updateLogicalWidthAndColumnWidth()
{
LayoutUnit oldWidth = logicalWidth();
updateLogicalWidth();
return oldWidth != logicalWidth() || widthAvailableToChildrenHasChanged();
}
void RenderBlock::layoutBlock(bool)
{
ASSERT_NOT_REACHED();
clearNeedsLayout();
}
void RenderBlock::addOverflowFromChildren()
{
for (RenderBox* child = firstChildBox(); child; child = child->nextSiblingBox()) {
if (!child->isFloatingOrOutOfFlowPositioned())
addOverflowFromChild(child);
}
}
void RenderBlock::computeOverflow(LayoutUnit oldClientAfterEdge, bool)
{
m_overflow.clear();
// Add overflow from children.
addOverflowFromChildren();
// Add in the overflow from positioned objects.
addOverflowFromPositionedObjects();
if (hasOverflowClip()) {
// When we have overflow clip, propagate the original spillout since it will include collapsed bottom margins
// and bottom padding. Set the axis we don't care about to be 1, since we want this overflow to always
// be considered reachable.
LayoutRect clientRect(paddingBoxRect());
LayoutRect rectToApply;
rectToApply = LayoutRect(clientRect.x(), clientRect.y(), 1, std::max<LayoutUnit>(0, oldClientAfterEdge - clientRect.y()));
addLayoutOverflow(rectToApply);
if (hasRenderOverflow())
m_overflow->setLayoutClientAfterEdge(oldClientAfterEdge);
}
addVisualEffectOverflow();
}
void RenderBlock::addOverflowFromPositionedObjects()
{
TrackedRendererListHashSet* positionedDescendants = positionedObjects();
if (!positionedDescendants)
return;
RenderBox* positionedObject;
TrackedRendererListHashSet::iterator end = positionedDescendants->end();
for (TrackedRendererListHashSet::iterator it = positionedDescendants->begin(); it != end; ++it) {
positionedObject = *it;
addOverflowFromChild(positionedObject, LayoutSize(positionedObject->x(), positionedObject->y()));
}
}
bool RenderBlock::createsBlockFormattingContext() const
{
return isInlineBlock() || isFloatingOrOutOfFlowPositioned() || hasOverflowClip() || isFlexItem() || isDocumentElement();
}
void RenderBlock::updateBlockChildDirtyBitsBeforeLayout(bool relayoutChildren, RenderBox* child)
{
// FIXME: Technically percentage height objects only need a relayout if their percentage isn't going to be turned into
// an auto value. Add a method to determine this, so that we can avoid the relayout.
if (relayoutChildren || (child->hasRelativeLogicalHeight() && !isRenderView()))
child->setChildNeedsLayout(MarkOnlyThis);
// If relayoutChildren is set and the child has percentage padding or an embedded content box, we also need to invalidate the childs pref widths.
if (relayoutChildren && child->needsPreferredWidthsRecalculation())
child->setPreferredLogicalWidthsDirty(MarkOnlyThis);
}
void RenderBlock::simplifiedNormalFlowLayout()
{
for (RenderBox* box = firstChildBox(); box; box = box->nextSiblingBox()) {
if (!box->isOutOfFlowPositioned())
box->layoutIfNeeded();
}
}
bool RenderBlock::simplifiedLayout()
{
// Check if we need to do a full layout.
if (normalChildNeedsLayout() || selfNeedsLayout())
return false;
// Check that we actually need to do a simplified layout.
if (!posChildNeedsLayout() && !(needsSimplifiedNormalFlowLayout() || needsPositionedMovementLayout()))
return false;
{
// LayoutState needs this deliberate scope to pop before paint invalidation.
LayoutState state(*this, locationOffset());
if (needsPositionedMovementLayout() && !tryLayoutDoingPositionedMovementOnly())
return false;
// Lay out positioned descendants or objects that just need to recompute overflow.
if (needsSimplifiedNormalFlowLayout())
simplifiedNormalFlowLayout();
if (posChildNeedsLayout() || needsPositionedMovementLayout())
layoutPositionedObjects(false, needsPositionedMovementLayout() ? ForcedLayoutAfterContainingBlockMoved : DefaultLayout);
// Recompute our overflow information.
// FIXME: We could do better here by computing a temporary overflow object from layoutPositionedObjects and only
// updating our overflow if we either used to have overflow or if the new temporary object has overflow.
// For now just always recompute overflow. This is no worse performance-wise than the old code that called rightmostPosition and
// lowestPosition on every relayout so it's not a regression.
// computeOverflow expects the bottom edge before we clamp our height. Since this information isn't available during
// simplifiedLayout, we cache the value in m_overflow.
LayoutUnit oldClientAfterEdge = hasRenderOverflow() ? m_overflow->layoutClientAfterEdge() : clientLogicalBottom();
computeOverflow(oldClientAfterEdge, true);
}
updateLayerTransformAfterLayout();
updateScrollInfoAfterLayout();
clearNeedsLayout();
return true;
}
LayoutUnit RenderBlock::marginIntrinsicLogicalWidthForChild(RenderBox* child) const
{
// A margin has three types: fixed, percentage, and auto (variable).
// Auto and percentage margins become 0 when computing min/max width.
// Fixed margins can be added in as is.
Length marginLeft = child->style()->marginStartUsing(style());
Length marginRight = child->style()->marginEndUsing(style());
LayoutUnit margin = 0;
if (marginLeft.isFixed())
margin += marginLeft.value();
if (marginRight.isFixed())
margin += marginRight.value();
return margin;
}
void RenderBlock::layoutPositionedObjects(bool relayoutChildren, PositionedLayoutBehavior info)
{
TrackedRendererListHashSet* positionedDescendants = positionedObjects();
if (!positionedDescendants)
return;
RenderBox* r;
TrackedRendererListHashSet::iterator end = positionedDescendants->end();
for (TrackedRendererListHashSet::iterator it = positionedDescendants->begin(); it != end; ++it) {
r = *it;
// FIXME: this should only be set from clearNeedsLayout crbug.com/361250
r->setLayoutDidGetCalled(true);
SubtreeLayoutScope layoutScope(*r);
// When a non-positioned block element moves, it may have positioned children that are implicitly positioned relative to the
// non-positioned block. Rather than trying to detect all of these movement cases, we just always lay out positioned
// objects that are positioned implicitly like this. Such objects are rare, and so in typical DHTML menu usage (where everything is
// positioned explicitly) this should not incur a performance penalty.
if (relayoutChildren || (r->style()->hasStaticBlockPosition() && r->parent() != this))
layoutScope.setChildNeedsLayout(r);
// If relayoutChildren is set and the child has percentage padding or an embedded content box, we also need to invalidate the childs pref widths.
if (relayoutChildren && r->needsPreferredWidthsRecalculation())
r->setPreferredLogicalWidthsDirty(MarkOnlyThis);
if (info == ForcedLayoutAfterContainingBlockMoved)
r->setNeedsPositionedMovementLayout();
r->layoutIfNeeded();
}
}
void RenderBlock::markPositionedObjectsForLayout()
{
if (TrackedRendererListHashSet* positionedDescendants = positionedObjects()) {
TrackedRendererListHashSet::iterator end = positionedDescendants->end();
for (TrackedRendererListHashSet::iterator it = positionedDescendants->begin(); it != end; ++it)
(*it)->setChildNeedsLayout();
}
}
void RenderBlock::paint(PaintInfo& paintInfo, const LayoutPoint& paintOffset)
{
ANNOTATE_GRAPHICS_CONTEXT(paintInfo, this);
LayoutPoint adjustedPaintOffset = paintOffset + location();
PaintPhase phase = paintInfo.phase;
LayoutRect overflowBox;
// Check if we need to do anything at all.
// FIXME: Could eliminate the isDocumentElement() check if we fix background painting so that the RenderView
// paints the root's background.
if (!isDocumentElement()) {
overflowBox = visualOverflowRect();
overflowBox.moveBy(adjustedPaintOffset);
if (!overflowBox.intersects(paintInfo.rect))
return;
}
// There are some cases where not all clipped visual overflow is accounted for.
// FIXME: reduce the number of such cases.
ContentsClipBehavior contentsClipBehavior = ForceContentsClip;
if (hasOverflowClip() && !hasControlClip() && !(shouldPaintSelectionGaps() && phase == PaintPhaseForeground) && !hasCaret())
contentsClipBehavior = SkipContentsClipIfPossible;
bool pushedClip = pushContentsClip(paintInfo, adjustedPaintOffset, contentsClipBehavior);
{
paintObject(paintInfo, adjustedPaintOffset);
}
if (pushedClip)
popContentsClip(paintInfo, phase, adjustedPaintOffset);
// Our scrollbar widgets paint exactly when we tell them to, so that they work properly with
// z-index. We paint after we painted the background/border, so that the scrollbars will
// sit above the background/border.
if (hasOverflowClip() && (phase == PaintPhaseBlockBackground || phase == PaintPhaseChildBlockBackground) && paintInfo.shouldPaintWithinRoot(this))
layer()->scrollableArea()->paintOverflowControls(paintInfo.context, roundedIntPoint(adjustedPaintOffset), paintInfo.rect, false /* paintingOverlayControls */);
}
void RenderBlock::paintContents(PaintInfo& paintInfo, const LayoutPoint& paintOffset)
{
PaintPhase newPhase = (paintInfo.phase == PaintPhaseChildOutlines) ? PaintPhaseOutline : paintInfo.phase;
newPhase = (newPhase == PaintPhaseChildBlockBackgrounds) ? PaintPhaseChildBlockBackground : newPhase;
// We don't paint our own background, but we do let the kids paint their backgrounds.
PaintInfo paintInfoForChild(paintInfo);
paintInfoForChild.phase = newPhase;
paintInfoForChild.updatePaintingRootForChildren(this);
paintChildren(paintInfoForChild, paintOffset);
}
void RenderBlock::paintChildren(PaintInfo& paintInfo, const LayoutPoint& paintOffset)
{
for (RenderBox* child = firstChildBox(); child; child = child->nextSiblingBox())
paintChild(child, paintInfo, paintOffset);
}
void RenderBlock::paintChild(RenderBox* child, PaintInfo& paintInfo, const LayoutPoint& paintOffset)
{
if (!child->hasSelfPaintingLayer())
child->paint(paintInfo, paintOffset);
}
void RenderBlock::paintChildAsInlineBlock(RenderBox* child, PaintInfo& paintInfo, const LayoutPoint& paintOffset)
{
if (!child->hasSelfPaintingLayer())
paintAsInlineBlock(child, paintInfo, paintOffset);
}
void RenderBlock::paintAsInlineBlock(RenderObject* renderer, PaintInfo& paintInfo, const LayoutPoint& childPoint)
{
if (paintInfo.phase != PaintPhaseForeground && paintInfo.phase != PaintPhaseSelection)
return;
// Paint all phases atomically, as though the element established its own
// stacking context. (See Appendix E.2, section 7.2.1.4 on
// inline block/table/replaced elements in the CSS2.1 specification.)
// This is also used by other elements (e.g. flex items).
bool preservePhase = paintInfo.phase == PaintPhaseSelection;
PaintInfo info(paintInfo);
info.phase = preservePhase ? paintInfo.phase : PaintPhaseBlockBackground;
renderer->paint(info, childPoint);
if (!preservePhase) {
info.phase = PaintPhaseChildBlockBackgrounds;
renderer->paint(info, childPoint);
info.phase = PaintPhaseForeground;
renderer->paint(info, childPoint);
info.phase = PaintPhaseOutline;
renderer->paint(info, childPoint);
}
}
static inline bool hasCursorCaret(const FrameSelection& selection, const RenderBlock* block)
{
return selection.caretRenderer() == block && selection.hasEditableStyle();
}
static inline bool hasDragCaret(const DragCaretController& dragCaretController, const RenderBlock* block)
{
return dragCaretController.caretRenderer() == block && dragCaretController.isContentEditable();
}
bool RenderBlock::hasCaret() const
{
return hasCursorCaret(frame()->selection(), this)
|| hasDragCaret(frame()->page()->dragCaretController(), this);
}
void RenderBlock::paintCarets(PaintInfo& paintInfo, const LayoutPoint& paintOffset)
{
FrameSelection& selection = frame()->selection();
if (hasCursorCaret(selection, this)) {
selection.paintCaret(paintInfo.context, paintOffset, paintInfo.rect);
}
DragCaretController& dragCaretController = frame()->page()->dragCaretController();
if (hasDragCaret(dragCaretController, this)) {
dragCaretController.paintDragCaret(frame(), paintInfo.context, paintOffset, paintInfo.rect);
}
}
void RenderBlock::paintObject(PaintInfo& paintInfo, const LayoutPoint& paintOffset)
{
PaintPhase paintPhase = paintInfo.phase;
LayoutPoint scrolledOffset = paintOffset;
if (hasOverflowClip())
scrolledOffset.move(-scrolledContentOffset());
if (paintPhase == PaintPhaseBlockBackground || paintPhase == PaintPhaseChildBlockBackground) {
if (hasBoxDecorationBackground())
paintBoxDecorationBackground(paintInfo, paintOffset);
}
if (paintPhase == PaintPhaseMask) {
paintMask(paintInfo, paintOffset);
return;
}
// We're done. We don't bother painting any children.
if (paintPhase == PaintPhaseBlockBackground)
return;
if (paintPhase != PaintPhaseSelfOutline) {
// Avoid painting descendants of the root element when stylesheets haven't loaded. This eliminates FOUC.
// It's ok not to draw, because later on, when all the stylesheets do load, styleResolverChanged() on the Document
// will do a full paint invalidation.
if (!document().didLayoutWithPendingStylesheets() || isRenderView())
paintContents(paintInfo, scrolledOffset);
}
paintSelection(paintInfo, scrolledOffset); // Fill in gaps in selection on lines and between blocks.
if ((paintPhase == PaintPhaseOutline || paintPhase == PaintPhaseSelfOutline)
&& style()->hasOutline() && !style()->outlineStyleIsAuto()) {
paintOutline(paintInfo, LayoutRect(paintOffset, size()));
}
// If the caret's node's render object's containing block is this block, and the paint action is PaintPhaseForeground,
// then paint the caret.
if (paintPhase == PaintPhaseForeground)
paintCarets(paintInfo, paintOffset);
}
bool RenderBlock::shouldPaintSelectionGaps() const
{
return selectionState() != SelectionNone && isSelectionRoot();
}
bool RenderBlock::isSelectionRoot() const
{
ASSERT(node() || isAnonymous());
if (isDocumentElement() || hasOverflowClip()
|| isPositioned()
|| isInlineBlock()
|| hasTransform() || hasMask()
|| isFlexItem())
return true;
if (view() && view()->selectionStart()) {
Node* startElement = view()->selectionStart()->node();
if (startElement && startElement->rootEditableElement() == node())
return true;
}
return false;
}
GapRects RenderBlock::selectionGapRectsForPaintInvalidation(const RenderLayerModelObject* paintInvalidationContainer)
{
ASSERT(!needsLayout());
if (!shouldPaintSelectionGaps())
return GapRects();
TransformState transformState(TransformState::ApplyTransformDirection, FloatPoint());
mapLocalToContainer(paintInvalidationContainer, transformState, ApplyContainerFlip | UseTransforms);
LayoutPoint offsetFromPaintInvalidationContainer = roundedLayoutPoint(transformState.mappedPoint());
if (hasOverflowClip())
offsetFromPaintInvalidationContainer -= scrolledContentOffset();
LayoutUnit lastTop = 0;
LayoutUnit lastLeft = logicalLeftSelectionOffset(this, lastTop);
LayoutUnit lastRight = logicalRightSelectionOffset(this, lastTop);
return selectionGaps(this, offsetFromPaintInvalidationContainer, IntSize(), lastTop, lastLeft, lastRight);
}
void RenderBlock::paintSelection(PaintInfo& paintInfo, const LayoutPoint& paintOffset)
{
if (shouldPaintSelectionGaps() && paintInfo.phase == PaintPhaseForeground) {
LayoutUnit lastTop = 0;
LayoutUnit lastLeft = logicalLeftSelectionOffset(this, lastTop);
LayoutUnit lastRight = logicalRightSelectionOffset(this, lastTop);
GraphicsContextStateSaver stateSaver(*paintInfo.context);
LayoutRect gapRectsBounds = selectionGaps(this, paintOffset, LayoutSize(), lastTop, lastLeft, lastRight, &paintInfo);
if (!gapRectsBounds.isEmpty()) {
RenderLayer* layer = enclosingLayer();
gapRectsBounds.moveBy(-paintOffset);
if (!hasLayer()) {
LayoutRect localBounds(gapRectsBounds);
gapRectsBounds = localToContainerQuad(FloatRect(localBounds), layer->renderer()).enclosingBoundingBox();
if (layer->renderer()->hasOverflowClip())
gapRectsBounds.move(layer->renderBox()->scrolledContentOffset());
}
layer->addBlockSelectionGapsBounds(gapRectsBounds);
}
}
}
static void clipOutPositionedObjects(const PaintInfo* paintInfo, const LayoutPoint& offset, TrackedRendererListHashSet* positionedObjects)
{
if (!positionedObjects)
return;
TrackedRendererListHashSet::const_iterator end = positionedObjects->end();
for (TrackedRendererListHashSet::const_iterator it = positionedObjects->begin(); it != end; ++it) {
RenderBox* r = *it;
paintInfo->context->clipOut(IntRect(offset.x() + r->x(), offset.y() + r->y(), r->width(), r->height()));
}
}
LayoutUnit RenderBlock::blockDirectionOffset(const LayoutSize& offsetFromBlock) const
{
// FIXME(sky): Remove
return offsetFromBlock.height();
}
LayoutUnit RenderBlock::inlineDirectionOffset(const LayoutSize& offsetFromBlock) const
{
// FIXME(sky): Remove
return offsetFromBlock.width();
}
LayoutRect RenderBlock::logicalRectToPhysicalRect(const LayoutPoint& rootBlockPhysicalPosition, const LayoutRect& logicalRect)
{
LayoutRect result = logicalRect;
result.moveBy(rootBlockPhysicalPosition);
return result;
}
GapRects RenderBlock::selectionGaps(RenderBlock* rootBlock, const LayoutPoint& rootBlockPhysicalPosition, const LayoutSize& offsetFromRootBlock,
LayoutUnit& lastLogicalTop, LayoutUnit& lastLogicalLeft, LayoutUnit& lastLogicalRight, const PaintInfo* paintInfo)
{
// IMPORTANT: Callers of this method that intend for painting to happen need to do a save/restore.
// Clip out floating and positioned objects when painting selection gaps.
if (paintInfo) {
// Note that we don't clip out overflow for positioned objects. We just stick to the border box.
LayoutRect blockRect(offsetFromRootBlock.width(), offsetFromRootBlock.height(), width(), height());
blockRect.moveBy(rootBlockPhysicalPosition);
clipOutPositionedObjects(paintInfo, blockRect.location(), positionedObjects());
if (isDocumentElement()) // The <body> must make sure to examine its containingBlock's positioned objects.
for (RenderBlock* cb = containingBlock(); cb && !cb->isRenderView(); cb = cb->containingBlock())
clipOutPositionedObjects(paintInfo, LayoutPoint(cb->x(), cb->y()), cb->positionedObjects());
}
// FIXME: overflow: auto/scroll regions need more math here, since painting in the border box is different from painting in the padding box (one is scrolled, the other is
// fixed).
GapRects result;
if (!isRenderBlockFlow()) // FIXME: Make multi-column selection gap filling work someday.
return result;
if (hasTransform()) {
// FIXME: We should learn how to gap fill multiple columns and transforms eventually.
lastLogicalTop = rootBlock->blockDirectionOffset(offsetFromRootBlock) + logicalHeight();
lastLogicalLeft = logicalLeftSelectionOffset(rootBlock, logicalHeight());
lastLogicalRight = logicalRightSelectionOffset(rootBlock, logicalHeight());
return result;
}
if (isRenderParagraph())
result = toRenderParagraph(this)->inlineSelectionGaps(rootBlock, rootBlockPhysicalPosition, offsetFromRootBlock, lastLogicalTop, lastLogicalLeft, lastLogicalRight, paintInfo);
else
result = blockSelectionGaps(rootBlock, rootBlockPhysicalPosition, offsetFromRootBlock, lastLogicalTop, lastLogicalLeft, lastLogicalRight, paintInfo);
// Go ahead and fill the vertical gap all the way to the bottom of our block if the selection extends past our block.
if (rootBlock == this && (selectionState() != SelectionBoth && selectionState() != SelectionEnd))
result.uniteCenter(blockSelectionGap(rootBlock, rootBlockPhysicalPosition, offsetFromRootBlock, lastLogicalTop, lastLogicalLeft, lastLogicalRight,
logicalHeight(), paintInfo));
return result;
}
GapRects RenderBlock::blockSelectionGaps(RenderBlock* rootBlock, const LayoutPoint& rootBlockPhysicalPosition, const LayoutSize& offsetFromRootBlock,
LayoutUnit& lastLogicalTop, LayoutUnit& lastLogicalLeft, LayoutUnit& lastLogicalRight, const PaintInfo* paintInfo)
{
GapRects result;
// Go ahead and jump right to the first block child that contains some selected objects.
RenderBox* curr;
for (curr = firstChildBox(); curr && curr->selectionState() == SelectionNone; curr = curr->nextSiblingBox()) { }
for (bool sawSelectionEnd = false; curr && !sawSelectionEnd; curr = curr->nextSiblingBox()) {
SelectionState childState = curr->selectionState();
if (childState == SelectionBoth || childState == SelectionEnd)
sawSelectionEnd = true;
if (curr->isFloatingOrOutOfFlowPositioned())
continue; // We must be a normal flow object in order to even be considered.
if (curr->isRelPositioned() && curr->hasLayer()) {
// If the relposition offset is anything other than 0, then treat this just like an absolute positioned element.
// Just disregard it completely.
LayoutSize relOffset = curr->layer()->offsetForInFlowPosition();
if (relOffset.width() || relOffset.height())
continue;
}
bool paintsOwnSelection = curr->shouldPaintSelectionGaps();
bool fillBlockGaps = paintsOwnSelection || (curr->canBeSelectionLeaf() && childState != SelectionNone);
if (fillBlockGaps) {
// We need to fill the vertical gap above this object.
if (childState == SelectionEnd || childState == SelectionInside)
// Fill the gap above the object.
result.uniteCenter(blockSelectionGap(rootBlock, rootBlockPhysicalPosition, offsetFromRootBlock, lastLogicalTop, lastLogicalLeft, lastLogicalRight,
curr->logicalTop(), paintInfo));
// Only fill side gaps for objects that paint their own selection if we know for sure the selection is going to extend all the way *past*
// our object. We know this if the selection did not end inside our object.
if (paintsOwnSelection && (childState == SelectionStart || sawSelectionEnd))
childState = SelectionNone;
// Fill side gaps on this object based off its state.
bool leftGap, rightGap;
getSelectionGapInfo(childState, leftGap, rightGap);
if (leftGap)
result.uniteLeft(logicalLeftSelectionGap(rootBlock, rootBlockPhysicalPosition, offsetFromRootBlock, this, curr->logicalLeft(), curr->logicalTop(), curr->logicalHeight(), paintInfo));
if (rightGap)
result.uniteRight(logicalRightSelectionGap(rootBlock, rootBlockPhysicalPosition, offsetFromRootBlock, this, curr->logicalRight(), curr->logicalTop(), curr->logicalHeight(), paintInfo));
// Update lastLogicalTop to be just underneath the object. lastLogicalLeft and lastLogicalRight extend as far as
// they can without bumping into floating or positioned objects. Ideally they will go right up
// to the border of the root selection block.
lastLogicalTop = rootBlock->blockDirectionOffset(offsetFromRootBlock) + curr->logicalBottom();
lastLogicalLeft = logicalLeftSelectionOffset(rootBlock, curr->logicalBottom());
lastLogicalRight = logicalRightSelectionOffset(rootBlock, curr->logicalBottom());
} else if (childState != SelectionNone)
// We must be a block that has some selected object inside it. Go ahead and recur.
result.unite(toRenderBlock(curr)->selectionGaps(rootBlock, rootBlockPhysicalPosition, LayoutSize(offsetFromRootBlock.width() + curr->x(), offsetFromRootBlock.height() + curr->y()),
lastLogicalTop, lastLogicalLeft, lastLogicalRight, paintInfo));
}
return result;
}
IntRect alignSelectionRectToDevicePixels(LayoutRect& rect)
{
LayoutUnit roundedX = rect.x().round();
return IntRect(roundedX, rect.y().round(),
(rect.maxX() - roundedX).round(),
snapSizeToPixel(rect.height(), rect.y()));
}
LayoutRect RenderBlock::blockSelectionGap(RenderBlock* rootBlock, const LayoutPoint& rootBlockPhysicalPosition, const LayoutSize& offsetFromRootBlock,
LayoutUnit lastLogicalTop, LayoutUnit lastLogicalLeft, LayoutUnit lastLogicalRight, LayoutUnit logicalBottom, const PaintInfo* paintInfo)
{
LayoutUnit logicalTop = lastLogicalTop;
LayoutUnit logicalHeight = rootBlock->blockDirectionOffset(offsetFromRootBlock) + logicalBottom - logicalTop;
if (logicalHeight <= 0)
return LayoutRect();
// Get the selection offsets for the bottom of the gap
LayoutUnit logicalLeft = std::max(lastLogicalLeft, logicalLeftSelectionOffset(rootBlock, logicalBottom));
LayoutUnit logicalRight = std::min(lastLogicalRight, logicalRightSelectionOffset(rootBlock, logicalBottom));
LayoutUnit logicalWidth = logicalRight - logicalLeft;
if (logicalWidth <= 0)
return LayoutRect();
LayoutRect gapRect = rootBlock->logicalRectToPhysicalRect(rootBlockPhysicalPosition, LayoutRect(logicalLeft, logicalTop, logicalWidth, logicalHeight));
if (paintInfo)
paintInfo->context->fillRect(alignSelectionRectToDevicePixels(gapRect), selectionBackgroundColor());
return gapRect;
}
LayoutRect RenderBlock::logicalLeftSelectionGap(RenderBlock* rootBlock, const LayoutPoint& rootBlockPhysicalPosition, const LayoutSize& offsetFromRootBlock,
RenderObject* selObj, LayoutUnit logicalLeft, LayoutUnit logicalTop, LayoutUnit logicalHeight, const PaintInfo* paintInfo)
{
LayoutUnit rootBlockLogicalTop = rootBlock->blockDirectionOffset(offsetFromRootBlock) + logicalTop;
LayoutUnit rootBlockLogicalLeft = std::max(logicalLeftSelectionOffset(rootBlock, logicalTop), logicalLeftSelectionOffset(rootBlock, logicalTop + logicalHeight));
LayoutUnit rootBlockLogicalRight = std::min(rootBlock->inlineDirectionOffset(offsetFromRootBlock) + logicalLeft, std::min(logicalRightSelectionOffset(rootBlock, logicalTop), logicalRightSelectionOffset(rootBlock, logicalTop + logicalHeight)));
LayoutUnit rootBlockLogicalWidth = rootBlockLogicalRight - rootBlockLogicalLeft;
if (rootBlockLogicalWidth <= 0)
return LayoutRect();
LayoutRect gapRect = rootBlock->logicalRectToPhysicalRect(rootBlockPhysicalPosition, LayoutRect(rootBlockLogicalLeft, rootBlockLogicalTop, rootBlockLogicalWidth, logicalHeight));
if (paintInfo)
paintInfo->context->fillRect(alignSelectionRectToDevicePixels(gapRect), selObj->selectionBackgroundColor());
return gapRect;
}
LayoutRect RenderBlock::logicalRightSelectionGap(RenderBlock* rootBlock, const LayoutPoint& rootBlockPhysicalPosition, const LayoutSize& offsetFromRootBlock,
RenderObject* selObj, LayoutUnit logicalRight, LayoutUnit logicalTop, LayoutUnit logicalHeight, const PaintInfo* paintInfo)
{
LayoutUnit rootBlockLogicalTop = rootBlock->blockDirectionOffset(offsetFromRootBlock) + logicalTop;
LayoutUnit rootBlockLogicalLeft = std::max(rootBlock->inlineDirectionOffset(offsetFromRootBlock) + logicalRight, max(logicalLeftSelectionOffset(rootBlock, logicalTop), logicalLeftSelectionOffset(rootBlock, logicalTop + logicalHeight)));
LayoutUnit rootBlockLogicalRight = std::min(logicalRightSelectionOffset(rootBlock, logicalTop), logicalRightSelectionOffset(rootBlock, logicalTop + logicalHeight));
LayoutUnit rootBlockLogicalWidth = rootBlockLogicalRight - rootBlockLogicalLeft;
if (rootBlockLogicalWidth <= 0)
return LayoutRect();
LayoutRect gapRect = rootBlock->logicalRectToPhysicalRect(rootBlockPhysicalPosition, LayoutRect(rootBlockLogicalLeft, rootBlockLogicalTop, rootBlockLogicalWidth, logicalHeight));
if (paintInfo)
paintInfo->context->fillRect(alignSelectionRectToDevicePixels(gapRect), selObj->selectionBackgroundColor());
return gapRect;
}
void RenderBlock::getSelectionGapInfo(SelectionState state, bool& leftGap, bool& rightGap)
{
bool ltr = style()->isLeftToRightDirection();
leftGap = (state == RenderObject::SelectionInside) ||
(state == RenderObject::SelectionEnd && ltr) ||
(state == RenderObject::SelectionStart && !ltr);
rightGap = (state == RenderObject::SelectionInside) ||
(state == RenderObject::SelectionStart && ltr) ||
(state == RenderObject::SelectionEnd && !ltr);
}
LayoutUnit RenderBlock::logicalLeftSelectionOffset(RenderBlock* rootBlock, LayoutUnit position)
{
// The border can potentially be further extended by our containingBlock().
if (rootBlock != this)
return containingBlock()->logicalLeftSelectionOffset(rootBlock, position + logicalTop());
return logicalLeftOffsetForContent();
}
LayoutUnit RenderBlock::logicalRightSelectionOffset(RenderBlock* rootBlock, LayoutUnit position)
{
// The border can potentially be further extended by our containingBlock().
if (rootBlock != this)
return containingBlock()->logicalRightSelectionOffset(rootBlock, position + logicalTop());
return logicalRightOffsetForContent();
}
RenderBlock* RenderBlock::blockBeforeWithinSelectionRoot(LayoutSize& offset) const
{
if (isSelectionRoot())
return 0;
const RenderObject* object = this;
RenderObject* sibling;
do {
sibling = object->previousSibling();
while (sibling && (!sibling->isRenderBlock() || toRenderBlock(sibling)->isSelectionRoot()))
sibling = sibling->previousSibling();
offset -= LayoutSize(toRenderBlock(object)->logicalLeft(), toRenderBlock(object)->logicalTop());
object = object->parent();
} while (!sibling && object && object->isRenderBlock() && !toRenderBlock(object)->isSelectionRoot());
if (!sibling)
return 0;
RenderBlock* beforeBlock = toRenderBlock(sibling);
offset += LayoutSize(beforeBlock->logicalLeft(), beforeBlock->logicalTop());
RenderObject* child = beforeBlock->lastChild();
while (child && child->isRenderBlock()) {
beforeBlock = toRenderBlock(child);
offset += LayoutSize(beforeBlock->logicalLeft(), beforeBlock->logicalTop());
child = beforeBlock->lastChild();
}
return beforeBlock;
}
void RenderBlock::setSelectionState(SelectionState state)
{
RenderBox::setSelectionState(state);
if (inlineBoxWrapper() && canUpdateSelectionOnRootLineBoxes())
inlineBoxWrapper()->root().setHasSelectedChildren(state != SelectionNone);
}
void RenderBlock::insertIntoTrackedRendererMaps(RenderBox* descendant, TrackedDescendantsMap*& descendantsMap, TrackedContainerMap*& containerMap)
{
if (!descendantsMap) {
descendantsMap = new TrackedDescendantsMap;
containerMap = new TrackedContainerMap;
}
TrackedRendererListHashSet* descendantSet = descendantsMap->get(this);
if (!descendantSet) {
descendantSet = new TrackedRendererListHashSet;
descendantsMap->set(this, adoptPtr(descendantSet));
}
bool added = descendantSet->add(descendant).isNewEntry;
if (!added) {
ASSERT(containerMap->get(descendant));
ASSERT(containerMap->get(descendant)->contains(this));
return;
}
HashSet<RenderBlock*>* containerSet = containerMap->get(descendant);
if (!containerSet) {
containerSet = new HashSet<RenderBlock*>;
containerMap->set(descendant, adoptPtr(containerSet));
}
ASSERT(!containerSet->contains(this));
containerSet->add(this);
}
void RenderBlock::removeFromTrackedRendererMaps(RenderBox* descendant, TrackedDescendantsMap*& descendantsMap, TrackedContainerMap*& containerMap)
{
if (!descendantsMap)
return;
OwnPtr<HashSet<RenderBlock*> > containerSet = containerMap->take(descendant);
if (!containerSet)
return;
HashSet<RenderBlock*>::iterator end = containerSet->end();
for (HashSet<RenderBlock*>::iterator it = containerSet->begin(); it != end; ++it) {
RenderBlock* container = *it;
// FIXME: Disabling this assert temporarily until we fix the layout
// bugs associated with positioned objects not properly cleared from
// their ancestor chain before being moved. See webkit bug 93766.
// ASSERT(descendant->isDescendantOf(container));
TrackedDescendantsMap::iterator descendantsMapIterator = descendantsMap->find(container);
ASSERT(descendantsMapIterator != descendantsMap->end());
if (descendantsMapIterator == descendantsMap->end())
continue;
TrackedRendererListHashSet* descendantSet = descendantsMapIterator->value.get();
ASSERT(descendantSet->contains(descendant));
descendantSet->remove(descendant);
if (descendantSet->isEmpty())
descendantsMap->remove(descendantsMapIterator);
}
}
TrackedRendererListHashSet* RenderBlock::positionedObjects() const
{
if (gPositionedDescendantsMap)
return gPositionedDescendantsMap->get(this);
return 0;
}
void RenderBlock::insertPositionedObject(RenderBox* o)
{
ASSERT(!isAnonymousBlock());
insertIntoTrackedRendererMaps(o, gPositionedDescendantsMap, gPositionedContainerMap);
}
void RenderBlock::removePositionedObject(RenderBox* o)
{
removeFromTrackedRendererMaps(o, gPositionedDescendantsMap, gPositionedContainerMap);
}
void RenderBlock::removePositionedObjects(RenderBlock* o, ContainingBlockState containingBlockState)
{
TrackedRendererListHashSet* positionedDescendants = positionedObjects();
if (!positionedDescendants)
return;
RenderBox* r;
TrackedRendererListHashSet::iterator end = positionedDescendants->end();
Vector<RenderBox*, 16> deadObjects;
for (TrackedRendererListHashSet::iterator it = positionedDescendants->begin(); it != end; ++it) {
r = *it;
if (!o || r->isDescendantOf(o)) {
if (containingBlockState == NewContainingBlock)
r->setChildNeedsLayout(MarkOnlyThis);
// It is parent blocks job to add positioned child to positioned objects list of its containing block
// Parent layout needs to be invalidated to ensure this happens.
RenderObject* p = r->parent();
while (p && !p->isRenderBlock())
p = p->parent();
if (p)
p->setChildNeedsLayout();
deadObjects.append(r);
}
}
for (unsigned i = 0; i < deadObjects.size(); i++)
removePositionedObject(deadObjects.at(i));
}
void RenderBlock::addPercentHeightDescendant(RenderBox* descendant)
{
insertIntoTrackedRendererMaps(descendant, gPercentHeightDescendantsMap, gPercentHeightContainerMap);
}
void RenderBlock::removePercentHeightDescendant(RenderBox* descendant)
{
removeFromTrackedRendererMaps(descendant, gPercentHeightDescendantsMap, gPercentHeightContainerMap);
}
TrackedRendererListHashSet* RenderBlock::percentHeightDescendants() const
{
return gPercentHeightDescendantsMap ? gPercentHeightDescendantsMap->get(this) : 0;
}
bool RenderBlock::hasPercentHeightContainerMap()
{
return gPercentHeightContainerMap;
}
bool RenderBlock::hasPercentHeightDescendant(RenderBox* descendant)
{
// We don't null check gPercentHeightContainerMap since the caller
// already ensures this and we need to call this function on every
// descendant in clearPercentHeightDescendantsFrom().
ASSERT(gPercentHeightContainerMap);
return gPercentHeightContainerMap->contains(descendant);
}
void RenderBlock::dirtyForLayoutFromPercentageHeightDescendants(SubtreeLayoutScope& layoutScope)
{
if (!gPercentHeightDescendantsMap)
return;
TrackedRendererListHashSet* descendants = gPercentHeightDescendantsMap->get(this);
if (!descendants)
return;
TrackedRendererListHashSet::iterator end = descendants->end();
for (TrackedRendererListHashSet::iterator it = descendants->begin(); it != end; ++it) {
RenderBox* box = *it;
while (box != this) {
if (box->normalChildNeedsLayout())
break;
layoutScope.setChildNeedsLayout(box);
box = box->containingBlock();
ASSERT(box);
if (!box)
break;
}
}
}
void RenderBlock::removePercentHeightDescendantIfNeeded(RenderBox* descendant)
{
// We query the map directly, rather than looking at style's
// logicalHeight()/logicalMinHeight()/logicalMaxHeight() since those
// can change with writing mode/directional changes.
if (!hasPercentHeightContainerMap())
return;
if (!hasPercentHeightDescendant(descendant))
return;
removePercentHeightDescendant(descendant);
}
void RenderBlock::clearPercentHeightDescendantsFrom(RenderBox* parent)
{
ASSERT(gPercentHeightContainerMap);
for (RenderObject* curr = parent->slowFirstChild(); curr; curr = curr->nextInPreOrder(parent)) {
if (!curr->isBox())
continue;
RenderBox* box = toRenderBox(curr);
if (!hasPercentHeightDescendant(box))
continue;
removePercentHeightDescendant(box);
}
}
LayoutUnit RenderBlock::textIndentOffset() const
{
LayoutUnit cw = 0;
if (style()->textIndent().isPercent())
cw = containingBlock()->availableLogicalWidth();
return minimumValueForLength(style()->textIndent(), cw);
}
bool RenderBlock::nodeAtPoint(const HitTestRequest& request, HitTestResult& result, const HitTestLocation& locationInContainer, const LayoutPoint& accumulatedOffset, HitTestAction hitTestAction)
{
LayoutPoint adjustedLocation(accumulatedOffset + location());
LayoutSize localOffset = toLayoutSize(adjustedLocation);
if (!isRenderView()) {
// Check if we need to do anything at all.
// If we have clipping, then we can't have any spillout.
LayoutRect overflowBox = hasOverflowClip() ? borderBoxRect() : visualOverflowRect();
overflowBox.moveBy(adjustedLocation);
if (!locationInContainer.intersects(overflowBox))
return false;
}
if (style()->clipPath()) {
switch (style()->clipPath()->type()) {
case ClipPathOperation::SHAPE: {
ShapeClipPathOperation* clipPath = toShapeClipPathOperation(style()->clipPath());
// FIXME: handle marginBox etc.
if (!clipPath->path(borderBoxRect()).contains(locationInContainer.point() - localOffset, clipPath->windRule()))
return false;
break;
}
case ClipPathOperation::REFERENCE:
break;
}
}
// If we have clipping, then we can't have any spillout.
bool useOverflowClip = hasOverflowClip() && !hasSelfPaintingLayer();
bool useClip = (hasControlClip() || useOverflowClip);
bool checkChildren = !useClip;
if (!checkChildren) {
if (hasControlClip()) {
checkChildren = locationInContainer.intersects(controlClipRect(adjustedLocation));
} else {
LayoutRect clipRect = overflowClipRect(adjustedLocation);
if (style()->hasBorderRadius())
checkChildren = locationInContainer.intersects(style()->getRoundedBorderFor(clipRect));
else
checkChildren = locationInContainer.intersects(clipRect);
}
}
if (checkChildren) {
// Hit test descendants first.
LayoutSize scrolledOffset(localOffset);
if (hasOverflowClip())
scrolledOffset -= scrolledContentOffset();
if (hitTestContents(request, result, locationInContainer, toLayoutPoint(scrolledOffset), hitTestAction)) {
updateHitTestResult(result, locationInContainer.point() - localOffset);
return true;
}
}
// Check if the point is outside radii.
if (style()->hasBorderRadius()) {
LayoutRect borderRect = borderBoxRect();
borderRect.moveBy(adjustedLocation);
RoundedRect border = style()->getRoundedBorderFor(borderRect);
if (!locationInContainer.intersects(border))
return false;
}
// Now hit test our background
if (hitTestAction == HitTestBlockBackground || hitTestAction == HitTestChildBlockBackground) {
LayoutRect boundsRect(adjustedLocation, size());
if (visibleToHitTestRequest(request) && locationInContainer.intersects(boundsRect)) {
updateHitTestResult(result, locationInContainer.point() - localOffset);
if (!result.addNodeToRectBasedTestResult(node(), request, locationInContainer, boundsRect))
return true;
}
}
return false;
}
bool RenderBlock::hitTestContents(const HitTestRequest& request, HitTestResult& result, const HitTestLocation& locationInContainer, const LayoutPoint& accumulatedOffset, HitTestAction hitTestAction)
{
// Hit test our children.
HitTestAction childHitTest = hitTestAction;
if (hitTestAction == HitTestChildBlockBackgrounds)
childHitTest = HitTestChildBlockBackground;
for (RenderBox* child = lastChildBox(); child; child = child->previousSiblingBox()) {
if (!child->hasSelfPaintingLayer() && child->nodeAtPoint(request, result, locationInContainer, accumulatedOffset, childHitTest))
return true;
}
return false;
}
Position RenderBlock::positionForBox(InlineBox *box, bool start) const
{
if (!box)
return Position();
if (!box->renderer().node())
return createLegacyEditingPosition(node(), start ? caretMinOffset() : caretMaxOffset());
if (!box->isInlineTextBox())
return createLegacyEditingPosition(box->renderer().node(), start ? box->renderer().caretMinOffset() : box->renderer().caretMaxOffset());
InlineTextBox* textBox = toInlineTextBox(box);
return createLegacyEditingPosition(box->renderer().node(), start ? textBox->start() : textBox->start() + textBox->len());
}
static inline bool isEditingBoundary(RenderObject* ancestor, RenderObject* child)
{
ASSERT(!ancestor || ancestor->node());
ASSERT(child && child->node());
return !ancestor || !ancestor->parent() || (ancestor->hasLayer() && ancestor->parent()->isRenderView())
|| ancestor->node()->hasEditableStyle() == child->node()->hasEditableStyle();
}
// FIXME: This function should go on RenderObject as an instance method. Then
// all cases in which positionForPoint recurs could call this instead to
// prevent crossing editable boundaries. This would require many tests.
static PositionWithAffinity positionForPointRespectingEditingBoundaries(RenderBlock* parent, RenderBox* child, const LayoutPoint& pointInParentCoordinates)
{
LayoutPoint childLocation = child->location();
if (child->isRelPositioned())
childLocation += child->offsetForInFlowPosition();
// FIXME: This is wrong if the child's writing-mode is different from the parent's.
LayoutPoint pointInChildCoordinates(toLayoutPoint(pointInParentCoordinates - childLocation));
// If this is an anonymous renderer, we just recur normally
Node* childNode = child->node();
if (!childNode)
return child->positionForPoint(pointInChildCoordinates);
// Otherwise, first make sure that the editability of the parent and child agree.
// If they don't agree, then we return a visible position just before or after the child
RenderObject* ancestor = parent;
while (ancestor && !ancestor->node())
ancestor = ancestor->parent();
// If we can't find an ancestor to check editability on, or editability is unchanged, we recur like normal
if (isEditingBoundary(ancestor, child))
return child->positionForPoint(pointInChildCoordinates);
// Otherwise return before or after the child, depending on if the click was to the logical left or logical right of the child
LayoutUnit childMiddle = parent->logicalWidthForChild(child) / 2;
LayoutUnit logicalLeft = pointInChildCoordinates.x();
if (logicalLeft < childMiddle)
return ancestor->createPositionWithAffinity(childNode->nodeIndex(), DOWNSTREAM);
return ancestor->createPositionWithAffinity(childNode->nodeIndex() + 1, UPSTREAM);
}
PositionWithAffinity RenderBlock::positionForPointWithInlineChildren(const LayoutPoint& pointInLogicalContents)
{
ASSERT(isRenderParagraph());
if (!firstRootBox())
return createPositionWithAffinity(0, DOWNSTREAM);
// look for the closest line box in the root box which is at the passed-in y coordinate
InlineBox* closestBox = 0;
RootInlineBox* firstRootBoxWithChildren = 0;
RootInlineBox* lastRootBoxWithChildren = 0;
for (RootInlineBox* root = firstRootBox(); root; root = root->nextRootBox()) {
if (!root->firstLeafChild())
continue;
if (!firstRootBoxWithChildren)
firstRootBoxWithChildren = root;
if (root->isFirstAfterPageBreak() && (pointInLogicalContents.y() < root->lineTopWithLeading()))
break;
lastRootBoxWithChildren = root;
// check if this root line box is located at this y coordinate
if (pointInLogicalContents.y() < root->selectionBottom()) {
closestBox = root->closestLeafChildForLogicalLeftPosition(pointInLogicalContents.x());
if (closestBox)
break;
}
}
bool moveCaretToBoundary = document().frame()->editor().behavior().shouldMoveCaretToHorizontalBoundaryWhenPastTopOrBottom();
if (!moveCaretToBoundary && !closestBox && lastRootBoxWithChildren) {
// y coordinate is below last root line box, pretend we hit it
closestBox = lastRootBoxWithChildren->closestLeafChildForLogicalLeftPosition(pointInLogicalContents.x());
}
if (closestBox) {
if (moveCaretToBoundary) {
LayoutUnit firstRootBoxWithChildrenTop = std::min<LayoutUnit>(firstRootBoxWithChildren->selectionTop(), firstRootBoxWithChildren->logicalTop());
if (pointInLogicalContents.y() < firstRootBoxWithChildrenTop) {
InlineBox* box = firstRootBoxWithChildren->firstLeafChild();
if (box->isLineBreak()) {
if (InlineBox* newBox = box->nextLeafChildIgnoringLineBreak())
box = newBox;
}
// y coordinate is above first root line box, so return the start of the first
return PositionWithAffinity(positionForBox(box, true), DOWNSTREAM);
}
}
// pass the box a top position that is inside it
LayoutPoint point(pointInLogicalContents.x(), closestBox->root().blockDirectionPointInLine());
if (closestBox->renderer().isReplaced())
return positionForPointRespectingEditingBoundaries(this, &toRenderBox(closestBox->renderer()), point);
return closestBox->renderer().positionForPoint(point);
}
if (lastRootBoxWithChildren) {
// We hit this case for Mac behavior when the Y coordinate is below the last box.
ASSERT(moveCaretToBoundary);
InlineBox* logicallyLastBox;
if (lastRootBoxWithChildren->getLogicalEndBoxWithNode(logicallyLastBox))
return PositionWithAffinity(positionForBox(logicallyLastBox, false), DOWNSTREAM);
}
// Can't reach this. We have a root line box, but it has no kids.
// FIXME: This should ASSERT_NOT_REACHED(), but clicking on placeholder text
// seems to hit this code path.
return createPositionWithAffinity(0, DOWNSTREAM);
}
static inline bool isChildHitTestCandidate(RenderBox* box)
{
return box->height() && !box->isFloatingOrOutOfFlowPositioned();
}
PositionWithAffinity RenderBlock::positionForPoint(const LayoutPoint& point)
{
if (isReplaced()) {
// FIXME: This seems wrong when the object's writing-mode doesn't match the line's writing-mode.
LayoutUnit pointLogicalLeft = point.x();
LayoutUnit pointLogicalTop = point.y();
if (pointLogicalLeft < 0)
return createPositionWithAffinity(caretMinOffset(), DOWNSTREAM);
if (pointLogicalLeft >= logicalWidth())
return createPositionWithAffinity(caretMaxOffset(), DOWNSTREAM);
if (pointLogicalTop < 0)
return createPositionWithAffinity(caretMinOffset(), DOWNSTREAM);
if (pointLogicalTop >= logicalHeight())
return createPositionWithAffinity(caretMaxOffset(), DOWNSTREAM);
}
LayoutPoint pointInContents = point;
offsetForContents(pointInContents);
LayoutPoint pointInLogicalContents(pointInContents);
if (isRenderParagraph())
return positionForPointWithInlineChildren(pointInLogicalContents);
RenderBox* lastCandidateBox = lastChildBox();
while (lastCandidateBox && !isChildHitTestCandidate(lastCandidateBox))
lastCandidateBox = lastCandidateBox->previousSiblingBox();
if (lastCandidateBox) {
if (pointInLogicalContents.y() > logicalTopForChild(lastCandidateBox)
|| (pointInLogicalContents.y() == logicalTopForChild(lastCandidateBox)))
return positionForPointRespectingEditingBoundaries(this, lastCandidateBox, pointInContents);
for (RenderBox* childBox = firstChildBox(); childBox; childBox = childBox->nextSiblingBox()) {
if (!isChildHitTestCandidate(childBox))
continue;
LayoutUnit childLogicalBottom = logicalTopForChild(childBox) + logicalHeightForChild(childBox);
// We hit child if our click is above the bottom of its padding box (like IE6/7 and FF3).
if (isChildHitTestCandidate(childBox) && (pointInLogicalContents.y() < childLogicalBottom))
return positionForPointRespectingEditingBoundaries(this, childBox, pointInContents);
}
}
// We only get here if there are no hit test candidate children below the click.
return RenderBox::positionForPoint(point);
}
void RenderBlock::offsetForContents(LayoutPoint& offset) const
{
if (hasOverflowClip())
offset += scrolledContentOffset();
}
LayoutUnit RenderBlock::availableLogicalWidth() const
{
return RenderBox::availableLogicalWidth();
}
void RenderBlock::computePreferredLogicalWidths()
{
ASSERT(preferredLogicalWidthsDirty());
m_minPreferredLogicalWidth = 0;
m_maxPreferredLogicalWidth = 0;
// FIXME: The isFixed() calls here should probably be checking for isSpecified since you
// should be able to use percentage, calc or viewport relative values for width.
RenderStyle* styleToUse = style();
if (styleToUse->logicalWidth().isFixed() && styleToUse->logicalWidth().value() >= 0)
m_minPreferredLogicalWidth = m_maxPreferredLogicalWidth = adjustContentBoxLogicalWidthForBoxSizing(styleToUse->logicalWidth().value());
else
computeIntrinsicLogicalWidths(m_minPreferredLogicalWidth, m_maxPreferredLogicalWidth);
if (styleToUse->logicalMinWidth().isFixed() && styleToUse->logicalMinWidth().value() > 0) {
m_maxPreferredLogicalWidth = std::max(m_maxPreferredLogicalWidth, adjustContentBoxLogicalWidthForBoxSizing(styleToUse->logicalMinWidth().value()));
m_minPreferredLogicalWidth = std::max(m_minPreferredLogicalWidth, adjustContentBoxLogicalWidthForBoxSizing(styleToUse->logicalMinWidth().value()));
}
if (styleToUse->logicalMaxWidth().isFixed()) {
m_maxPreferredLogicalWidth = std::min(m_maxPreferredLogicalWidth, adjustContentBoxLogicalWidthForBoxSizing(styleToUse->logicalMaxWidth().value()));
m_minPreferredLogicalWidth = std::min(m_minPreferredLogicalWidth, adjustContentBoxLogicalWidthForBoxSizing(styleToUse->logicalMaxWidth().value()));
}
LayoutUnit borderAndPadding = borderAndPaddingLogicalWidth();
m_minPreferredLogicalWidth += borderAndPadding;
m_maxPreferredLogicalWidth += borderAndPadding;
clearPreferredLogicalWidthsDirty();
}
void RenderBlock::computeIntrinsicLogicalWidths(LayoutUnit& minLogicalWidth, LayoutUnit& maxLogicalWidth) const
{
RenderStyle* styleToUse = style();
bool nowrap = styleToUse->whiteSpace() == NOWRAP;
RenderObject* child = firstChild();
while (child) {
// Positioned children don't affect the min/max width
if (child->isOutOfFlowPositioned()) {
child = child->nextSibling();
continue;
}
RefPtr<RenderStyle> childStyle = child->style();
// A margin basically has three types: fixed, percentage, and auto (variable).
// Auto and percentage margins simply become 0 when computing min/max width.
// Fixed margins can be added in as is.
Length startMarginLength = childStyle->marginStartUsing(styleToUse);
Length endMarginLength = childStyle->marginEndUsing(styleToUse);
LayoutUnit margin = 0;
LayoutUnit marginStart = 0;
LayoutUnit marginEnd = 0;
if (startMarginLength.isFixed())
marginStart += startMarginLength.value();
if (endMarginLength.isFixed())
marginEnd += endMarginLength.value();
margin = marginStart + marginEnd;
LayoutUnit childMinPreferredLogicalWidth = child->minPreferredLogicalWidth();
LayoutUnit childMaxPreferredLogicalWidth = child->maxPreferredLogicalWidth();
LayoutUnit w = childMinPreferredLogicalWidth + margin;
minLogicalWidth = std::max(w, minLogicalWidth);
// IE ignores tables for calculation of nowrap. Makes some sense.
if (nowrap)
maxLogicalWidth = std::max(w, maxLogicalWidth);
w = childMaxPreferredLogicalWidth + margin;
maxLogicalWidth = std::max(w, maxLogicalWidth);
child = child->nextSibling();
}
// Always make sure these values are non-negative.
minLogicalWidth = std::max<LayoutUnit>(0, minLogicalWidth);
maxLogicalWidth = std::max<LayoutUnit>(minLogicalWidth, maxLogicalWidth);
}
bool RenderBlock::hasLineIfEmpty() const
{
return node() && node()->isRootEditableElement();
}
LayoutUnit RenderBlock::lineHeight(bool firstLine, LineDirectionMode direction, LinePositionMode linePositionMode) const
{
// Inline blocks are replaced elements. Otherwise, just pass off to
// the base class. If we're being queried as though we're the root line
// box, then the fact that we're an inline-block is irrelevant, and we behave
// just like a block.
if (isReplaced() && linePositionMode == PositionOnContainingLine)
return RenderBox::lineHeight(firstLine, direction, linePositionMode);
RenderStyle* s = style(firstLine && document().styleEngine()->usesFirstLineRules());
return s->computedLineHeight();
}
int RenderBlock::beforeMarginInLineDirection(LineDirectionMode direction) const
{
return direction == HorizontalLine ? marginTop() : marginRight();
}
int RenderBlock::baselinePosition(FontBaseline baselineType, bool firstLine, LineDirectionMode direction, LinePositionMode linePositionMode) const
{
// Inline blocks are replaced elements. Otherwise, just pass off to
// the base class. If we're being queried as though we're the root line
// box, then the fact that we're an inline-block is irrelevant, and we behave
// just like a block.
if (isInline() && linePositionMode == PositionOnContainingLine) {
// CSS2.1 states that the baseline of an inline block is the baseline of the last line box in
// the normal flow. We make an exception for marquees, since their baselines are meaningless
// (the content inside them moves). This matches WinIE as well, which just bottom-aligns them.
// We also give up on finding a baseline if we have a vertical scrollbar, or if we are scrolled
// vertically (e.g., an overflow:hidden block that has had scrollTop moved).
bool ignoreBaseline = (layer() && layer()->scrollableArea() && ((direction == HorizontalLine ? (layer()->scrollableArea()->verticalScrollbar() || layer()->scrollableArea()->scrollYOffset())
: (layer()->scrollableArea()->horizontalScrollbar() || layer()->scrollableArea()->scrollXOffset()))));
int baselinePos = ignoreBaseline ? -1 : inlineBlockBaseline(direction);
if (baselinePos != -1)
return beforeMarginInLineDirection(direction) + baselinePos;
return RenderBox::baselinePosition(baselineType, firstLine, direction, linePositionMode);
}
// If we're not replaced, we'll only get called with PositionOfInteriorLineBoxes.
// Note that inline-block counts as replaced here.
ASSERT(linePositionMode == PositionOfInteriorLineBoxes);
const FontMetrics& fontMetrics = style(firstLine)->fontMetrics();
return fontMetrics.ascent(baselineType) + (lineHeight(firstLine, direction, linePositionMode) - fontMetrics.height()) / 2;
}
LayoutUnit RenderBlock::minLineHeightForReplacedRenderer(bool isFirstLine, LayoutUnit replacedHeight) const
{
if (!(style(isFirstLine)->lineBoxContain() & LineBoxContainBlock))
return 0;
return std::max<LayoutUnit>(replacedHeight, lineHeight(isFirstLine, HorizontalLine, PositionOfInteriorLineBoxes));
}
int RenderBlock::firstLineBoxBaseline() const
{
for (RenderBox* curr = firstChildBox(); curr; curr = curr->nextSiblingBox()) {
if (!curr->isFloatingOrOutOfFlowPositioned()) {
int result = curr->firstLineBoxBaseline();
if (result != -1)
return curr->logicalTop() + result; // Translate to our coordinate space.
}
}
return -1;
}
int RenderBlock::inlineBlockBaseline(LineDirectionMode direction) const
{
if (!style()->isOverflowVisible()) {
// We are not calling RenderBox::baselinePosition here because the caller should add the margin-top/margin-right, not us.
return direction == HorizontalLine ? height() + m_marginBox.bottom() : width() + m_marginBox.left();
}
return lastLineBoxBaseline(direction);
}
int RenderBlock::lastLineBoxBaseline(LineDirectionMode lineDirection) const
{
bool haveNormalFlowChild = false;
for (RenderBox* curr = lastChildBox(); curr; curr = curr->previousSiblingBox()) {
if (!curr->isFloatingOrOutOfFlowPositioned()) {
haveNormalFlowChild = true;
int result = curr->inlineBlockBaseline(lineDirection);
if (result != -1)
return curr->logicalTop() + result; // Translate to our coordinate space.
}
}
if (!haveNormalFlowChild && hasLineIfEmpty()) {
const FontMetrics& fontMetrics = firstLineStyle()->fontMetrics();
return fontMetrics.ascent()
+ (lineHeight(true, lineDirection, PositionOfInteriorLineBoxes) - fontMetrics.height()) / 2
+ (lineDirection == HorizontalLine ? borderTop() + paddingTop() : borderRight() + paddingRight());
}
return -1;
}
RenderBlock* RenderBlock::firstLineBlock() const
{
RenderBlock* firstLineBlock = const_cast<RenderBlock*>(this);
bool hasPseudo = false;
while (true) {
// FIXME(sky): Remove all this.
hasPseudo = false;
if (hasPseudo)
break;
RenderObject* parentBlock = firstLineBlock->parent();
if (firstLineBlock->isReplaced()
|| !parentBlock
|| !parentBlock->isRenderBlockFlow())
break;
ASSERT_WITH_SECURITY_IMPLICATION(parentBlock->isRenderBlock());
if (toRenderBlock(parentBlock)->firstChild() != firstLineBlock)
break;
firstLineBlock = toRenderBlock(parentBlock);
}
if (!hasPseudo)
return 0;
return firstLineBlock;
}
// Helper methods for obtaining the last line, computing line counts and heights for line counts
// (crawling into blocks).
static bool shouldCheckLines(RenderObject* obj)
{
return !obj->isFloatingOrOutOfFlowPositioned()
&& obj->isRenderBlock() && obj->style()->height().isAuto();
}
static int getHeightForLineCount(RenderBlock* block, int l, bool includeBottom, int& count)
{
RenderBox* normalFlowChildWithoutLines = 0;
for (RenderBox* obj = block->firstChildBox(); obj; obj = obj->nextSiblingBox()) {
if (shouldCheckLines(obj)) {
int result = getHeightForLineCount(toRenderBlock(obj), l, false, count);
if (result != -1)
return result + obj->y() + (includeBottom ? (block->borderBottom() + block->paddingBottom()) : LayoutUnit());
} else if (!obj->isFloatingOrOutOfFlowPositioned()) {
normalFlowChildWithoutLines = obj;
}
}
if (normalFlowChildWithoutLines && l == 0)
return normalFlowChildWithoutLines->y() + normalFlowChildWithoutLines->height();
return -1;
}
RootInlineBox* RenderBlock::lineAtIndex(int i) const
{
ASSERT(i >= 0);
for (RenderObject* child = firstChild(); child; child = child->nextSibling()) {
if (!shouldCheckLines(child))
continue;
if (RootInlineBox* box = toRenderBlock(child)->lineAtIndex(i))
return box;
}
return 0;
}
int RenderBlock::lineCount(const RootInlineBox* stopRootInlineBox, bool* found) const
{
int count = 0;
for (RenderObject* obj = firstChild(); obj; obj = obj->nextSibling()) {
if (shouldCheckLines(obj)) {
bool recursiveFound = false;
count += toRenderBlock(obj)->lineCount(stopRootInlineBox, &recursiveFound);
if (recursiveFound) {
if (found)
*found = true;
break;
}
}
}
return count;
}
void RenderBlock::clearTruncation()
{
for (RenderObject* obj = firstChild(); obj; obj = obj->nextSibling()) {
if (shouldCheckLines(obj))
toRenderBlock(obj)->clearTruncation();
}
}
void RenderBlock::absoluteRects(Vector<IntRect>& rects, const LayoutPoint& accumulatedOffset) const
{
rects.append(pixelSnappedIntRect(accumulatedOffset, size()));
}
void RenderBlock::absoluteQuads(Vector<FloatQuad>& quads) const
{
quads.append(RenderBox::localToAbsoluteQuad(FloatRect(0, 0, width().toFloat(), height().toFloat()), 0 /* mode */));
}
void RenderBlock::updateHitTestResult(HitTestResult& result, const LayoutPoint& point)
{
if (result.innerNode())
return;
if (Node* n = node()) {
result.setInnerNode(n);
if (!result.innerNonSharedNode())
result.setInnerNonSharedNode(n);
result.setLocalPoint(point);
}
}
LayoutRect RenderBlock::localCaretRect(InlineBox* inlineBox, int caretOffset, LayoutUnit* extraWidthToEndOfLine)
{
// Do the normal calculation in most cases.
if (firstChild())
return RenderBox::localCaretRect(inlineBox, caretOffset, extraWidthToEndOfLine);
LayoutRect caretRect = localCaretRectForEmptyElement(width(), textIndentOffset());
if (extraWidthToEndOfLine)
*extraWidthToEndOfLine = width() - caretRect.maxX();
return caretRect;
}
void RenderBlock::addFocusRingRects(Vector<IntRect>& rects, const LayoutPoint& additionalOffset, const RenderLayerModelObject* paintContainer) const
{
if (width() && height())
rects.append(pixelSnappedIntRect(additionalOffset, size()));
if (!hasOverflowClip() && !hasControlClip()) {
for (RootInlineBox* curr = firstRootBox(); curr; curr = curr->nextRootBox()) {
LayoutUnit top = std::max<LayoutUnit>(curr->lineTop(), curr->top());
LayoutUnit bottom = std::min<LayoutUnit>(curr->lineBottom(), curr->top() + curr->height());
LayoutRect rect(additionalOffset.x() + curr->x(), additionalOffset.y() + top, curr->width(), bottom - top);
if (!rect.isEmpty())
rects.append(pixelSnappedIntRect(rect));
}
addChildFocusRingRects(rects, additionalOffset, paintContainer);
}
}
LayoutUnit RenderBlock::marginBeforeForChild(const RenderBox* child) const
{
// FIXME(sky): Remove
return child->marginBefore();
}
LayoutUnit RenderBlock::marginAfterForChild(const RenderBox* child) const
{
// FIXME(sky): Remove
return child->marginAfter();
}
bool RenderBlock::hasMarginBeforeQuirk(const RenderBox* child) const
{
return child->isRenderBlock() ? toRenderBlock(child)->hasMarginBeforeQuirk() : child->style()->hasMarginBeforeQuirk();
}
bool RenderBlock::hasMarginAfterQuirk(const RenderBox* child) const
{
return child->isRenderBlock() ? toRenderBlock(child)->hasMarginAfterQuirk() : child->style()->hasMarginAfterQuirk();
}
const char* RenderBlock::renderName() const
{
if (isOutOfFlowPositioned())
return "RenderBlock (positioned)";
if (isAnonymousBlock())
return "RenderBlock (anonymous)";
if (isAnonymous())
return "RenderBlock (generated)";
if (isRelPositioned())
return "RenderBlock (relative positioned)";
return "RenderBlock";
}
// FIXME(sky): Clean up callers now that we no longer use the EDisplay argument.
RenderBlock* RenderBlock::createAnonymousWithParentRendererAndDisplay(const RenderObject* parent, EDisplay)
{
RenderBlock* newBox = RenderParagraph::createAnonymous(parent->document());
RefPtr<RenderStyle> newStyle = RenderStyle::createAnonymousStyleWithDisplay(parent->style(), PARAGRAPH);
parent->updateAnonymousChildStyle(newBox, newStyle.get());
newBox->setStyle(newStyle.release());
return newBox;
}
static bool recalcNormalFlowChildOverflowIfNeeded(RenderObject* renderer)
{
if (renderer->isOutOfFlowPositioned() || !renderer->needsOverflowRecalcAfterStyleChange())
return false;
ASSERT(renderer->isRenderBlock());
return toRenderBlock(renderer)->recalcOverflowAfterStyleChange();
}
bool RenderBlock::recalcChildOverflowAfterStyleChange()
{
ASSERT(childNeedsOverflowRecalcAfterStyleChange());
setChildNeedsOverflowRecalcAfterStyleChange(false);
bool childrenOverflowChanged = false;
if (isRenderParagraph()) {
ListHashSet<RootInlineBox*> lineBoxes;
for (InlineWalker walker(this); !walker.atEnd(); walker.advance()) {
RenderObject* renderer = walker.current();
if (recalcNormalFlowChildOverflowIfNeeded(renderer)) {
childrenOverflowChanged = true;
if (InlineBox* inlineBoxWrapper = toRenderBlock(renderer)->inlineBoxWrapper())
lineBoxes.add(&inlineBoxWrapper->root());
}
}
// FIXME: Glyph overflow will get lost in this case, but not really a big deal.
GlyphOverflowAndFallbackFontsMap textBoxDataMap;
for (ListHashSet<RootInlineBox*>::const_iterator it = lineBoxes.begin(); it != lineBoxes.end(); ++it) {
RootInlineBox* box = *it;
box->computeOverflow(box->lineTop(), box->lineBottom(), textBoxDataMap);
}
} else {
for (RenderBox* box = firstChildBox(); box; box = box->nextSiblingBox()) {
if (recalcNormalFlowChildOverflowIfNeeded(box))
childrenOverflowChanged = true;
}
}
TrackedRendererListHashSet* positionedDescendants = positionedObjects();
if (!positionedDescendants)
return childrenOverflowChanged;
TrackedRendererListHashSet::iterator end = positionedDescendants->end();
for (TrackedRendererListHashSet::iterator it = positionedDescendants->begin(); it != end; ++it) {
RenderBox* box = *it;
if (!box->needsOverflowRecalcAfterStyleChange())
continue;
RenderBlock* block = toRenderBlock(box);
if (!block->recalcOverflowAfterStyleChange())
continue;
childrenOverflowChanged = true;
}
return childrenOverflowChanged;
}
bool RenderBlock::recalcOverflowAfterStyleChange()
{
ASSERT(needsOverflowRecalcAfterStyleChange());
bool childrenOverflowChanged = false;
if (childNeedsOverflowRecalcAfterStyleChange())
childrenOverflowChanged = recalcChildOverflowAfterStyleChange();
if (!selfNeedsOverflowRecalcAfterStyleChange() && !childrenOverflowChanged)
return false;
setSelfNeedsOverflowRecalcAfterStyleChange(false);
// If the current block needs layout, overflow will be recalculated during
// layout time anyway. We can safely exit here.
if (needsLayout())
return false;
LayoutUnit oldClientAfterEdge = hasRenderOverflow() ? m_overflow->layoutClientAfterEdge() : clientLogicalBottom();
computeOverflow(oldClientAfterEdge, true);
if (hasOverflowClip())
layer()->scrollableArea()->updateAfterOverflowRecalc();
return !hasOverflowClip();
}
#if ENABLE(ASSERT)
void RenderBlock::checkPositionedObjectsNeedLayout()
{
if (!gPositionedDescendantsMap)
return;
if (TrackedRendererListHashSet* positionedDescendantSet = positionedObjects()) {
TrackedRendererListHashSet::const_iterator end = positionedDescendantSet->end();
for (TrackedRendererListHashSet::const_iterator it = positionedDescendantSet->begin(); it != end; ++it) {
RenderBox* currBox = *it;
ASSERT(!currBox->needsLayout());
}
}
}
#endif
#ifndef NDEBUG
void RenderBlock::showLineTreeAndMark(const InlineBox* markedBox1, const char* markedLabel1, const InlineBox* markedBox2, const char* markedLabel2, const RenderObject* obj) const
{
showRenderObject();
for (const RootInlineBox* root = firstRootBox(); root; root = root->nextRootBox())
root->showLineTreeAndMark(markedBox1, markedLabel1, markedBox2, markedLabel2, obj, 1);
}
#endif
} // namespace blink