mirror of
https://github.com/flutter/flutter.git
synced 2026-02-20 02:29:02 +08:00
1725 lines
63 KiB
C++
1725 lines
63 KiB
C++
/*
|
|
* 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 "flutter/sky/engine/core/rendering/RenderBlock.h"
|
|
|
|
#include "flutter/sky/engine/core/rendering/HitTestLocation.h"
|
|
#include "flutter/sky/engine/core/rendering/HitTestResult.h"
|
|
#include "flutter/sky/engine/core/rendering/InlineIterator.h"
|
|
#include "flutter/sky/engine/core/rendering/InlineTextBox.h"
|
|
#include "flutter/sky/engine/core/rendering/PaintInfo.h"
|
|
#include "flutter/sky/engine/core/rendering/RenderFlexibleBox.h"
|
|
#include "flutter/sky/engine/core/rendering/RenderInline.h"
|
|
#include "flutter/sky/engine/core/rendering/RenderLayer.h"
|
|
#include "flutter/sky/engine/core/rendering/RenderObjectInlines.h"
|
|
#include "flutter/sky/engine/core/rendering/RenderParagraph.h"
|
|
#include "flutter/sky/engine/core/rendering/RenderView.h"
|
|
#include "flutter/sky/engine/core/rendering/style/RenderStyle.h"
|
|
#include "flutter/sky/engine/platform/geometry/FloatQuad.h"
|
|
#include "flutter/sky/engine/platform/geometry/TransformState.h"
|
|
#include "flutter/sky/engine/platform/graphics/GraphicsContextStateSaver.h"
|
|
#include "flutter/sky/engine/wtf/StdLibExtras.h"
|
|
#include "flutter/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;
|
|
|
|
RenderBlock::RenderBlock()
|
|
: 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() && parent())
|
|
parent()->dirtyLinesFromChangedChild(this);
|
|
}
|
|
|
|
m_lineBoxes.deleteLineBoxes();
|
|
|
|
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()) {
|
|
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);
|
|
|
|
// 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, style());
|
|
}
|
|
|
|
void RenderBlock::addChild(RenderObject* newChild, RenderObject* beforeChild) {
|
|
ASSERT(isRenderParagraph() || !newChild->isInline());
|
|
RenderBox::addChild(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();
|
|
}
|
|
|
|
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();
|
|
|
|
return widthAvailableToChildrenHasChanged;
|
|
}
|
|
|
|
bool RenderBlock::updateLogicalWidthAndColumnWidth() {
|
|
LayoutUnit oldWidth = logicalWidth();
|
|
updateLogicalWidth();
|
|
return oldWidth != logicalWidth() || widthAvailableToChildrenHasChanged();
|
|
}
|
|
|
|
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()));
|
|
}
|
|
}
|
|
|
|
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;
|
|
|
|
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();
|
|
|
|
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;
|
|
|
|
// 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,
|
|
Vector<RenderBox*>& layers) {
|
|
LayoutPoint adjustedPaintOffset = paintOffset + location();
|
|
|
|
LayoutRect 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() && !shouldPaintSelectionGaps())
|
|
contentsClipBehavior = SkipContentsClipIfPossible;
|
|
|
|
bool pushedClip =
|
|
pushContentsClip(paintInfo, adjustedPaintOffset, contentsClipBehavior);
|
|
paintObject(paintInfo, adjustedPaintOffset, layers);
|
|
if (pushedClip)
|
|
popContentsClip(paintInfo, adjustedPaintOffset);
|
|
}
|
|
|
|
void RenderBlock::paintChildren(PaintInfo& paintInfo,
|
|
const LayoutPoint& paintOffset,
|
|
Vector<RenderBox*>& layers) {
|
|
for (RenderBox* child = firstChildBox(); child;
|
|
child = child->nextSiblingBox()) {
|
|
if (child->hasSelfPaintingLayer())
|
|
layers.append(child);
|
|
else
|
|
child->paint(paintInfo, paintOffset, layers);
|
|
}
|
|
}
|
|
|
|
void RenderBlock::paintObject(PaintInfo& paintInfo,
|
|
const LayoutPoint& paintOffset,
|
|
Vector<RenderBox*>& layers) {
|
|
if (hasBoxDecorationBackground())
|
|
paintBoxDecorationBackground(paintInfo, paintOffset);
|
|
|
|
paintChildren(paintInfo, paintOffset, layers);
|
|
paintSelection(
|
|
paintInfo,
|
|
paintOffset); // Fill in gaps in selection on lines and between blocks.
|
|
}
|
|
|
|
bool RenderBlock::shouldPaintSelectionGaps() const {
|
|
return selectionState() != SelectionNone && isSelectionRoot();
|
|
}
|
|
|
|
bool RenderBlock::isSelectionRoot() const {
|
|
return false;
|
|
}
|
|
|
|
void RenderBlock::paintSelection(PaintInfo& paintInfo,
|
|
const LayoutPoint& paintOffset) {
|
|
if (shouldPaintSelectionGaps()) {
|
|
LayoutUnit lastTop = 0;
|
|
LayoutUnit lastLeft = logicalLeftSelectionOffset(this, lastTop);
|
|
LayoutUnit lastRight = logicalRightSelectionOffset(this, lastTop);
|
|
GraphicsContextStateSaver stateSaver(*paintInfo.context);
|
|
|
|
// TODO(ojan): In sky, we don't use the return value, but we
|
|
// need this in order to actually paint selection gaps.
|
|
// We should rename it appropriately.
|
|
selectionGaps(this, paintOffset, LayoutSize(), lastTop, lastLeft, lastRight,
|
|
&paintInfo);
|
|
}
|
|
}
|
|
|
|
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());
|
|
}
|
|
|
|
// 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 (!isRenderParagraph()) // 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.
|
|
|
|
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) {
|
|
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) {
|
|
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:
|
|
break;
|
|
case ClipPathOperation::REFERENCE:
|
|
break;
|
|
}
|
|
}
|
|
|
|
// If we have clipping, then we can't have any spillout.
|
|
bool useOverflowClip = hasOverflowClip() && !hasSelfPaintingLayer();
|
|
bool checkChildren = !useOverflowClip;
|
|
if (!checkChildren) {
|
|
LayoutRect clipRect = overflowClipRect(adjustedLocation);
|
|
if (style()->hasBorderRadius())
|
|
checkChildren = locationInContainer.intersects(
|
|
style()->getRoundedBorderFor(clipRect));
|
|
else
|
|
checkChildren = locationInContainer.intersects(clipRect);
|
|
}
|
|
if (checkChildren) {
|
|
if (hitTestContents(request, result, locationInContainer,
|
|
toLayoutPoint(localOffset))) {
|
|
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
|
|
LayoutRect boundsRect(adjustedLocation, size());
|
|
if (visibleToHitTestRequest(request) &&
|
|
locationInContainer.intersects(boundsRect)) {
|
|
updateHitTestResult(result, locationInContainer.point() - localOffset);
|
|
return true;
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
bool RenderBlock::hitTestContents(const HitTestRequest& request,
|
|
HitTestResult& result,
|
|
const HitTestLocation& locationInContainer,
|
|
const LayoutPoint& accumulatedOffset) {
|
|
for (RenderBox* child = lastChildBox(); child;
|
|
child = child->previousSiblingBox()) {
|
|
if (!child->hasSelfPaintingLayer() &&
|
|
child->nodeAtPoint(request, result, locationInContainer,
|
|
accumulatedOffset))
|
|
return true;
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
static PositionWithAffinity positionForPointInChild(
|
|
RenderBox* child,
|
|
const LayoutPoint& pointInParentCoordinates) {
|
|
LayoutPoint childLocation = child->location();
|
|
|
|
// FIXME: This is wrong if the child's writing-mode is different from the
|
|
// parent's.
|
|
LayoutPoint pointInChildCoordinates(
|
|
toLayoutPoint(pointInParentCoordinates - childLocation));
|
|
return child->positionForPoint(pointInChildCoordinates);
|
|
}
|
|
|
|
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;
|
|
|
|
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;
|
|
}
|
|
}
|
|
|
|
if (!closestBox && lastRootBoxWithChildren) {
|
|
// y coordinate is below last root line box, pretend we hit it
|
|
closestBox =
|
|
lastRootBoxWithChildren->closestLeafChildForLogicalLeftPosition(
|
|
pointInLogicalContents.x());
|
|
}
|
|
|
|
if (closestBox) {
|
|
// pass the box a top position that is inside it
|
|
LayoutPoint point(pointInLogicalContents.x(),
|
|
closestBox->root().blockDirectionPointInLine());
|
|
if (closestBox->renderer().isReplaced())
|
|
return positionForPointInChild(&toRenderBox(closestBox->renderer()),
|
|
point);
|
|
return closestBox->renderer().positionForPoint(point);
|
|
}
|
|
|
|
// 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;
|
|
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 positionForPointInChild(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 positionForPointInChild(childBox, pointInContents);
|
|
}
|
|
}
|
|
|
|
// We only get here if there are no hit test candidate children below the
|
|
// click.
|
|
return RenderBox::positionForPoint(point);
|
|
}
|
|
|
|
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 false;
|
|
}
|
|
|
|
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);
|
|
return style()->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).
|
|
int baselinePos = 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(FontBaselineOrAuto baselineType) const {
|
|
for (RenderBox* curr = firstChildBox(); curr; curr = curr->nextSiblingBox()) {
|
|
if (!curr->isFloatingOrOutOfFlowPositioned()) {
|
|
int result = curr->firstLineBoxBaseline(baselineType);
|
|
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->isRenderParagraph())
|
|
break;
|
|
ASSERT_WITH_SECURITY_IMPLICATION(parentBlock->isRenderBlock());
|
|
if (toRenderParagraph(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();
|
|
}
|
|
|
|
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::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) {}
|
|
|
|
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 RenderBox* paintContainer) const {
|
|
if (width() && height())
|
|
rects.append(pixelSnappedIntRect(additionalOffset, size()));
|
|
|
|
if (!hasOverflowClip()) {
|
|
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 (isInlineBlock())
|
|
return "RenderBlock (inline-block)";
|
|
if (isOutOfFlowPositioned())
|
|
return "RenderBlock (positioned)";
|
|
return "RenderBlock";
|
|
}
|
|
|
|
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);
|
|
|
|
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
|