blob: ff657ef3a695d283fdcee846b6cca75d17dc2ceb [file] [log] [blame]
/*
* Copyright (C) 1999 Lars Knoll (knoll@kde.org)
* Copyright (C) 2000 Dirk Mueller (mueller@kde.org)
* Copyright (C) 2004, 2006, 2007 Apple Inc. All rights reserved.
* Copyright (C) Research In Motion Limited 2011-2012. 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 "core/layout/LayoutReplaced.h"
#include "core/editing/PositionWithAffinity.h"
#include "core/layout/LayoutAnalyzer.h"
#include "core/layout/LayoutBlock.h"
#include "core/layout/LayoutImage.h"
#include "core/layout/LayoutInline.h"
#include "core/layout/api/LineLayoutBlockFlow.h"
#include "core/paint/PaintInfo.h"
#include "core/paint/PaintLayer.h"
#include "core/paint/ReplacedPainter.h"
#include "platform/LengthFunctions.h"
namespace blink {
const int LayoutReplaced::defaultWidth = 300;
const int LayoutReplaced::defaultHeight = 150;
LayoutReplaced::LayoutReplaced(Element* element)
: LayoutBox(element)
, m_intrinsicSize(defaultWidth, defaultHeight)
{
// TODO(jchaffraix): We should not set this boolean for block-level
// replaced elements (crbug.com/567964).
setIsAtomicInlineLevel(true);
}
LayoutReplaced::LayoutReplaced(Element* element, const LayoutSize& intrinsicSize)
: LayoutBox(element)
, m_intrinsicSize(intrinsicSize)
{
// TODO(jchaffraix): We should not set this boolean for block-level
// replaced elements (crbug.com/567964).
setIsAtomicInlineLevel(true);
}
LayoutReplaced::~LayoutReplaced()
{
}
void LayoutReplaced::willBeDestroyed()
{
if (!documentBeingDestroyed() && parent())
parent()->dirtyLinesFromChangedChild(this);
LayoutBox::willBeDestroyed();
}
void LayoutReplaced::styleDidChange(StyleDifference diff, const ComputedStyle* oldStyle)
{
LayoutBox::styleDidChange(diff, oldStyle);
bool hadStyle = (oldStyle != 0);
float oldZoom = hadStyle ? oldStyle->effectiveZoom() : ComputedStyle::initialZoom();
if (style() && style()->effectiveZoom() != oldZoom)
intrinsicSizeChanged();
}
void LayoutReplaced::layout()
{
ASSERT(needsLayout());
LayoutAnalyzer::Scope analyzer(*this);
LayoutRect oldContentRect = replacedContentRect();
setHeight(minimumReplacedHeight());
updateLogicalWidth();
updateLogicalHeight();
m_overflow.reset();
addVisualEffectOverflow();
updateLayerTransformAfterLayout();
invalidateBackgroundObscurationStatus();
clearNeedsLayout();
if (!RuntimeEnabledFeatures::slimmingPaintV2Enabled() && replacedContentRect() != oldContentRect)
setShouldDoFullPaintInvalidation();
}
void LayoutReplaced::intrinsicSizeChanged()
{
int scaledWidth = static_cast<int>(defaultWidth * style()->effectiveZoom());
int scaledHeight = static_cast<int>(defaultHeight * style()->effectiveZoom());
m_intrinsicSize = LayoutSize(scaledWidth, scaledHeight);
setNeedsLayoutAndPrefWidthsRecalcAndFullPaintInvalidation(LayoutInvalidationReason::SizeChanged);
}
void LayoutReplaced::paint(const PaintInfo& paintInfo, const LayoutPoint& paintOffset) const
{
ReplacedPainter(*this).paint(paintInfo, paintOffset);
}
bool LayoutReplaced::hasReplacedLogicalHeight() const
{
if (style()->logicalHeight().isAuto())
return false;
if (style()->logicalHeight().isSpecified()) {
if (hasAutoHeightOrContainingBlockWithAutoHeight())
return false;
return true;
}
if (style()->logicalHeight().isIntrinsic())
return true;
return false;
}
bool LayoutReplaced::needsPreferredWidthsRecalculation() const
{
// If the height is a percentage and the width is auto, then the containingBlocks's height changing can cause
// this node to change it's preferred width because it maintains aspect ratio.
return hasRelativeLogicalHeight() && style()->logicalWidth().isAuto() && !hasAutoHeightOrContainingBlockWithAutoHeight();
}
static inline bool layoutObjectHasAspectRatio(const LayoutObject* layoutObject)
{
ASSERT(layoutObject);
return layoutObject->isImage() || layoutObject->isCanvas() || layoutObject->isVideo();
}
void LayoutReplaced::computeIntrinsicSizingInfoForReplacedContent(LayoutReplaced* contentLayoutObject, IntrinsicSizingInfo& intrinsicSizingInfo) const
{
if (contentLayoutObject) {
contentLayoutObject->computeIntrinsicSizingInfo(intrinsicSizingInfo);
// Handle zoom & vertical writing modes here, as the embedded document doesn't know about them.
intrinsicSizingInfo.size.scale(style()->effectiveZoom());
if (isLayoutImage())
intrinsicSizingInfo.size.scale(toLayoutImage(this)->imageDevicePixelRatio());
// Update our intrinsic size to match what the content layoutObject has computed, so that when we
// constrain the size below, the correct intrinsic size will be obtained for comparison against
// min and max widths.
if (!intrinsicSizingInfo.aspectRatio.isEmpty() && !intrinsicSizingInfo.size.isEmpty())
m_intrinsicSize = LayoutSize(intrinsicSizingInfo.size);
if (!isHorizontalWritingMode())
intrinsicSizingInfo.transpose();
} else {
computeIntrinsicSizingInfo(intrinsicSizingInfo);
if (!intrinsicSizingInfo.aspectRatio.isEmpty() && !intrinsicSizingInfo.size.isEmpty())
m_intrinsicSize = LayoutSize(isHorizontalWritingMode() ? intrinsicSizingInfo.size : intrinsicSizingInfo.size.transposedSize());
}
}
FloatSize LayoutReplaced::constrainIntrinsicSizeToMinMax(const IntrinsicSizingInfo& intrinsicSizingInfo) const
{
// Constrain the intrinsic size along each axis according to minimum and maximum width/heights along the opposite
// axis. So for example a maximum width that shrinks our width will result in the height we compute here having
// to shrink in order to preserve the aspect ratio. Because we compute these values independently along each
// axis, the final returned size may in fact not preserve the aspect ratio.
// TODO(davve): Investigate using only the intrinsic aspect ratio here.
FloatSize constrainedSize = intrinsicSizingInfo.size;
if (!intrinsicSizingInfo.aspectRatio.isEmpty() && !intrinsicSizingInfo.size.isEmpty() && style()->logicalWidth().isAuto() && style()->logicalHeight().isAuto()) {
// We can't multiply or divide by 'intrinsicSizingInfo.aspectRatio' here, it breaks tests, like fast/images/zoomed-img-size.html, which
// can only be fixed once subpixel precision is available for things like intrinsicWidth/Height - which include zoom!
constrainedSize.setWidth(LayoutBox::computeReplacedLogicalHeight() * intrinsicSizingInfo.size.width() / intrinsicSizingInfo.size.height());
constrainedSize.setHeight(LayoutBox::computeReplacedLogicalWidth() * intrinsicSizingInfo.size.height() / intrinsicSizingInfo.size.width());
}
return constrainedSize;
}
void LayoutReplaced::computePositionedLogicalWidth(LogicalExtentComputedValues& computedValues) const
{
// The following is based off of the W3C Working Draft from April 11, 2006 of
// CSS 2.1: Section 10.3.8 "Absolutely positioned, replaced elements"
// <http://www.w3.org/TR/2005/WD-CSS21-20050613/visudet.html#abs-replaced-width>
// (block-style-comments in this function correspond to text from the spec and
// the numbers correspond to numbers in spec)
// We don't use containingBlock(), since we may be positioned by an enclosing
// relative positioned inline.
const LayoutBoxModelObject* containerBlock = toLayoutBoxModelObject(container());
const LayoutUnit containerLogicalWidth = containingBlockLogicalWidthForPositioned(containerBlock);
const LayoutUnit containerRelativeLogicalWidth = containingBlockLogicalWidthForPositioned(containerBlock, false);
// To match WinIE, in quirks mode use the parent's 'direction' property
// instead of the the container block's.
TextDirection containerDirection = containerBlock->style()->direction();
// Variables to solve.
bool isHorizontal = isHorizontalWritingMode();
Length logicalLeft = style()->logicalLeft();
Length logicalRight = style()->logicalRight();
Length marginLogicalLeft = isHorizontal ? style()->marginLeft() : style()->marginTop();
Length marginLogicalRight = isHorizontal ? style()->marginRight() : style()->marginBottom();
LayoutUnit& marginLogicalLeftAlias = style()->isLeftToRightDirection() ? computedValues.m_margins.m_start : computedValues.m_margins.m_end;
LayoutUnit& marginLogicalRightAlias = style()->isLeftToRightDirection() ? computedValues.m_margins.m_end : computedValues.m_margins.m_start;
/*-----------------------------------------------------------------------*\
* 1. The used value of 'width' is determined as for inline replaced
* elements.
\*-----------------------------------------------------------------------*/
// NOTE: This value of width is final in that the min/max width calculations
// are dealt with in computeReplacedWidth(). This means that the steps to produce
// correct max/min in the non-replaced version, are not necessary.
computedValues.m_extent = computeReplacedLogicalWidth() + borderAndPaddingLogicalWidth();
const LayoutUnit availableSpace = containerLogicalWidth - computedValues.m_extent;
/*-----------------------------------------------------------------------*\
* 2. If both 'left' and 'right' have the value 'auto', then if 'direction'
* of the containing block is 'ltr', set 'left' to the static position;
* else if 'direction' is 'rtl', set 'right' to the static position.
\*-----------------------------------------------------------------------*/
// see FIXME 1
computeInlineStaticDistance(logicalLeft, logicalRight, this, containerBlock, containerLogicalWidth);
/*-----------------------------------------------------------------------*\
* 3. If 'left' or 'right' are 'auto', replace any 'auto' on 'margin-left'
* or 'margin-right' with '0'.
\*-----------------------------------------------------------------------*/
if (logicalLeft.isAuto() || logicalRight.isAuto()) {
if (marginLogicalLeft.isAuto())
marginLogicalLeft.setValue(Fixed, 0);
if (marginLogicalRight.isAuto())
marginLogicalRight.setValue(Fixed, 0);
}
/*-----------------------------------------------------------------------*\
* 4. If at this point both 'margin-left' and 'margin-right' are still
* 'auto', solve the equation under the extra constraint that the two
* margins must get equal values, unless this would make them negative,
* in which case when the direction of the containing block is 'ltr'
* ('rtl'), set 'margin-left' ('margin-right') to zero and solve for
* 'margin-right' ('margin-left').
\*-----------------------------------------------------------------------*/
LayoutUnit logicalLeftValue;
LayoutUnit logicalRightValue;
if (marginLogicalLeft.isAuto() && marginLogicalRight.isAuto()) {
// 'left' and 'right' cannot be 'auto' due to step 3
ASSERT(!(logicalLeft.isAuto() && logicalRight.isAuto()));
logicalLeftValue = valueForLength(logicalLeft, containerLogicalWidth);
logicalRightValue = valueForLength(logicalRight, containerLogicalWidth);
LayoutUnit difference = availableSpace - (logicalLeftValue + logicalRightValue);
if (difference > LayoutUnit()) {
marginLogicalLeftAlias = difference / 2; // split the difference
marginLogicalRightAlias = difference - marginLogicalLeftAlias; // account for odd valued differences
} else {
// Use the containing block's direction rather than the parent block's
// per CSS 2.1 reference test abspos-replaced-width-margin-000.
if (containerDirection == LTR) {
marginLogicalLeftAlias = LayoutUnit();
marginLogicalRightAlias = difference; // will be negative
} else {
marginLogicalLeftAlias = difference; // will be negative
marginLogicalRightAlias = LayoutUnit();
}
}
/*-----------------------------------------------------------------------*\
* 5. If at this point there is an 'auto' left, solve the equation for
* that value.
\*-----------------------------------------------------------------------*/
} else if (logicalLeft.isAuto()) {
marginLogicalLeftAlias = valueForLength(marginLogicalLeft, containerRelativeLogicalWidth);
marginLogicalRightAlias = valueForLength(marginLogicalRight, containerRelativeLogicalWidth);
logicalRightValue = valueForLength(logicalRight, containerLogicalWidth);
// Solve for 'left'
logicalLeftValue = availableSpace - (logicalRightValue + marginLogicalLeftAlias + marginLogicalRightAlias);
} else if (logicalRight.isAuto()) {
marginLogicalLeftAlias = valueForLength(marginLogicalLeft, containerRelativeLogicalWidth);
marginLogicalRightAlias = valueForLength(marginLogicalRight, containerRelativeLogicalWidth);
logicalLeftValue = valueForLength(logicalLeft, containerLogicalWidth);
// Solve for 'right'
logicalRightValue = availableSpace - (logicalLeftValue + marginLogicalLeftAlias + marginLogicalRightAlias);
} else if (marginLogicalLeft.isAuto()) {
marginLogicalRightAlias = valueForLength(marginLogicalRight, containerRelativeLogicalWidth);
logicalLeftValue = valueForLength(logicalLeft, containerLogicalWidth);
logicalRightValue = valueForLength(logicalRight, containerLogicalWidth);
// Solve for 'margin-left'
marginLogicalLeftAlias = availableSpace - (logicalLeftValue + logicalRightValue + marginLogicalRightAlias);
} else if (marginLogicalRight.isAuto()) {
marginLogicalLeftAlias = valueForLength(marginLogicalLeft, containerRelativeLogicalWidth);
logicalLeftValue = valueForLength(logicalLeft, containerLogicalWidth);
logicalRightValue = valueForLength(logicalRight, containerLogicalWidth);
// Solve for 'margin-right'
marginLogicalRightAlias = availableSpace - (logicalLeftValue + logicalRightValue + marginLogicalLeftAlias);
} else {
// Nothing is 'auto', just calculate the values.
marginLogicalLeftAlias = valueForLength(marginLogicalLeft, containerRelativeLogicalWidth);
marginLogicalRightAlias = valueForLength(marginLogicalRight, containerRelativeLogicalWidth);
logicalRightValue = valueForLength(logicalRight, containerLogicalWidth);
logicalLeftValue = valueForLength(logicalLeft, containerLogicalWidth);
// If the containing block is right-to-left, then push the left position as far to the right as possible
if (containerDirection == RTL) {
int totalLogicalWidth = computedValues.m_extent + logicalLeftValue + logicalRightValue + marginLogicalLeftAlias + marginLogicalRightAlias;
logicalLeftValue = containerLogicalWidth - (totalLogicalWidth - logicalLeftValue);
}
}
/*-----------------------------------------------------------------------*\
* 6. If at this point the values are over-constrained, ignore the value
* for either 'left' (in case the 'direction' property of the
* containing block is 'rtl') or 'right' (in case 'direction' is
* 'ltr') and solve for that value.
\*-----------------------------------------------------------------------*/
// NOTE: Constraints imposed by the width of the containing block and its content have already been accounted for above.
// FIXME: Deal with differing writing modes here. Our offset needs to be in the containing block's coordinate space, so that
// can make the result here rather complicated to compute.
// Use computed values to calculate the horizontal position.
// FIXME: This hack is needed to calculate the logical left position for a 'rtl' relatively
// positioned, inline containing block because right now, it is using the logical left position
// of the first line box when really it should use the last line box. When
// this is fixed elsewhere, this block should be removed.
if (containerBlock->isLayoutInline() && !containerBlock->style()->isLeftToRightDirection()) {
const LayoutInline* flow = toLayoutInline(containerBlock);
InlineFlowBox* firstLine = flow->firstLineBox();
InlineFlowBox* lastLine = flow->lastLineBox();
if (firstLine && lastLine && firstLine != lastLine) {
computedValues.m_position = logicalLeftValue + marginLogicalLeftAlias + lastLine->borderLogicalLeft() + (lastLine->logicalLeft() - firstLine->logicalLeft());
return;
}
}
LayoutUnit logicalLeftPos = logicalLeftValue + marginLogicalLeftAlias;
computeLogicalLeftPositionedOffset(logicalLeftPos, this, computedValues.m_extent, containerBlock, containerLogicalWidth);
computedValues.m_position = logicalLeftPos;
}
void LayoutReplaced::computePositionedLogicalHeight(LogicalExtentComputedValues& computedValues) const
{
// The following is based off of the W3C Working Draft from April 11, 2006 of
// CSS 2.1: Section 10.6.5 "Absolutely positioned, replaced elements"
// <http://www.w3.org/TR/2005/WD-CSS21-20050613/visudet.html#abs-replaced-height>
// (block-style-comments in this function correspond to text from the spec and
// the numbers correspond to numbers in spec)
// We don't use containingBlock(), since we may be positioned by an enclosing relpositioned inline.
const LayoutBoxModelObject* containerBlock = toLayoutBoxModelObject(container());
const LayoutUnit containerLogicalHeight = containingBlockLogicalHeightForPositioned(containerBlock);
const LayoutUnit containerRelativeLogicalWidth = containingBlockLogicalWidthForPositioned(containerBlock, false);
// Variables to solve.
Length marginBefore = style()->marginBefore();
Length marginAfter = style()->marginAfter();
LayoutUnit& marginBeforeAlias = computedValues.m_margins.m_before;
LayoutUnit& marginAfterAlias = computedValues.m_margins.m_after;
Length logicalTop = style()->logicalTop();
Length logicalBottom = style()->logicalBottom();
/*-----------------------------------------------------------------------*\
* 1. The used value of 'height' is determined as for inline replaced
* elements.
\*-----------------------------------------------------------------------*/
// NOTE: This value of height is final in that the min/max height calculations
// are dealt with in computeReplacedHeight(). This means that the steps to produce
// correct max/min in the non-replaced version, are not necessary.
computedValues.m_extent = computeReplacedLogicalHeight() + borderAndPaddingLogicalHeight();
const LayoutUnit availableSpace = containerLogicalHeight - computedValues.m_extent;
/*-----------------------------------------------------------------------*\
* 2. If both 'top' and 'bottom' have the value 'auto', replace 'top'
* with the element's static position.
\*-----------------------------------------------------------------------*/
// see FIXME 1
computeBlockStaticDistance(logicalTop, logicalBottom, this, containerBlock);
/*-----------------------------------------------------------------------*\
* 3. If 'bottom' is 'auto', replace any 'auto' on 'margin-top' or
* 'margin-bottom' with '0'.
\*-----------------------------------------------------------------------*/
// FIXME: The spec. says that this step should only be taken when bottom is
// auto, but if only top is auto, this makes step 4 impossible.
if (logicalTop.isAuto() || logicalBottom.isAuto()) {
if (marginBefore.isAuto())
marginBefore.setValue(Fixed, 0);
if (marginAfter.isAuto())
marginAfter.setValue(Fixed, 0);
}
/*-----------------------------------------------------------------------*\
* 4. If at this point both 'margin-top' and 'margin-bottom' are still
* 'auto', solve the equation under the extra constraint that the two
* margins must get equal values.
\*-----------------------------------------------------------------------*/
LayoutUnit logicalTopValue;
LayoutUnit logicalBottomValue;
if (marginBefore.isAuto() && marginAfter.isAuto()) {
// 'top' and 'bottom' cannot be 'auto' due to step 2 and 3 combined.
ASSERT(!(logicalTop.isAuto() || logicalBottom.isAuto()));
logicalTopValue = valueForLength(logicalTop, containerLogicalHeight);
logicalBottomValue = valueForLength(logicalBottom, containerLogicalHeight);
LayoutUnit difference = availableSpace - (logicalTopValue + logicalBottomValue);
// NOTE: This may result in negative values.
marginBeforeAlias = difference / 2; // split the difference
marginAfterAlias = difference - marginBeforeAlias; // account for odd valued differences
/*-----------------------------------------------------------------------*\
* 5. If at this point there is only one 'auto' left, solve the equation
* for that value.
\*-----------------------------------------------------------------------*/
} else if (logicalTop.isAuto()) {
marginBeforeAlias = valueForLength(marginBefore, containerRelativeLogicalWidth);
marginAfterAlias = valueForLength(marginAfter, containerRelativeLogicalWidth);
logicalBottomValue = valueForLength(logicalBottom, containerLogicalHeight);
// Solve for 'top'
logicalTopValue = availableSpace - (logicalBottomValue + marginBeforeAlias + marginAfterAlias);
} else if (logicalBottom.isAuto()) {
marginBeforeAlias = valueForLength(marginBefore, containerRelativeLogicalWidth);
marginAfterAlias = valueForLength(marginAfter, containerRelativeLogicalWidth);
logicalTopValue = valueForLength(logicalTop, containerLogicalHeight);
// Solve for 'bottom'
// NOTE: It is not necessary to solve for 'bottom' because we don't ever
// use the value.
} else if (marginBefore.isAuto()) {
marginAfterAlias = valueForLength(marginAfter, containerRelativeLogicalWidth);
logicalTopValue = valueForLength(logicalTop, containerLogicalHeight);
logicalBottomValue = valueForLength(logicalBottom, containerLogicalHeight);
// Solve for 'margin-top'
marginBeforeAlias = availableSpace - (logicalTopValue + logicalBottomValue + marginAfterAlias);
} else if (marginAfter.isAuto()) {
marginBeforeAlias = valueForLength(marginBefore, containerRelativeLogicalWidth);
logicalTopValue = valueForLength(logicalTop, containerLogicalHeight);
logicalBottomValue = valueForLength(logicalBottom, containerLogicalHeight);
// Solve for 'margin-bottom'
marginAfterAlias = availableSpace - (logicalTopValue + logicalBottomValue + marginBeforeAlias);
} else {
// Nothing is 'auto', just calculate the values.
marginBeforeAlias = valueForLength(marginBefore, containerRelativeLogicalWidth);
marginAfterAlias = valueForLength(marginAfter, containerRelativeLogicalWidth);
logicalTopValue = valueForLength(logicalTop, containerLogicalHeight);
// NOTE: It is not necessary to solve for 'bottom' because we don't ever
// use the value.
}
/*-----------------------------------------------------------------------*\
* 6. If at this point the values are over-constrained, ignore the value
* for 'bottom' and solve for that value.
\*-----------------------------------------------------------------------*/
// NOTE: It is not necessary to do this step because we don't end up using
// the value of 'bottom' regardless of whether the values are over-constrained
// or not.
// Use computed values to calculate the vertical position.
LayoutUnit logicalTopPos = logicalTopValue + marginBeforeAlias;
computeLogicalTopPositionedOffset(logicalTopPos, this, computedValues.m_extent, containerBlock, containerLogicalHeight);
computedValues.m_position = logicalTopPos;
}
LayoutRect LayoutReplaced::replacedContentRect(const LayoutSize* overriddenIntrinsicSize) const
{
LayoutRect contentRect = contentBoxRect();
ObjectFit objectFit = style()->getObjectFit();
if (objectFit == ObjectFitFill && style()->objectPosition() == ComputedStyle::initialObjectPosition()) {
return contentRect;
}
// TODO(davve): intrinsicSize doubles as both intrinsic size and intrinsic ratio. In the case of
// SVG images this isn't correct since they can have intrinsic ratio but no intrinsic size. In
// order to maintain aspect ratio, the intrinsic size for SVG might be faked from the aspect
// ratio, see SVGImage::containerSize().
LayoutSize intrinsicSize = overriddenIntrinsicSize ? *overriddenIntrinsicSize : this->intrinsicSize();
if (!intrinsicSize.width() || !intrinsicSize.height())
return contentRect;
LayoutRect finalRect = contentRect;
switch (objectFit) {
case ObjectFitContain:
case ObjectFitScaleDown:
case ObjectFitCover:
finalRect.setSize(finalRect.size().fitToAspectRatio(intrinsicSize, objectFit == ObjectFitCover ? AspectRatioFitGrow : AspectRatioFitShrink));
if (objectFit != ObjectFitScaleDown || finalRect.width() <= intrinsicSize.width())
break;
// fall through
case ObjectFitNone:
finalRect.setSize(intrinsicSize);
break;
case ObjectFitFill:
break;
default:
ASSERT_NOT_REACHED();
}
LayoutUnit xOffset = minimumValueForLength(style()->objectPosition().x(), contentRect.width() - finalRect.width());
LayoutUnit yOffset = minimumValueForLength(style()->objectPosition().y(), contentRect.height() - finalRect.height());
finalRect.move(xOffset, yOffset);
return finalRect;
}
void LayoutReplaced::computeIntrinsicSizingInfo(IntrinsicSizingInfo& intrinsicSizingInfo) const
{
// If there's an embeddedReplacedContent() of a remote, referenced document available, this code-path should never be used.
ASSERT(!embeddedReplacedContent());
intrinsicSizingInfo.size = FloatSize(intrinsicLogicalWidth().toFloat(), intrinsicLogicalHeight().toFloat());
// Figure out if we need to compute an intrinsic ratio.
if (intrinsicSizingInfo.size.isEmpty() || !layoutObjectHasAspectRatio(this))
return;
intrinsicSizingInfo.aspectRatio = intrinsicSizingInfo.size;
}
static inline LayoutUnit resolveWidthForRatio(LayoutUnit height, const FloatSize& aspectRatio)
{
return LayoutUnit(height * aspectRatio.width() / aspectRatio.height());
}
static inline LayoutUnit resolveHeightForRatio(LayoutUnit width, const FloatSize& aspectRatio)
{
return LayoutUnit(width * aspectRatio.height() / aspectRatio.width());
}
LayoutUnit LayoutReplaced::computeConstrainedLogicalWidth(ShouldComputePreferred shouldComputePreferred) const
{
if (shouldComputePreferred == ComputePreferred)
return computeReplacedLogicalWidthRespectingMinMaxWidth(LayoutUnit(), ComputePreferred);
// The aforementioned 'constraint equation' used for block-level, non-replaced elements in normal flow:
// 'margin-left' + 'border-left-width' + 'padding-left' + 'width' + 'padding-right' + 'border-right-width' + 'margin-right' = width of containing block
LayoutUnit logicalWidth = containingBlock()->availableLogicalWidth();
// This solves above equation for 'width' (== logicalWidth).
LayoutUnit marginStart = minimumValueForLength(style()->marginStart(), logicalWidth);
LayoutUnit marginEnd = minimumValueForLength(style()->marginEnd(), logicalWidth);
logicalWidth = (logicalWidth - (marginStart + marginEnd + (size().width() - clientWidth()))).clampNegativeToZero();
return computeReplacedLogicalWidthRespectingMinMaxWidth(logicalWidth, shouldComputePreferred);
}
LayoutUnit LayoutReplaced::computeReplacedLogicalWidth(ShouldComputePreferred shouldComputePreferred) const
{
if (style()->logicalWidth().isSpecified() || style()->logicalWidth().isIntrinsic())
return computeReplacedLogicalWidthRespectingMinMaxWidth(computeReplacedLogicalWidthUsing(MainOrPreferredSize, style()->logicalWidth()), shouldComputePreferred);
LayoutReplaced* contentLayoutObject = embeddedReplacedContent();
// 10.3.2 Inline, replaced elements: http://www.w3.org/TR/CSS21/visudet.html#inline-replaced-width
IntrinsicSizingInfo intrinsicSizingInfo;
computeIntrinsicSizingInfoForReplacedContent(contentLayoutObject, intrinsicSizingInfo);
FloatSize constrainedSize = constrainIntrinsicSizeToMinMax(intrinsicSizingInfo);
if (style()->logicalWidth().isAuto()) {
bool computedHeightIsAuto = style()->logicalHeight().isAuto();
// If 'height' and 'width' both have computed values of 'auto' and the element also has an intrinsic width, then that intrinsic width is the used value of 'width'.
if (computedHeightIsAuto && intrinsicSizingInfo.hasWidth)
return computeReplacedLogicalWidthRespectingMinMaxWidth(LayoutUnit(constrainedSize.width()), shouldComputePreferred);
if (!intrinsicSizingInfo.aspectRatio.isEmpty()) {
// If 'height' and 'width' both have computed values of 'auto' and the element has no intrinsic width, but does have an intrinsic height and intrinsic ratio;
// or if 'width' has a computed value of 'auto', 'height' has some other computed value, and the element does have an intrinsic ratio; then the used value
// of 'width' is: (used height) * (intrinsic ratio)
if ((computedHeightIsAuto && !intrinsicSizingInfo.hasWidth && intrinsicSizingInfo.hasHeight) || !computedHeightIsAuto) {
LayoutUnit estimatedUsedWidth = intrinsicSizingInfo.hasWidth ? LayoutUnit(constrainedSize.width()) : computeConstrainedLogicalWidth(shouldComputePreferred);
LayoutUnit logicalHeight = computeReplacedLogicalHeight(estimatedUsedWidth);
return computeReplacedLogicalWidthRespectingMinMaxWidth(resolveWidthForRatio(logicalHeight, intrinsicSizingInfo.aspectRatio), shouldComputePreferred);
}
// If 'height' and 'width' both have computed values of 'auto' and the element has an intrinsic ratio but no intrinsic height or width, then the used value of
// 'width' is undefined in CSS 2.1. However, it is suggested that, if the containing block's width does not itself depend on the replaced element's width, then
// the used value of 'width' is calculated from the constraint equation used for block-level, non-replaced elements in normal flow.
if (computedHeightIsAuto && !intrinsicSizingInfo.hasWidth && !intrinsicSizingInfo.hasHeight)
return computeConstrainedLogicalWidth(shouldComputePreferred);
}
// Otherwise, if 'width' has a computed value of 'auto', and the element has an intrinsic width, then that intrinsic width is the used value of 'width'.
if (intrinsicSizingInfo.hasWidth)
return computeReplacedLogicalWidthRespectingMinMaxWidth(LayoutUnit(constrainedSize.width()), shouldComputePreferred);
// Otherwise, if 'width' has a computed value of 'auto', but none of the conditions above are met, then the used value of 'width' becomes 300px. If 300px is too
// wide to fit the device, UAs should use the width of the largest rectangle that has a 2:1 ratio and fits the device instead.
// Note: We fall through and instead return intrinsicLogicalWidth() here - to preserve existing WebKit behavior, which might or might not be correct, or desired.
// Changing this to return cDefaultWidth, will affect lots of test results. Eg. some tests assume that a blank <img> tag (which implies width/height=auto)
// has no intrinsic size, which is wrong per CSS 2.1, but matches our behavior since a long time.
}
return computeReplacedLogicalWidthRespectingMinMaxWidth(intrinsicLogicalWidth(), shouldComputePreferred);
}
LayoutUnit LayoutReplaced::computeReplacedLogicalHeight(LayoutUnit estimatedUsedWidth) const
{
// 10.5 Content height: the 'height' property: http://www.w3.org/TR/CSS21/visudet.html#propdef-height
if (hasReplacedLogicalHeight())
return computeReplacedLogicalHeightRespectingMinMaxHeight(computeReplacedLogicalHeightUsing(MainOrPreferredSize, style()->logicalHeight()));
LayoutReplaced* contentLayoutObject = embeddedReplacedContent();
// 10.6.2 Inline, replaced elements: http://www.w3.org/TR/CSS21/visudet.html#inline-replaced-height
IntrinsicSizingInfo intrinsicSizingInfo;
computeIntrinsicSizingInfoForReplacedContent(contentLayoutObject, intrinsicSizingInfo);
FloatSize constrainedSize = constrainIntrinsicSizeToMinMax(intrinsicSizingInfo);
bool widthIsAuto = style()->logicalWidth().isAuto();
// If 'height' and 'width' both have computed values of 'auto' and the element also has an intrinsic height, then that intrinsic height is the used value of 'height'.
if (widthIsAuto && intrinsicSizingInfo.hasHeight)
return computeReplacedLogicalHeightRespectingMinMaxHeight(LayoutUnit(constrainedSize.height()));
// Otherwise, if 'height' has a computed value of 'auto', and the element has an intrinsic ratio then the used value of 'height' is:
// (used width) / (intrinsic ratio)
if (!intrinsicSizingInfo.aspectRatio.isEmpty()) {
LayoutUnit usedWidth = estimatedUsedWidth ? estimatedUsedWidth : availableLogicalWidth();
return computeReplacedLogicalHeightRespectingMinMaxHeight(resolveHeightForRatio(usedWidth, intrinsicSizingInfo.aspectRatio));
}
// Otherwise, if 'height' has a computed value of 'auto', and the element has an intrinsic height, then that intrinsic height is the used value of 'height'.
if (intrinsicSizingInfo.hasHeight)
return computeReplacedLogicalHeightRespectingMinMaxHeight(LayoutUnit(constrainedSize.height()));
// Otherwise, if 'height' has a computed value of 'auto', but none of the conditions above are met, then the used value of 'height' must be set to the height
// of the largest rectangle that has a 2:1 ratio, has a height not greater than 150px, and has a width not greater than the device width.
return computeReplacedLogicalHeightRespectingMinMaxHeight(intrinsicLogicalHeight());
}
void LayoutReplaced::computeIntrinsicLogicalWidths(LayoutUnit& minLogicalWidth, LayoutUnit& maxLogicalWidth) const
{
minLogicalWidth = maxLogicalWidth = intrinsicLogicalWidth();
}
void LayoutReplaced::computePreferredLogicalWidths()
{
ASSERT(preferredLogicalWidthsDirty());
// We cannot resolve some logical width here (i.e. percent, fill-available or fit-content)
// as the available logical width may not be set on our containing block.
const Length& logicalWidth = style()->logicalWidth();
if (logicalWidth.hasPercent() || logicalWidth.isFillAvailable() || logicalWidth.isFitContent())
computeIntrinsicLogicalWidths(m_minPreferredLogicalWidth, m_maxPreferredLogicalWidth);
else
m_minPreferredLogicalWidth = m_maxPreferredLogicalWidth = computeReplacedLogicalWidth(ComputePreferred);
const ComputedStyle& styleToUse = styleRef();
if (styleToUse.logicalWidth().hasPercent() || styleToUse.logicalMaxWidth().hasPercent())
m_minPreferredLogicalWidth = LayoutUnit();
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();
}
PositionWithAffinity LayoutReplaced::positionForPoint(const LayoutPoint& point)
{
// FIXME: This code is buggy if the replaced element is relative positioned.
InlineBox* box = inlineBoxWrapper();
RootInlineBox* rootBox = box ? &box->root() : 0;
LayoutUnit top = rootBox ? rootBox->selectionTop() : logicalTop();
LayoutUnit bottom = rootBox ? rootBox->selectionBottom() : logicalBottom();
LayoutUnit blockDirectionPosition = isHorizontalWritingMode() ? point.y() + location().y() : point.x() + location().x();
LayoutUnit lineDirectionPosition = isHorizontalWritingMode() ? point.x() + location().x() : point.y() + location().y();
if (blockDirectionPosition < top)
return createPositionWithAffinity(caretMinOffset()); // coordinates are above
if (blockDirectionPosition >= bottom)
return createPositionWithAffinity(caretMaxOffset()); // coordinates are below
if (node()) {
if (lineDirectionPosition <= logicalLeft() + (logicalWidth() / 2))
return createPositionWithAffinity(0);
return createPositionWithAffinity(1);
}
return LayoutBox::positionForPoint(point);
}
LayoutRect LayoutReplaced::localSelectionRect() const
{
if (getSelectionState() == SelectionNone)
return LayoutRect();
if (!inlineBoxWrapper()) {
// We're a block-level replaced element. Just return our own dimensions.
return LayoutRect(LayoutPoint(), size());
}
RootInlineBox& root = inlineBoxWrapper()->root();
LayoutUnit newLogicalTop = root.block().style()->isFlippedBlocksWritingMode() ? inlineBoxWrapper()->logicalBottom() - root.selectionBottom() : root.selectionTop() - inlineBoxWrapper()->logicalTop();
if (root.block().style()->isHorizontalWritingMode())
return LayoutRect(LayoutUnit(), newLogicalTop, size().width(), root.selectionHeight());
return LayoutRect(newLogicalTop, LayoutUnit(), root.selectionHeight(), size().height());
}
void LayoutReplaced::setSelectionState(SelectionState state)
{
// The selection state for our containing block hierarchy is updated by the base class call.
LayoutBox::setSelectionState(state);
if (!inlineBoxWrapper())
return;
// We only include the space below the baseline in our layer's cached paint invalidation rect if the
// image is selected. Since the selection state has changed update the rect.
if (hasLayer()) {
LayoutRect rect = localOverflowRectForPaintInvalidation();
PaintLayer::mapRectToPaintInvalidationBacking(*this, containerForPaintInvalidation(), rect);
setPreviousPaintInvalidationRect(rect);
}
if (canUpdateSelectionOnRootLineBoxes())
inlineBoxWrapper()->root().setHasSelectedChildren(state != SelectionNone);
}
void LayoutReplaced::IntrinsicSizingInfo::transpose()
{
size = size.transposedSize();
aspectRatio = aspectRatio.transposedSize();
std::swap(hasWidth, hasHeight);
}
} // namespace blink