blob: 9460837de934d171b87138dd10dc175b49815f74 [file] [log] [blame]
/*
* Copyright (C) 1999 Lars Knoll (knoll@kde.org)
* (C) 1999 Antti Koivisto (koivisto@kde.org)
* (C) 2005 Allan Sandfeld Jensen (kde@carewolf.com)
* (C) 2005, 2006 Samuel Weinig (sam.weinig@gmail.com)
* Copyright (C) 2005, 2006, 2007, 2008, 2009, 2010 Apple Inc.
* All rights reserved.
* Copyright (C) 2013 Adobe Systems Incorporated. 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/LayoutBox.h"
#include "core/dom/Document.h"
#include "core/editing/EditingUtilities.h"
#include "core/frame/FrameView.h"
#include "core/frame/LocalFrame.h"
#include "core/frame/Settings.h"
#include "core/html/HTMLElement.h"
#include "core/html/HTMLFrameElementBase.h"
#include "core/html/HTMLFrameOwnerElement.h"
#include "core/input/EventHandler.h"
#include "core/layout/HitTestResult.h"
#include "core/layout/LayoutAnalyzer.h"
#include "core/layout/LayoutDeprecatedFlexibleBox.h"
#include "core/layout/LayoutFlexibleBox.h"
#include "core/layout/LayoutGrid.h"
#include "core/layout/LayoutInline.h"
#include "core/layout/LayoutListMarker.h"
#include "core/layout/LayoutMultiColumnFlowThread.h"
#include "core/layout/LayoutMultiColumnSpannerPlaceholder.h"
#include "core/layout/LayoutPart.h"
#include "core/layout/LayoutTableCell.h"
#include "core/layout/LayoutView.h"
#include "core/layout/api/LayoutAPIShim.h"
#include "core/layout/api/LayoutPartItem.h"
#include "core/layout/api/LineLayoutBlockFlow.h"
#include "core/layout/api/LineLayoutBox.h"
#include "core/layout/compositing/PaintLayerCompositor.h"
#include "core/layout/shapes/ShapeOutsideInfo.h"
#include "core/page/AutoscrollController.h"
#include "core/page/Page.h"
#include "core/page/scrolling/ScrollingCoordinator.h"
#include "core/page/scrolling/SnapCoordinator.h"
#include "core/paint/BackgroundImageGeometry.h"
#include "core/paint/BoxPaintInvalidator.h"
#include "core/paint/BoxPainter.h"
#include "core/paint/PaintLayer.h"
#include "core/style/ShadowList.h"
#include "platform/LengthFunctions.h"
#include "platform/geometry/DoubleRect.h"
#include "platform/geometry/FloatQuad.h"
#include "platform/geometry/FloatRoundedRect.h"
#include "wtf/PtrUtil.h"
#include <algorithm>
#include <math.h>
namespace blink {
// Used by flexible boxes when flexing this element and by table cells.
typedef WTF::HashMap<const LayoutBox*, LayoutUnit> OverrideSizeMap;
static OverrideSizeMap* gExtraInlineOffsetMap = nullptr;
static OverrideSizeMap* gExtraBlockOffsetMap = nullptr;
// Size of border belt for autoscroll. When mouse pointer in border belt,
// autoscroll is started.
static const int autoscrollBeltSize = 20;
static const unsigned backgroundObscurationTestMaxDepth = 4;
struct SameSizeAsLayoutBox : public LayoutBoxModelObject {
LayoutRect frameRect;
LayoutSize previousSize;
LayoutUnit intrinsicContentLogicalHeight;
LayoutRectOutsets marginBoxOutsets;
LayoutUnit preferredLogicalWidth[2];
void* pointers[3];
};
static_assert(sizeof(LayoutBox) == sizeof(SameSizeAsLayoutBox),
"LayoutBox should stay small");
LayoutBox::LayoutBox(ContainerNode* node)
: LayoutBoxModelObject(node),
m_intrinsicContentLogicalHeight(-1),
m_minPreferredLogicalWidth(-1),
m_maxPreferredLogicalWidth(-1),
m_inlineBoxWrapper(nullptr) {
setIsBox();
}
PaintLayerType LayoutBox::layerTypeRequired() const {
// hasAutoZIndex only returns true if the element is positioned or a flex-item
// since position:static elements that are not flex-items get their z-index
// coerced to auto.
if (isPositioned() || createsGroup() || hasClipPath() ||
hasTransformRelatedProperty() || style()->hasCompositorProxy() ||
hasHiddenBackface() || hasReflection() || style()->specifiesColumns() ||
style()->isStackingContext() ||
style()->shouldCompositeForCurrentAnimations())
return NormalPaintLayer;
if (hasOverflowClip())
return OverflowClipPaintLayer;
return NoPaintLayer;
}
void LayoutBox::willBeDestroyed() {
clearOverrideSize();
clearContainingBlockOverrideSize();
clearExtraInlineAndBlockOffests();
if (isOutOfFlowPositioned())
LayoutBlock::removePositionedObject(this);
removeFromPercentHeightContainer();
if (isOrthogonalWritingModeRoot() && !documentBeingDestroyed())
unmarkOrthogonalWritingModeRoot();
ShapeOutsideInfo::removeInfo(*this);
LayoutBoxModelObject::willBeDestroyed();
}
void LayoutBox::insertedIntoTree() {
LayoutBoxModelObject::insertedIntoTree();
addScrollSnapMapping();
if (isOrthogonalWritingModeRoot())
markOrthogonalWritingModeRoot();
}
void LayoutBox::willBeRemovedFromTree() {
if (!documentBeingDestroyed() && isOrthogonalWritingModeRoot())
unmarkOrthogonalWritingModeRoot();
clearScrollSnapMapping();
LayoutBoxModelObject::willBeRemovedFromTree();
}
void LayoutBox::removeFloatingOrPositionedChildFromBlockLists() {
DCHECK(isFloatingOrOutOfFlowPositioned());
if (documentBeingDestroyed())
return;
if (isFloating()) {
LayoutBlockFlow* parentBlockFlow = nullptr;
for (LayoutObject* curr = parent(); curr; curr = curr->parent()) {
if (curr->isLayoutBlockFlow()) {
LayoutBlockFlow* currBlockFlow = toLayoutBlockFlow(curr);
if (!parentBlockFlow || currBlockFlow->containsFloat(this))
parentBlockFlow = currBlockFlow;
}
}
if (parentBlockFlow) {
parentBlockFlow->markSiblingsWithFloatsForLayout(this);
parentBlockFlow->markAllDescendantsWithFloatsForLayout(this, false);
}
}
if (isOutOfFlowPositioned())
LayoutBlock::removePositionedObject(this);
}
void LayoutBox::styleWillChange(StyleDifference diff,
const ComputedStyle& newStyle) {
const ComputedStyle* oldStyle = style();
if (oldStyle) {
LayoutFlowThread* flowThread = flowThreadContainingBlock();
if (flowThread && flowThread != this)
flowThread->flowThreadDescendantStyleWillChange(this, diff, newStyle);
// The background of the root element or the body element could propagate up
// to the canvas. Just dirty the entire canvas when our style changes
// substantially.
if ((diff.needsFullPaintInvalidation() || diff.needsLayout()) && node() &&
(isHTMLHtmlElement(*node()) || isHTMLBodyElement(*node()))) {
view()->setShouldDoFullPaintInvalidation();
if (oldStyle->hasEntirelyFixedBackground() !=
newStyle.hasEntirelyFixedBackground())
view()->compositor()->setNeedsUpdateFixedBackground();
}
// When a layout hint happens and an object's position style changes, we
// have to do a layout to dirty the layout tree using the old position
// value now.
if (diff.needsFullLayout() && parent() &&
oldStyle->position() != newStyle.position()) {
if (!oldStyle->hasOutOfFlowPosition() &&
newStyle.hasOutOfFlowPosition()) {
// We're about to go out of flow. Before that takes place, we need to
// mark the current containing block chain for preferred widths
// recalculation.
setNeedsLayoutAndPrefWidthsRecalc(
LayoutInvalidationReason::StyleChange);
} else {
markContainerChainForLayout();
}
if (oldStyle->position() == EPosition::kStatic)
setShouldDoFullPaintInvalidation();
else if (newStyle.hasOutOfFlowPosition())
parent()->setChildNeedsLayout();
if (isFloating() && !isOutOfFlowPositioned() &&
newStyle.hasOutOfFlowPosition())
removeFloatingOrPositionedChildFromBlockLists();
}
// FIXME: This branch runs when !oldStyle, which means that layout was never
// called so what's the point in invalidating the whole view that we never
// painted?
} else if (isBody()) {
view()->setShouldDoFullPaintInvalidation();
}
LayoutBoxModelObject::styleWillChange(diff, newStyle);
}
void LayoutBox::styleDidChange(StyleDifference diff,
const ComputedStyle* oldStyle) {
// Horizontal writing mode definition is updated in LayoutBoxModelObject::
// updateFromStyle, (as part of the LayoutBoxModelObject::styleDidChange call
// below). So, we can safely cache the horizontal writing mode value before
// style change here.
bool oldHorizontalWritingMode = isHorizontalWritingMode();
LayoutBoxModelObject::styleDidChange(diff, oldStyle);
if (isFloatingOrOutOfFlowPositioned() && oldStyle &&
!oldStyle->isFloating() && !oldStyle->hasOutOfFlowPosition() &&
parent() && parent()->isLayoutBlockFlow())
toLayoutBlockFlow(parent())->childBecameFloatingOrOutOfFlow(this);
const ComputedStyle& newStyle = styleRef();
if (needsLayout() && oldStyle)
removeFromPercentHeightContainer();
if (oldHorizontalWritingMode != isHorizontalWritingMode()) {
if (oldStyle) {
if (isOrthogonalWritingModeRoot())
markOrthogonalWritingModeRoot();
else
unmarkOrthogonalWritingModeRoot();
}
clearPercentHeightDescendants();
}
// If our zoom factor changes and we have a defined scrollLeft/Top, we need to
// adjust that value into the new zoomed coordinate space. Note that the new
// scroll offset may be outside the normal min/max range of the scrollable
// area, which is weird but OK, because the scrollable area will update its
// min/max in updateAfterLayout().
if (hasOverflowClip() && oldStyle &&
oldStyle->effectiveZoom() != newStyle.effectiveZoom()) {
PaintLayerScrollableArea* scrollableArea = this->getScrollableArea();
DCHECK(scrollableArea);
// We use getScrollOffset() rather than scrollPosition(), because scroll
// offset is the distance from the beginning of flow for the box, which is
// the dimension we want to preserve.
ScrollOffset oldOffset = scrollableArea->getScrollOffset();
if (oldOffset.width() || oldOffset.height()) {
ScrollOffset newOffset = oldOffset.scaledBy(newStyle.effectiveZoom() /
oldStyle->effectiveZoom());
scrollableArea->setScrollOffsetUnconditionally(newOffset);
}
}
// Our opaqueness might have changed without triggering layout.
if (diff.needsFullPaintInvalidation()) {
LayoutObject* parentToInvalidate = parent();
for (unsigned i = 0;
i < backgroundObscurationTestMaxDepth && parentToInvalidate; ++i) {
parentToInvalidate->invalidateBackgroundObscurationStatus();
parentToInvalidate = parentToInvalidate->parent();
}
}
if (isDocumentElement() || isBody()) {
document().view()->recalculateScrollbarOverlayColorTheme(
document().view()->documentBackgroundColor());
document().view()->recalculateCustomScrollbarStyle();
if (LayoutView* layoutView = view()) {
if (PaintLayerScrollableArea* scrollableArea =
layoutView->getScrollableArea()) {
if (scrollableArea->horizontalScrollbar() &&
scrollableArea->horizontalScrollbar()->isCustomScrollbar())
scrollableArea->horizontalScrollbar()->styleChanged();
if (scrollableArea->verticalScrollbar() &&
scrollableArea->verticalScrollbar()->isCustomScrollbar())
scrollableArea->verticalScrollbar()->styleChanged();
}
}
}
updateShapeOutsideInfoAfterStyleChange(*style(), oldStyle);
updateGridPositionAfterStyleChange(oldStyle);
// When we're no longer a flex item because we're now absolutely positioned,
// we need to clear the override size so we're not affected by it anymore.
// This technically covers too many cases (even when out-of-flow did not
// change) but that should be harmless.
if (isOutOfFlowPositioned() && parent() &&
parent()->styleRef().isDisplayFlexibleOrGridBox())
clearOverrideSize();
if (LayoutMultiColumnSpannerPlaceholder* placeholder =
this->spannerPlaceholder())
placeholder->layoutObjectInFlowThreadStyleDidChange(oldStyle);
updateBackgroundAttachmentFixedStatusAfterStyleChange();
if (oldStyle) {
LayoutFlowThread* flowThread = flowThreadContainingBlock();
if (flowThread && flowThread != this)
flowThread->flowThreadDescendantStyleDidChange(this, diff, *oldStyle);
updateScrollSnapMappingAfterStyleChange(&newStyle, oldStyle);
if (RuntimeEnabledFeatures::slimmingPaintInvalidationEnabled() &&
shouldClipOverflow()) {
// The overflow clip paint property depends on border sizes through
// overflowClipRect(), and border radii, so we update properties on
// border size or radii change.
if (!oldStyle->border().sizeEquals(newStyle.border()) ||
!oldStyle->border().radiiEqual(newStyle.border()))
setNeedsPaintPropertyUpdate();
}
}
if (diff.transformChanged()) {
if (ScrollingCoordinator* scrollingCoordinator =
document().frame()->page()->scrollingCoordinator())
scrollingCoordinator->notifyTransformChanged(*this);
}
// Non-atomic inlines should be LayoutInline or LayoutText, not LayoutBox.
DCHECK(!isInline() || isAtomicInlineLevel());
}
void LayoutBox::updateBackgroundAttachmentFixedStatusAfterStyleChange() {
if (!frameView())
return;
// On low-powered/mobile devices, preventing blitting on a scroll can cause
// noticeable delays when scrolling a page with a fixed background image. As
// an optimization, assuming there are no fixed positoned elements on the
// page, we can acclerate scrolling (via blitting) if we ignore the CSS
// property "background-attachment: fixed".
bool ignoreFixedBackgroundAttachment =
RuntimeEnabledFeatures::fastMobileScrollingEnabled();
if (ignoreFixedBackgroundAttachment)
return;
// An object needs to be repainted on frame scroll when it has background-
// attachment:fixed. LayoutView is responsible for painting root background,
// thus the root element (and the body element if html element has no
// background) skips painting backgrounds.
bool isBackgroundAttachmentFixedObject = !isDocumentElement() &&
!backgroundStolenForBeingBody() &&
styleRef().hasFixedBackgroundImage();
if (isLayoutView() &&
view()->compositor()->supportsFixedRootBackgroundCompositing()) {
if (styleRef().hasEntirelyFixedBackground())
isBackgroundAttachmentFixedObject = false;
}
setIsBackgroundAttachmentFixedObject(isBackgroundAttachmentFixedObject);
}
void LayoutBox::updateShapeOutsideInfoAfterStyleChange(
const ComputedStyle& style,
const ComputedStyle* oldStyle) {
const ShapeValue* shapeOutside = style.shapeOutside();
const ShapeValue* oldShapeOutside =
oldStyle ? oldStyle->shapeOutside()
: ComputedStyle::initialShapeOutside();
Length shapeMargin = style.shapeMargin();
Length oldShapeMargin =
oldStyle ? oldStyle->shapeMargin() : ComputedStyle::initialShapeMargin();
float shapeImageThreshold = style.shapeImageThreshold();
float oldShapeImageThreshold =
oldStyle ? oldStyle->shapeImageThreshold()
: ComputedStyle::initialShapeImageThreshold();
// FIXME: A future optimization would do a deep comparison for equality. (bug
// 100811)
if (shapeOutside == oldShapeOutside && shapeMargin == oldShapeMargin &&
shapeImageThreshold == oldShapeImageThreshold)
return;
if (!shapeOutside)
ShapeOutsideInfo::removeInfo(*this);
else
ShapeOutsideInfo::ensureInfo(*this).markShapeAsDirty();
if (shapeOutside || shapeOutside != oldShapeOutside)
markShapeOutsideDependentsForLayout();
}
void LayoutBox::updateGridPositionAfterStyleChange(
const ComputedStyle* oldStyle) {
if (!oldStyle || !parent() || !parent()->isLayoutGrid())
return;
if (oldStyle->gridColumnStart() == style()->gridColumnStart() &&
oldStyle->gridColumnEnd() == style()->gridColumnEnd() &&
oldStyle->gridRowStart() == style()->gridRowStart() &&
oldStyle->gridRowEnd() == style()->gridRowEnd() &&
oldStyle->order() == style()->order() &&
oldStyle->hasOutOfFlowPosition() == style()->hasOutOfFlowPosition())
return;
// Positioned items don't participate on the layout of the grid,
// so we don't need to mark the grid as dirty if they change positions.
if (oldStyle->hasOutOfFlowPosition() && style()->hasOutOfFlowPosition())
return;
// It should be possible to not dirty the grid in some cases (like moving an
// explicitly placed grid item).
// For now, it's more simple to just always recompute the grid.
toLayoutGrid(parent())->dirtyGrid();
}
void LayoutBox::updateScrollSnapMappingAfterStyleChange(
const ComputedStyle* newStyle,
const ComputedStyle* oldStyle) {
SnapCoordinator* snapCoordinator = document().snapCoordinator();
if (!snapCoordinator)
return;
// Scroll snap type has no effect on the viewport defining element instead
// they are handled by the LayoutView.
bool allowsSnapContainer = node() != document().viewportDefiningElement();
ScrollSnapType oldSnapType =
oldStyle ? oldStyle->getScrollSnapType() : ScrollSnapTypeNone;
ScrollSnapType newSnapType = newStyle && allowsSnapContainer
? newStyle->getScrollSnapType()
: ScrollSnapTypeNone;
if (oldSnapType != newSnapType)
snapCoordinator->snapContainerDidChange(*this, newSnapType);
Vector<LengthPoint> emptyVector;
const Vector<LengthPoint>& oldSnapCoordinate =
oldStyle ? oldStyle->scrollSnapCoordinate() : emptyVector;
const Vector<LengthPoint>& newSnapCoordinate =
newStyle ? newStyle->scrollSnapCoordinate() : emptyVector;
if (oldSnapCoordinate != newSnapCoordinate)
snapCoordinator->snapAreaDidChange(*this, newSnapCoordinate);
}
void LayoutBox::addScrollSnapMapping() {
updateScrollSnapMappingAfterStyleChange(style(), nullptr);
}
void LayoutBox::clearScrollSnapMapping() {
updateScrollSnapMappingAfterStyleChange(nullptr, style());
}
void LayoutBox::updateFromStyle() {
LayoutBoxModelObject::updateFromStyle();
const ComputedStyle& styleToUse = styleRef();
setFloating(!isOutOfFlowPositioned() && styleToUse.isFloating());
setHasTransformRelatedProperty(styleToUse.hasTransformRelatedProperty());
setHasReflection(styleToUse.boxReflect());
}
void LayoutBox::layout() {
DCHECK(needsLayout());
LayoutAnalyzer::Scope analyzer(*this);
LayoutObject* child = slowFirstChild();
if (!child) {
clearNeedsLayout();
return;
}
LayoutState state(*this);
while (child) {
child->layoutIfNeeded();
DCHECK(!child->needsLayout());
child = child->nextSibling();
}
invalidateBackgroundObscurationStatus();
clearNeedsLayout();
}
// More IE extensions. clientWidth and clientHeight represent the interior of
// an object excluding border and scrollbar.
DISABLE_CFI_PERF
LayoutUnit LayoutBox::clientWidth() const {
// We need to clamp negative values. The scrollbar may be wider than the
// padding box. Another reason: While border side values are currently limited
// to 2^20px (a recent change in the code), if this limit is raised again in
// the future, we'd have ill effects of saturated arithmetic otherwise.
return (m_frameRect.width() - borderLeft() - borderRight() -
verticalScrollbarWidth())
.clampNegativeToZero();
}
DISABLE_CFI_PERF
LayoutUnit LayoutBox::clientHeight() const {
// We need to clamp negative values. The scrollbar may be wider than the
// padding box. Another reason: While border side values are currently limited
// to 2^20px (a recent change in the code), if this limit is raised again in
// the future, we'd have ill effects of saturated arithmetic otherwise.
return (m_frameRect.height() - borderTop() - borderBottom() -
horizontalScrollbarHeight())
.clampNegativeToZero();
}
int LayoutBox::pixelSnappedClientWidth() const {
return snapSizeToPixel(clientWidth(), location().x() + clientLeft());
}
DISABLE_CFI_PERF
int LayoutBox::pixelSnappedClientHeight() const {
return snapSizeToPixel(clientHeight(), location().y() + clientTop());
}
int LayoutBox::pixelSnappedOffsetWidth(const Element*) const {
return snapSizeToPixel(offsetWidth(), location().x() + clientLeft());
}
int LayoutBox::pixelSnappedOffsetHeight(const Element*) const {
return snapSizeToPixel(offsetHeight(), location().y() + clientTop());
}
LayoutUnit LayoutBox::scrollWidth() const {
if (hasOverflowClip())
return getScrollableArea()->scrollWidth();
// For objects with visible overflow, this matches IE.
// FIXME: Need to work right with writing modes.
if (style()->isLeftToRightDirection())
return std::max(clientWidth(), layoutOverflowRect().maxX() - borderLeft());
return clientWidth() -
std::min(LayoutUnit(), layoutOverflowRect().x() - borderLeft());
}
LayoutUnit LayoutBox::scrollHeight() const {
if (hasOverflowClip())
return getScrollableArea()->scrollHeight();
// For objects with visible overflow, this matches IE.
// FIXME: Need to work right with writing modes.
return std::max(clientHeight(), layoutOverflowRect().maxY() - borderTop());
}
LayoutUnit LayoutBox::scrollLeft() const {
return hasOverflowClip()
? LayoutUnit(getScrollableArea()->scrollPosition().x())
: LayoutUnit();
}
LayoutUnit LayoutBox::scrollTop() const {
return hasOverflowClip()
? LayoutUnit(getScrollableArea()->scrollPosition().y())
: LayoutUnit();
}
int LayoutBox::pixelSnappedScrollWidth() const {
return snapSizeToPixel(scrollWidth(), location().x() + clientLeft());
}
int LayoutBox::pixelSnappedScrollHeight() const {
if (hasOverflowClip())
return snapSizeToPixel(getScrollableArea()->scrollHeight(),
location().y() + clientTop());
// For objects with visible overflow, this matches IE.
// FIXME: Need to work right with writing modes.
return snapSizeToPixel(scrollHeight(), location().y() + clientTop());
}
void LayoutBox::setScrollLeft(LayoutUnit newLeft) {
// This doesn't hit in any tests, but since the equivalent code in
// setScrollTop does, presumably this code does as well.
DisableCompositingQueryAsserts disabler;
if (!hasOverflowClip())
return;
PaintLayerScrollableArea* scrollableArea = getScrollableArea();
FloatPoint newPosition(newLeft.toFloat(),
scrollableArea->scrollPosition().y());
scrollableArea->scrollToAbsolutePosition(newPosition, ScrollBehaviorAuto);
}
void LayoutBox::setScrollTop(LayoutUnit newTop) {
// Hits in
// compositing/overflow/do-not-assert-on-invisible-composited-layers.html
DisableCompositingQueryAsserts disabler;
if (!hasOverflowClip())
return;
PaintLayerScrollableArea* scrollableArea = getScrollableArea();
FloatPoint newPosition(scrollableArea->scrollPosition().x(),
newTop.toFloat());
scrollableArea->scrollToAbsolutePosition(newPosition, ScrollBehaviorAuto);
}
void LayoutBox::scrollToPosition(const FloatPoint& position,
ScrollBehavior scrollBehavior) {
// This doesn't hit in any tests, but since the equivalent code in
// setScrollTop does, presumably this code does as well.
DisableCompositingQueryAsserts disabler;
if (!hasOverflowClip())
return;
getScrollableArea()->scrollToAbsolutePosition(position, scrollBehavior);
}
// Returns true iff we are attempting an autoscroll inside an iframe with
// scrolling="no".
static bool isDisallowedAutoscroll(HTMLFrameOwnerElement* ownerElement,
FrameView* frameView) {
if (ownerElement && isHTMLFrameElementBase(*ownerElement)) {
HTMLFrameElementBase* frameElementBase =
toHTMLFrameElementBase(ownerElement);
if (Page* page = frameView->frame().page()) {
return page->autoscrollController().autoscrollInProgress() &&
frameElementBase->scrollingMode() == ScrollbarAlwaysOff;
}
}
return false;
}
void LayoutBox::scrollRectToVisible(const LayoutRect& rect,
const ScrollAlignment& alignX,
const ScrollAlignment& alignY,
ScrollType scrollType,
bool makeVisibleInVisualViewport) {
DCHECK(scrollType == ProgrammaticScroll || scrollType == UserScroll);
// Presumably the same issue as in setScrollTop. See crbug.com/343132.
DisableCompositingQueryAsserts disabler;
LayoutRect rectToScroll = rect;
if (rectToScroll.width() <= 0)
rectToScroll.setWidth(LayoutUnit(1));
if (rectToScroll.height() <= 0)
rectToScroll.setHeight(LayoutUnit(1));
LayoutBox* parentBox = nullptr;
LayoutRect newRect = rectToScroll;
bool restrictedByLineClamp = false;
if (containingBlock()) {
parentBox = containingBlock();
restrictedByLineClamp = !containingBlock()->style()->lineClamp().isNone();
}
if (hasOverflowClip() && !restrictedByLineClamp) {
// Don't scroll to reveal an overflow layer that is restricted by the
// -webkit-line-clamp property. This will prevent us from revealing text
// hidden by the slider in Safari RSS.
// TODO(eae): We probably don't need this any more as we don't share any
// code with the Safari RSS reeder.
newRect = getScrollableArea()->scrollIntoView(rectToScroll, alignX, alignY,
scrollType);
if (newRect.isEmpty())
return;
} else if (!parentBox && canBeProgramaticallyScrolled()) {
if (FrameView* frameView = this->frameView()) {
HTMLFrameOwnerElement* ownerElement = document().localOwner();
if (!isDisallowedAutoscroll(ownerElement, frameView)) {
if (makeVisibleInVisualViewport) {
frameView->getScrollableArea()->scrollIntoView(rectToScroll, alignX,
alignY, scrollType);
} else {
frameView->layoutViewportScrollableArea()->scrollIntoView(
rectToScroll, alignX, alignY, scrollType);
}
if (ownerElement && ownerElement->layoutObject()) {
if (frameView->safeToPropagateScrollToParent()) {
parentBox = ownerElement->layoutObject()->enclosingBox();
LayoutView* parentView = ownerElement->layoutObject()->view();
newRect = enclosingLayoutRect(
view()
->localToAncestorQuad(
FloatRect(rectToScroll), parentView,
UseTransforms | TraverseDocumentBoundaries)
.boundingBox());
} else {
parentBox = nullptr;
}
}
}
}
}
// If we are fixed-position and stick to the viewport, it is useless to
// scroll the parent.
if (style()->position() == EPosition::kFixed &&
containerForFixedPosition() == view()) {
return;
}
if (frame()->page()->autoscrollController().autoscrollInProgress())
parentBox = enclosingScrollableBox();
if (parentBox)
parentBox->scrollRectToVisible(newRect, alignX, alignY, scrollType,
makeVisibleInVisualViewport);
}
void LayoutBox::absoluteRects(Vector<IntRect>& rects,
const LayoutPoint& accumulatedOffset) const {
rects.push_back(pixelSnappedIntRect(accumulatedOffset, size()));
}
void LayoutBox::absoluteQuads(Vector<FloatQuad>& quads,
MapCoordinatesFlags mode) const {
if (LayoutFlowThread* flowThread = flowThreadContainingBlock()) {
flowThread->absoluteQuadsForDescendant(*this, quads, mode);
return;
}
quads.push_back(
localToAbsoluteQuad(FloatRect(0, 0, m_frameRect.width().toFloat(),
m_frameRect.height().toFloat()),
mode));
}
FloatRect LayoutBox::localBoundingBoxRectForAccessibility() const {
return FloatRect(0, 0, m_frameRect.width().toFloat(),
m_frameRect.height().toFloat());
}
void LayoutBox::updateLayerTransformAfterLayout() {
// Transform-origin depends on box size, so we need to update the layer
// transform after layout.
if (hasLayer())
layer()->updateTransformationMatrix();
}
LayoutUnit LayoutBox::logicalHeightWithVisibleOverflow() const {
if (!m_overflow || hasOverflowClip())
return logicalHeight();
LayoutRect overflow = layoutOverflowRect();
if (style()->isHorizontalWritingMode())
return overflow.maxY();
return overflow.maxX();
}
LayoutUnit LayoutBox::constrainLogicalWidthByMinMax(LayoutUnit logicalWidth,
LayoutUnit availableWidth,
LayoutBlock* cb) const {
const ComputedStyle& styleToUse = styleRef();
if (!styleToUse.logicalMaxWidth().isMaxSizeNone())
logicalWidth =
std::min(logicalWidth,
computeLogicalWidthUsing(MaxSize, styleToUse.logicalMaxWidth(),
availableWidth, cb));
return std::max(logicalWidth, computeLogicalWidthUsing(
MinSize, styleToUse.logicalMinWidth(),
availableWidth, cb));
}
LayoutUnit LayoutBox::constrainLogicalHeightByMinMax(
LayoutUnit logicalHeight,
LayoutUnit intrinsicContentHeight) const {
const ComputedStyle& styleToUse = styleRef();
if (!styleToUse.logicalMaxHeight().isMaxSizeNone()) {
LayoutUnit maxH = computeLogicalHeightUsing(
MaxSize, styleToUse.logicalMaxHeight(), intrinsicContentHeight);
if (maxH != -1)
logicalHeight = std::min(logicalHeight, maxH);
}
return std::max(logicalHeight, computeLogicalHeightUsing(
MinSize, styleToUse.logicalMinHeight(),
intrinsicContentHeight));
}
LayoutUnit LayoutBox::constrainContentBoxLogicalHeightByMinMax(
LayoutUnit logicalHeight,
LayoutUnit intrinsicContentHeight) const {
// If the min/max height and logical height are both percentages we take
// advantage of already knowing the current resolved percentage height
// to avoid recursing up through our containing blocks again to determine it.
const ComputedStyle& styleToUse = styleRef();
if (!styleToUse.logicalMaxHeight().isMaxSizeNone()) {
if (styleToUse.logicalMaxHeight().type() == Percent &&
styleToUse.logicalHeight().type() == Percent) {
LayoutUnit availableLogicalHeight(
logicalHeight / styleToUse.logicalHeight().value() * 100);
logicalHeight =
std::min(logicalHeight, valueForLength(styleToUse.logicalMaxHeight(),
availableLogicalHeight));
} else {
LayoutUnit maxHeight(computeContentLogicalHeight(
MaxSize, styleToUse.logicalMaxHeight(), intrinsicContentHeight));
if (maxHeight != -1)
logicalHeight = std::min(logicalHeight, maxHeight);
}
}
if (styleToUse.logicalMinHeight().type() == Percent &&
styleToUse.logicalHeight().type() == Percent) {
LayoutUnit availableLogicalHeight(logicalHeight /
styleToUse.logicalHeight().value() * 100);
logicalHeight = std::max(
logicalHeight,
valueForLength(styleToUse.logicalMinHeight(), availableLogicalHeight));
} else {
logicalHeight = std::max(
logicalHeight,
computeContentLogicalHeight(MinSize, styleToUse.logicalMinHeight(),
intrinsicContentHeight));
}
return logicalHeight;
}
void LayoutBox::setLocationAndUpdateOverflowControlsIfNeeded(
const LayoutPoint& location) {
if (hasOverflowClip()) {
IntSize oldPixelSnappedBorderRectSize = pixelSnappedBorderBoxRect().size();
setLocation(location);
if (pixelSnappedBorderBoxRect().size() != oldPixelSnappedBorderRectSize)
getScrollableArea()->updateAfterLayout();
return;
}
setLocation(location);
}
IntRect LayoutBox::absoluteContentBox() const {
// This is wrong with transforms and flipped writing modes.
IntRect rect = pixelSnappedIntRect(contentBoxRect());
FloatPoint absPos = localToAbsolute();
rect.move(absPos.x(), absPos.y());
return rect;
}
IntSize LayoutBox::absoluteContentBoxOffset() const {
IntPoint offset = roundedIntPoint(contentBoxOffset());
FloatPoint absPos = localToAbsolute();
offset.move(absPos.x(), absPos.y());
return toIntSize(offset);
}
FloatQuad LayoutBox::absoluteContentQuad(MapCoordinatesFlags flags) const {
LayoutRect rect = contentBoxRect();
return localToAbsoluteQuad(FloatRect(rect), flags);
}
LayoutRect LayoutBox::backgroundRect(BackgroundRectType rectType) const {
EFillBox backgroundBox = TextFillBox;
// Find the largest background rect of the given opaqueness.
if (const FillLayer* current = &(style()->backgroundLayers())) {
do {
const FillLayer* cur = current;
current = current->next();
if (rectType == BackgroundKnownOpaqueRect) {
if (cur->blendMode() != WebBlendModeNormal ||
cur->composite() != CompositeSourceOver)
continue;
bool layerKnownOpaque = false;
// Check if the image is opaque and fills the clip.
if (const StyleImage* image = cur->image()) {
if ((cur->repeatX() == RepeatFill || cur->repeatX() == RoundFill) &&
(cur->repeatY() == RepeatFill || cur->repeatY() == RoundFill) &&
image->knownToBeOpaque(*this)) {
layerKnownOpaque = true;
}
}
// The background color is painted into the last layer.
if (!cur->next()) {
Color backgroundColor = resolveColor(CSSPropertyBackgroundColor);
if (!backgroundColor.hasAlpha())
layerKnownOpaque = true;
}
// If neither the image nor the color are opaque then skip this layer.
if (!layerKnownOpaque)
continue;
}
EFillBox currentClip = cur->clip();
// Restrict clip if attachment is local.
if (currentClip == BorderFillBox &&
cur->attachment() == LocalBackgroundAttachment)
currentClip = PaddingFillBox;
// If we're asking for the clip rect, a content-box clipped fill layer can
// be scrolled into the padding box of the overflow container.
if (rectType == BackgroundClipRect && currentClip == ContentFillBox &&
cur->attachment() == LocalBackgroundAttachment) {
currentClip = PaddingFillBox;
}
backgroundBox = enclosingFillBox(backgroundBox, currentClip);
} while (current);
}
switch (backgroundBox) {
case BorderFillBox:
return borderBoxRect();
break;
case PaddingFillBox:
return paddingBoxRect();
break;
case ContentFillBox:
return contentBoxRect();
break;
default:
break;
}
return LayoutRect();
}
void LayoutBox::addOutlineRects(Vector<LayoutRect>& rects,
const LayoutPoint& additionalOffset,
IncludeBlockVisualOverflowOrNot) const {
rects.push_back(LayoutRect(additionalOffset, size()));
}
bool LayoutBox::canResize() const {
// We need a special case for <iframe> because they never have
// hasOverflowClip(). However, they do "implicitly" clip their contents, so
// we want to allow resizing them also.
return (hasOverflowClip() || isLayoutIFrame()) &&
style()->resize() != RESIZE_NONE;
}
void LayoutBox::addLayerHitTestRects(LayerHitTestRects& layerRects,
const PaintLayer* currentLayer,
const LayoutPoint& layerOffset,
const LayoutRect& containerRect) const {
LayoutPoint adjustedLayerOffset = layerOffset + locationOffset();
LayoutBoxModelObject::addLayerHitTestRects(
layerRects, currentLayer, adjustedLayerOffset, containerRect);
}
void LayoutBox::computeSelfHitTestRects(Vector<LayoutRect>& rects,
const LayoutPoint& layerOffset) const {
if (!size().isEmpty())
rects.push_back(LayoutRect(layerOffset, size()));
}
int LayoutBox::verticalScrollbarWidth() const {
if (!hasOverflowClip() || style()->overflowY() == EOverflow::kOverlay)
return 0;
return getScrollableArea()->verticalScrollbarWidth();
}
int LayoutBox::horizontalScrollbarHeight() const {
if (!hasOverflowClip() || style()->overflowX() == EOverflow::kOverlay)
return 0;
return getScrollableArea()->horizontalScrollbarHeight();
}
ScrollResult LayoutBox::scroll(ScrollGranularity granularity,
const FloatSize& delta) {
// Presumably the same issue as in setScrollTop. See crbug.com/343132.
DisableCompositingQueryAsserts disabler;
if (!getScrollableArea())
return ScrollResult();
return getScrollableArea()->userScroll(granularity, delta);
}
bool LayoutBox::canBeScrolledAndHasScrollableArea() const {
return canBeProgramaticallyScrolled() &&
(pixelSnappedScrollHeight() != pixelSnappedClientHeight() ||
pixelSnappedScrollWidth() != pixelSnappedClientWidth());
}
bool LayoutBox::canBeProgramaticallyScrolled() const {
Node* node = this->node();
if (node && node->isDocumentNode())
return true;
if (!hasOverflowClip())
return false;
bool hasScrollableOverflow =
hasScrollableOverflowX() || hasScrollableOverflowY();
if (scrollsOverflow() && hasScrollableOverflow)
return true;
return node && hasEditableStyle(*node);
}
void LayoutBox::autoscroll(const IntPoint& positionInRootFrame) {
LocalFrame* frame = this->frame();
if (!frame)
return;
FrameView* frameView = frame->view();
if (!frameView)
return;
IntPoint positionInContent =
frameView->rootFrameToContents(positionInRootFrame);
scrollRectToVisible(LayoutRect(positionInContent, LayoutSize(1, 1)),
ScrollAlignment::alignToEdgeIfNeeded,
ScrollAlignment::alignToEdgeIfNeeded, UserScroll);
}
// There are two kinds of layoutObject that can autoscroll.
bool LayoutBox::canAutoscroll() const {
if (node() && node()->isDocumentNode())
return view()->frameView()->isScrollable();
// Check for a box that can be scrolled in its own right.
return canBeScrolledAndHasScrollableArea();
}
// If specified point is in border belt, returned offset denotes direction of
// scrolling.
IntSize LayoutBox::calculateAutoscrollDirection(
const IntPoint& pointInRootFrame) const {
if (!frame())
return IntSize();
FrameView* frameView = frame()->view();
if (!frameView)
return IntSize();
IntRect box(absoluteBoundingBoxRect());
box.move(view()->frameView()->scrollOffsetInt());
IntRect windowBox = view()->frameView()->contentsToRootFrame(box);
IntPoint windowAutoscrollPoint = pointInRootFrame;
if (windowAutoscrollPoint.x() < windowBox.x() + autoscrollBeltSize)
windowAutoscrollPoint.move(-autoscrollBeltSize, 0);
else if (windowAutoscrollPoint.x() > windowBox.maxX() - autoscrollBeltSize)
windowAutoscrollPoint.move(autoscrollBeltSize, 0);
if (windowAutoscrollPoint.y() < windowBox.y() + autoscrollBeltSize)
windowAutoscrollPoint.move(0, -autoscrollBeltSize);
else if (windowAutoscrollPoint.y() > windowBox.maxY() - autoscrollBeltSize)
windowAutoscrollPoint.move(0, autoscrollBeltSize);
return windowAutoscrollPoint - pointInRootFrame;
}
LayoutBox* LayoutBox::findAutoscrollable(LayoutObject* layoutObject) {
while (
layoutObject &&
!(layoutObject->isBox() && toLayoutBox(layoutObject)->canAutoscroll())) {
// Do not start autoscroll when the node is inside a fixed-position element.
if (layoutObject->isBox() && toLayoutBox(layoutObject)->hasLayer() &&
toLayoutBox(layoutObject)->layer()->fixedToViewport()) {
return nullptr;
}
if (!layoutObject->parent() &&
layoutObject->node() == layoutObject->document() &&
layoutObject->document().localOwner())
layoutObject = layoutObject->document().localOwner()->layoutObject();
else
layoutObject = layoutObject->parent();
}
return layoutObject && layoutObject->isBox() ? toLayoutBox(layoutObject)
: nullptr;
}
void LayoutBox::scrollByRecursively(const ScrollOffset& delta) {
if (delta.isZero())
return;
bool restrictedByLineClamp = false;
if (parent())
restrictedByLineClamp = !parent()->style()->lineClamp().isNone();
if (hasOverflowClip() && !restrictedByLineClamp) {
PaintLayerScrollableArea* scrollableArea = this->getScrollableArea();
DCHECK(scrollableArea);
ScrollOffset newScrollOffset = scrollableArea->getScrollOffset() + delta;
scrollableArea->setScrollOffset(newScrollOffset, ProgrammaticScroll);
// If this layer can't do the scroll we ask the next layer up that can
// scroll to try.
ScrollOffset remainingScrollOffset =
newScrollOffset - scrollableArea->getScrollOffset();
if (!remainingScrollOffset.isZero() && parent()) {
if (LayoutBox* scrollableBox = enclosingScrollableBox())
scrollableBox->scrollByRecursively(remainingScrollOffset);
LocalFrame* frame = this->frame();
if (frame && frame->page())
frame->page()->autoscrollController().updateAutoscrollLayoutObject();
}
} else if (view()->frameView()) {
// If we are here, we were called on a layoutObject that can be
// programmatically scrolled, but doesn't have an overflow clip. Which means
// that it is a document node that can be scrolled.
// FIXME: Pass in DoubleSize. crbug.com/414283.
view()->frameView()->scrollBy(delta, UserScroll);
// FIXME: If we didn't scroll the whole way, do we want to try looking at
// the frames ownerElement?
// https://bugs.webkit.org/show_bug.cgi?id=28237
}
}
bool LayoutBox::needsPreferredWidthsRecalculation() const {
return style()->paddingStart().isPercentOrCalc() ||
style()->paddingEnd().isPercentOrCalc();
}
IntSize LayoutBox::originAdjustmentForScrollbars() const {
IntSize size;
int adjustmentWidth = verticalScrollbarWidth();
if (hasFlippedBlocksWritingMode() ||
(isHorizontalWritingMode() &&
shouldPlaceBlockDirectionScrollbarOnLogicalLeft())) {
size.expand(adjustmentWidth, 0);
}
return size;
}
IntSize LayoutBox::scrolledContentOffset() const {
DCHECK(hasOverflowClip());
DCHECK(hasLayer());
// FIXME: Return DoubleSize here. crbug.com/414283.
PaintLayerScrollableArea* scrollableArea = getScrollableArea();
IntSize result =
scrollableArea->scrollOffsetInt() + originAdjustmentForScrollbars();
if (isHorizontalWritingMode() &&
shouldPlaceBlockDirectionScrollbarOnLogicalLeft())
result.expand(-verticalScrollbarWidth(), 0);
return result;
}
LayoutRect LayoutBox::clippingRect() const {
LayoutRect result = LayoutRect(LayoutRect::infiniteIntRect());
if (shouldClipOverflow())
result = overflowClipRect(LayoutPoint());
if (hasClip())
result.intersect(clipRect(LayoutPoint()));
return result;
}
bool LayoutBox::mapScrollingContentsRectToBoxSpace(
TransformState& transformState,
TransformState::TransformAccumulation accumulation,
VisualRectFlags visualRectFlags) const {
if (!hasClipRelatedProperty())
return true;
if (hasOverflowClip()) {
LayoutSize offset = LayoutSize(-scrolledContentOffset());
transformState.move(offset, accumulation);
}
// This won't work fully correctly for fixed-position elements, who should
// receive CSS clip but for whom the current object is not in the containing
// block chain.
LayoutRect clipRect = clippingRect();
transformState.flatten();
LayoutRect rect(transformState.lastPlanarQuad().enclosingBoundingBox());
bool doesIntersect;
if (visualRectFlags & EdgeInclusive) {
doesIntersect = rect.inclusiveIntersect(clipRect);
} else {
rect.intersect(clipRect);
doesIntersect = !rect.isEmpty();
}
transformState.setQuad(FloatQuad(FloatRect(rect)));
return doesIntersect;
}
void LayoutBox::computeIntrinsicLogicalWidths(
LayoutUnit& minLogicalWidth,
LayoutUnit& maxLogicalWidth) const {
minLogicalWidth = minPreferredLogicalWidth() - borderAndPaddingLogicalWidth();
maxLogicalWidth = maxPreferredLogicalWidth() - borderAndPaddingLogicalWidth();
}
LayoutUnit LayoutBox::minPreferredLogicalWidth() const {
if (preferredLogicalWidthsDirty()) {
#if DCHECK_IS_ON()
SetLayoutNeededForbiddenScope layoutForbiddenScope(
const_cast<LayoutBox&>(*this));
#endif
const_cast<LayoutBox*>(this)->computePreferredLogicalWidths();
DCHECK(!preferredLogicalWidthsDirty());
}
return m_minPreferredLogicalWidth;
}
DISABLE_CFI_PERF
LayoutUnit LayoutBox::maxPreferredLogicalWidth() const {
if (preferredLogicalWidthsDirty()) {
#if DCHECK_IS_ON()
SetLayoutNeededForbiddenScope layoutForbiddenScope(
const_cast<LayoutBox&>(*this));
#endif
const_cast<LayoutBox*>(this)->computePreferredLogicalWidths();
DCHECK(!preferredLogicalWidthsDirty());
}
return m_maxPreferredLogicalWidth;
}
bool LayoutBox::hasOverrideLogicalContentHeight() const {
return m_rareData && m_rareData->m_overrideLogicalContentHeight != -1;
}
bool LayoutBox::hasOverrideLogicalContentWidth() const {
return m_rareData && m_rareData->m_overrideLogicalContentWidth != -1;
}
void LayoutBox::setOverrideLogicalContentHeight(LayoutUnit height) {
DCHECK_GE(height, 0);
ensureRareData().m_overrideLogicalContentHeight = height;
}
void LayoutBox::setOverrideLogicalContentWidth(LayoutUnit width) {
DCHECK_GE(width, 0);
ensureRareData().m_overrideLogicalContentWidth = width;
}
void LayoutBox::clearOverrideLogicalContentHeight() {
if (m_rareData)
m_rareData->m_overrideLogicalContentHeight = LayoutUnit(-1);
}
void LayoutBox::clearOverrideLogicalContentWidth() {
if (m_rareData)
m_rareData->m_overrideLogicalContentWidth = LayoutUnit(-1);
}
void LayoutBox::clearOverrideSize() {
clearOverrideLogicalContentHeight();
clearOverrideLogicalContentWidth();
}
LayoutUnit LayoutBox::overrideLogicalContentWidth() const {
DCHECK(hasOverrideLogicalContentWidth());
return m_rareData->m_overrideLogicalContentWidth;
}
LayoutUnit LayoutBox::overrideLogicalContentHeight() const {
DCHECK(hasOverrideLogicalContentHeight());
return m_rareData->m_overrideLogicalContentHeight;
}
// TODO (lajava) Now that we have implemented these functions based on physical
// direction, we'd rather remove the logical ones.
LayoutUnit LayoutBox::overrideContainingBlockContentLogicalWidth() const {
DCHECK(hasOverrideContainingBlockLogicalWidth());
return m_rareData->m_overrideContainingBlockContentLogicalWidth;
}
// TODO (lajava) Shouldn't we implement these functions based on physical
// direction ?.
LayoutUnit LayoutBox::overrideContainingBlockContentLogicalHeight() const {
DCHECK(hasOverrideContainingBlockLogicalHeight());
return m_rareData->m_overrideContainingBlockContentLogicalHeight;
}
// TODO (lajava) Shouldn't we implement these functions based on physical
// direction ?.
bool LayoutBox::hasOverrideContainingBlockLogicalWidth() const {
return m_rareData &&
m_rareData->m_hasOverrideContainingBlockContentLogicalWidth;
}
// TODO (lajava) Shouldn't we implement these functions based on physical
// direction ?.
bool LayoutBox::hasOverrideContainingBlockLogicalHeight() const {
return m_rareData &&
m_rareData->m_hasOverrideContainingBlockContentLogicalHeight;
}
// TODO (lajava) Shouldn't we implement these functions based on physical
// direction ?.
void LayoutBox::setOverrideContainingBlockContentLogicalWidth(
LayoutUnit logicalWidth) {
DCHECK_GE(logicalWidth, LayoutUnit(-1));
ensureRareData().m_overrideContainingBlockContentLogicalWidth = logicalWidth;
ensureRareData().m_hasOverrideContainingBlockContentLogicalWidth = true;
}
// TODO (lajava) Shouldn't we implement these functions based on physical
// direction ?.
void LayoutBox::setOverrideContainingBlockContentLogicalHeight(
LayoutUnit logicalHeight) {
DCHECK_GE(logicalHeight, LayoutUnit(-1));
ensureRareData().m_overrideContainingBlockContentLogicalHeight =
logicalHeight;
ensureRareData().m_hasOverrideContainingBlockContentLogicalHeight = true;
}
// TODO (lajava) Shouldn't we implement these functions based on physical
// direction ?.
void LayoutBox::clearContainingBlockOverrideSize() {
if (!m_rareData)
return;
ensureRareData().m_hasOverrideContainingBlockContentLogicalWidth = false;
ensureRareData().m_hasOverrideContainingBlockContentLogicalHeight = false;
}
// TODO (lajava) Shouldn't we implement these functions based on physical
// direction ?.
void LayoutBox::clearOverrideContainingBlockContentLogicalHeight() {
if (!m_rareData)
return;
ensureRareData().m_hasOverrideContainingBlockContentLogicalHeight = false;
}
LayoutUnit LayoutBox::extraInlineOffset() const {
return gExtraInlineOffsetMap ? gExtraInlineOffsetMap->at(this) : LayoutUnit();
}
LayoutUnit LayoutBox::extraBlockOffset() const {
return gExtraBlockOffsetMap ? gExtraBlockOffsetMap->at(this) : LayoutUnit();
}
void LayoutBox::setExtraInlineOffset(LayoutUnit inlineOffest) {
if (!gExtraInlineOffsetMap)
gExtraInlineOffsetMap = new OverrideSizeMap;
gExtraInlineOffsetMap->set(this, inlineOffest);
}
void LayoutBox::setExtraBlockOffset(LayoutUnit blockOffest) {
if (!gExtraBlockOffsetMap)
gExtraBlockOffsetMap = new OverrideSizeMap;
gExtraBlockOffsetMap->set(this, blockOffest);
}
void LayoutBox::clearExtraInlineAndBlockOffests() {
if (gExtraInlineOffsetMap)
gExtraInlineOffsetMap->erase(this);
if (gExtraBlockOffsetMap)
gExtraBlockOffsetMap->erase(this);
}
LayoutUnit LayoutBox::adjustBorderBoxLogicalWidthForBoxSizing(
float width) const {
LayoutUnit bordersPlusPadding = collapsedBorderAndCSSPaddingLogicalWidth();
LayoutUnit result(width);
if (style()->boxSizing() == EBoxSizing::kContentBox)
return result + bordersPlusPadding;
return std::max(result, bordersPlusPadding);
}
LayoutUnit LayoutBox::adjustBorderBoxLogicalHeightForBoxSizing(
float height) const {
LayoutUnit bordersPlusPadding = collapsedBorderAndCSSPaddingLogicalHeight();
LayoutUnit result(height);
if (style()->boxSizing() == EBoxSizing::kContentBox)
return result + bordersPlusPadding;
return std::max(result, bordersPlusPadding);
}
LayoutUnit LayoutBox::adjustContentBoxLogicalWidthForBoxSizing(
float width) const {
LayoutUnit result(width);
if (style()->boxSizing() == EBoxSizing::kBorderBox)
result -= collapsedBorderAndCSSPaddingLogicalWidth();
return std::max(LayoutUnit(), result);
}
LayoutUnit LayoutBox::adjustContentBoxLogicalHeightForBoxSizing(
float height) const {
LayoutUnit result(height);
if (style()->boxSizing() == EBoxSizing::kBorderBox)
result -= collapsedBorderAndCSSPaddingLogicalHeight();
return std::max(LayoutUnit(), result);
}
// Hit Testing
bool LayoutBox::nodeAtPoint(HitTestResult& result,
const HitTestLocation& locationInContainer,
const LayoutPoint& accumulatedOffset,
HitTestAction action) {
LayoutPoint adjustedLocation = accumulatedOffset + location();
if (!isLayoutView()) {
// 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();
flipForWritingMode(overflowBox);
overflowBox.moveBy(adjustedLocation);
if (!locationInContainer.intersects(overflowBox))
return false;
}
bool shouldHitTestSelf = isInSelfHitTestingPhase(action);
if (shouldHitTestSelf && hasOverflowClip() &&
hitTestOverflowControl(result, locationInContainer, adjustedLocation))
return true;
// TODO(pdr): We should also check for css clip in the !isSelfPaintingLayer
// case, similar to overflow clip below.
bool skipChildren = false;
if (shouldClipOverflow() && !hasSelfPaintingLayer()) {
if (!locationInContainer.intersects(overflowClipRect(
adjustedLocation, ExcludeOverlayScrollbarSizeForHitTesting))) {
skipChildren = true;
} else if (style()->hasBorderRadius()) {
LayoutRect boundsRect(adjustedLocation, size());
skipChildren = !locationInContainer.intersects(
style()->getRoundedInnerBorderFor(boundsRect));
}
}
// TODO(pdr): We should also include checks for hit testing border radius at
// the layer level (see: crbug.com/568904).
if (!skipChildren &&
hitTestChildren(result, locationInContainer, adjustedLocation, action))
return true;
if (style()->hasBorderRadius() &&
hitTestClippedOutByBorder(locationInContainer, adjustedLocation))
return false;
// Now hit test ourselves.
if (shouldHitTestSelf && visibleToHitTestRequest(result.hitTestRequest())) {
LayoutRect boundsRect(adjustedLocation, size());
if (locationInContainer.intersects(boundsRect)) {
updateHitTestResult(result,
flipForWritingMode(locationInContainer.point() -
toLayoutSize(adjustedLocation)));
if (result.addNodeToListBasedTestResult(nodeForHitTest(),
locationInContainer,
boundsRect) == StopHitTesting)
return true;
}
}
return false;
}
bool LayoutBox::hitTestChildren(HitTestResult& result,
const HitTestLocation& locationInContainer,
const LayoutPoint& accumulatedOffset,
HitTestAction action) {
for (LayoutObject* child = slowLastChild(); child;
child = child->previousSibling()) {
if ((!child->hasLayer() ||
!toLayoutBoxModelObject(child)->layer()->isSelfPaintingLayer()) &&
child->nodeAtPoint(result, locationInContainer, accumulatedOffset,
action))
return true;
}
return false;
}
bool LayoutBox::hitTestClippedOutByBorder(
const HitTestLocation& locationInContainer,
const LayoutPoint& borderBoxLocation) const {
LayoutRect borderRect = borderBoxRect();
borderRect.moveBy(borderBoxLocation);
return !locationInContainer.intersects(
style()->getRoundedBorderFor(borderRect));
}
void LayoutBox::paint(const PaintInfo& paintInfo,
const LayoutPoint& paintOffset) const {
BoxPainter(*this).paint(paintInfo, paintOffset);
}
void LayoutBox::paintBoxDecorationBackground(
const PaintInfo& paintInfo,
const LayoutPoint& paintOffset) const {
BoxPainter(*this).paintBoxDecorationBackground(paintInfo, paintOffset);
}
bool LayoutBox::getBackgroundPaintedExtent(LayoutRect& paintedExtent) const {
DCHECK(styleRef().hasBackground());
// LayoutView is special in the sense that it expands to the whole canvas,
// thus can't be handled by this function.
DCHECK(!isLayoutView());
LayoutRect backgroundRect(borderBoxRect());
Color backgroundColor = resolveColor(CSSPropertyBackgroundColor);
if (backgroundColor.alpha()) {
paintedExtent = backgroundRect;
return true;
}
if (!style()->backgroundLayers().image() ||
style()->backgroundLayers().next()) {
paintedExtent = backgroundRect;
return true;
}
BackgroundImageGeometry geometry;
// TODO(jchaffraix): This function should be rethought as it's called during
// and outside of the paint phase. Potentially returning different results at
// different phases.
geometry.calculate(*this, nullptr, nullptr, GlobalPaintNormalPhase,
style()->backgroundLayers(), backgroundRect);
if (geometry.hasNonLocalGeometry())
return false;
paintedExtent = LayoutRect(geometry.destRect());
return true;
}
bool LayoutBox::backgroundIsKnownToBeOpaqueInRect(
const LayoutRect& localRect) const {
if (isDocumentElement() || backgroundStolenForBeingBody())
return false;
// If the element has appearance, it might be painted by theme.
// We cannot be sure if theme paints the background opaque.
// In this case it is safe to not assume opaqueness.
// FIXME: May be ask theme if it paints opaque.
if (style()->hasAppearance())
return false;
// FIXME: Check the opaqueness of background images.
// FIXME: Use rounded rect if border radius is present.
if (style()->hasBorderRadius())
return false;
if (hasClipPath())
return false;
if (style()->hasBlendMode())
return false;
return backgroundRect(BackgroundKnownOpaqueRect).contains(localRect);
}
static bool isCandidateForOpaquenessTest(const LayoutBox& childBox) {
const ComputedStyle& childStyle = childBox.styleRef();
if (childStyle.position() != EPosition::kStatic &&
childBox.containingBlock() != childBox.parent())
return false;
if (childStyle.visibility() != EVisibility::kVisible ||
childStyle.shapeOutside())
return false;
if (childBox.size().isZero())
return false;
if (PaintLayer* childLayer = childBox.layer()) {
// FIXME: perhaps this could be less conservative?
if (childLayer->compositingState() != NotComposited)
return false;
// FIXME: Deal with z-index.
if (childStyle.isStackingContext())
return false;
if (childLayer->hasTransformRelatedProperty() ||
childLayer->isTransparent() || childLayer->hasFilterInducingProperty())
return false;
if (childBox.hasOverflowClip() && childStyle.hasBorderRadius())
return false;
}
return true;
}
bool LayoutBox::foregroundIsKnownToBeOpaqueInRect(
const LayoutRect& localRect,
unsigned maxDepthToTest) const {
if (!maxDepthToTest)
return false;
for (LayoutObject* child = slowFirstChild(); child;
child = child->nextSibling()) {
if (!child->isBox())
continue;
LayoutBox* childBox = toLayoutBox(child);
if (!isCandidateForOpaquenessTest(*childBox))
continue;
LayoutPoint childLocation = childBox->location();
if (childBox->isInFlowPositioned())
childLocation.move(childBox->offsetForInFlowPosition());
LayoutRect childLocalRect = localRect;
childLocalRect.moveBy(-childLocation);
if (childLocalRect.y() < 0 || childLocalRect.x() < 0) {
// If there is unobscured area above/left of a static positioned box then
// the rect is probably not covered.
if (!childBox->isPositioned())
return false;
continue;
}
if (childLocalRect.maxY() > childBox->size().height() ||
childLocalRect.maxX() > childBox->size().width())
continue;
if (childBox->backgroundIsKnownToBeOpaqueInRect(childLocalRect))
return true;
if (childBox->foregroundIsKnownToBeOpaqueInRect(childLocalRect,
maxDepthToTest - 1))
return true;
}
return false;
}
DISABLE_CFI_PERF
bool LayoutBox::computeBackgroundIsKnownToBeObscured() const {
if (scrollsOverflow())
return false;
// Test to see if the children trivially obscure the background.
if (!styleRef().hasBackground())
return false;
// Root background painting is special.
if (isLayoutView())
return false;
// FIXME: box-shadow is painted while background painting.
if (style()->boxShadow())
return false;
LayoutRect backgroundRect;
if (!getBackgroundPaintedExtent(backgroundRect))
return false;
return foregroundIsKnownToBeOpaqueInRect(backgroundRect,
backgroundObscurationTestMaxDepth);
}
void LayoutBox::paintMask(const PaintInfo& paintInfo,
const LayoutPoint& paintOffset) const {
BoxPainter(*this).paintMask(paintInfo, paintOffset);
}
void LayoutBox::imageChanged(WrappedImagePtr image, const IntRect*) {
// TODO(chrishtr): support PaintInvalidationDelayedFull for animated border
// images.
if ((styleRef().borderImage().image() &&
styleRef().borderImage().image()->data() == image) ||
(styleRef().maskBoxImage().image() &&
styleRef().maskBoxImage().image()->data() == image) ||
(styleRef().boxReflect() && styleRef().boxReflect()->mask().image() &&
styleRef().boxReflect()->mask().image()->data() == image)) {
setShouldDoFullPaintInvalidationWithoutGeometryChange();
} else {
for (const FillLayer* layer = &styleRef().maskLayers(); layer;
layer = layer->next()) {
if (layer->image() && image == layer->image()->data()) {
setShouldDoFullPaintInvalidationWithoutGeometryChange();
break;
}
}
}
if (!isDocumentElement() && !backgroundStolenForBeingBody()) {
for (const FillLayer* layer = &styleRef().backgroundLayers(); layer;
layer = layer->next()) {
if (layer->image() && image == layer->image()->data()) {
invalidateBackgroundObscurationStatus();
bool maybeAnimated =
layer->image()->cachedImage() &&
layer->image()->cachedImage()->getImage() &&
layer->image()->cachedImage()->getImage()->maybeAnimated();
if (maybeAnimated) {
setMayNeedPaintInvalidationAnimatedBackgroundImage();
} else {
setShouldDoFullPaintInvalidationWithoutGeometryChange();
setBackgroundChangedSinceLastPaintInvalidation();
}
break;
}
}
}
ShapeValue* shapeOutsideValue = style()->shapeOutside();
if (!frameView()->isInPerformLayout() && isFloating() && shapeOutsideValue &&
shapeOutsideValue->image() &&
shapeOutsideValue->image()->data() == image) {
ShapeOutsideInfo& info = ShapeOutsideInfo::ensureInfo(*this);
if (!info.isComputingShape()) {
info.markShapeAsDirty();
markShapeOutsideDependentsForLayout();
}
}
}
ResourcePriority LayoutBox::computeResourcePriority() const {
LayoutRect viewBounds = viewRect();
LayoutRect objectBounds = LayoutRect(absoluteContentBox());
// The object bounds might be empty right now, so intersects will fail since
// it doesn't deal with empty rects. Use LayoutRect::contains in that case.
bool isVisible;
if (!objectBounds.isEmpty())
isVisible = viewBounds.intersects(objectBounds);
else
isVisible = viewBounds.contains(objectBounds);
LayoutRect screenRect;
if (!objectBounds.isEmpty()) {
screenRect = viewBounds;
screenRect.intersect(objectBounds);
}
int screenArea = 0;
if (!screenRect.isEmpty() && isVisible)
screenArea = (screenRect.width() * screenRect.height()).toInt();
return ResourcePriority(
isVisible ? ResourcePriority::Visible : ResourcePriority::NotVisible,
screenArea);
}
void LayoutBox::locationChanged() {
// The location may change because of layout of other objects. Should check
// this object for paint invalidation.
if (!needsLayout())
setMayNeedPaintInvalidation();
}
void LayoutBox::sizeChanged() {
// The size may change because of layout of other objects. Should check this
// object for paint invalidation.
if (!needsLayout())
setMayNeedPaintInvalidation();
if (node() && node()->isElementNode()) {
Element& element = toElement(*node());
element.setNeedsResizeObserverUpdate();
}
}
bool LayoutBox::intersectsVisibleViewport() const {
LayoutRect rect = visualOverflowRect();
LayoutView* layoutView = view();
while (!layoutView->frame()->ownerLayoutItem().isNull())
layoutView =
LayoutAPIShim::layoutObjectFrom(layoutView->frame()->ownerLayoutItem())
->view();
mapToVisualRectInAncestorSpace(layoutView, rect);
return rect.intersects(LayoutRect(
layoutView->frameView()->getScrollableArea()->visibleContentRect()));
}
void LayoutBox::ensureIsReadyForPaintInvalidation() {
LayoutBoxModelObject::ensureIsReadyForPaintInvalidation();
if (mayNeedPaintInvalidationAnimatedBackgroundImage() &&
!backgroundIsKnownToBeObscured()) {
setShouldDoFullPaintInvalidationWithoutGeometryChange(
PaintInvalidationDelayedFull);
}
if (fullPaintInvalidationReason() != PaintInvalidationDelayedFull ||
!intersectsVisibleViewport())
return;
// Do regular full paint invalidation if the object with
// PaintInvalidationDelayedFull is onscreen.
if (intersectsVisibleViewport()) {
// Conservatively assume the delayed paint invalidation was caused by
// background image change.
setBackgroundChangedSinceLastPaintInvalidation();
setShouldDoFullPaintInvalidationWithoutGeometryChange(
PaintInvalidationFull);
}
}
PaintInvalidationReason LayoutBox::invalidatePaintIfNeeded(
const PaintInvalidationState& paintInvalidationState) {
if (hasBoxDecorationBackground()
// We also paint overflow controls in background phase.
|| (hasOverflowClip() && getScrollableArea()->hasOverflowControls())) {
PaintLayer& layer = paintInvalidationState.paintingLayer();
if (&layer.layoutObject() != this)
layer.setNeedsPaintPhaseDescendantBlockBackgrounds();
}
return LayoutBoxModelObject::invalidatePaintIfNeeded(paintInvalidationState);
}
PaintInvalidationReason LayoutBox::invalidatePaintIfNeeded(
const PaintInvalidatorContext& context) const {
return BoxPaintInvalidator(*this, context).invalidatePaintIfNeeded();
}
LayoutRect LayoutBox::overflowClipRect(
const LayoutPoint& location,
OverlayScrollbarClipBehavior overlayScrollbarClipBehavior) const {
// FIXME: When overflow-clip (CSS3) is implemented, we'll obtain the property
// here.
LayoutRect clipRect = borderBoxRect();
clipRect.setLocation(location + clipRect.location() +
LayoutSize(borderLeft(), borderTop()));
clipRect.setSize(clipRect.size() - LayoutSize(borderLeft() + borderRight(),
borderTop() + borderBottom()));
if (hasOverflowClip())
excludeScrollbars(clipRect, overlayScrollbarClipBehavior);
if (hasControlClip())
clipRect.intersect(controlClipRect(location));
return clipRect;
}
void LayoutBox::excludeScrollbars(
LayoutRect& rect,
OverlayScrollbarClipBehavior overlayScrollbarClipBehavior) const {
if (PaintLayerScrollableArea* scrollableArea = this->getScrollableArea()) {
if (shouldPlaceBlockDirectionScrollbarOnLogicalLeft()) {
rect.move(
scrollableArea->verticalScrollbarWidth(overlayScrollbarClipBehavior),
0);
}
rect.contract(
scrollableArea->verticalScrollbarWidth(overlayScrollbarClipBehavior),
scrollableArea->horizontalScrollbarHeight(
overlayScrollbarClipBehavior));
}
}
LayoutRect LayoutBox::clipRect(const LayoutPoint& location) const {
LayoutRect borderBoxRect = this->borderBoxRect();
LayoutRect clipRect =
LayoutRect(borderBoxRect.location() + location, borderBoxRect.size());
if (!style()->clipLeft().isAuto()) {
LayoutUnit c = valueForLength(style()->clipLeft(), borderBoxRect.width());
clipRect.move(c, LayoutUnit());
clipRect.contract(c, LayoutUnit());
}
if (!style()->clipRight().isAuto())
clipRect.contract(
size().width() - valueForLength(style()->clipRight(), size().width()),
LayoutUnit());
if (!style()->clipTop().isAuto()) {
LayoutUnit c = valueForLength(style()->clipTop(), borderBoxRect.height());
clipRect.move(LayoutUnit(), c);
clipRect.contract(LayoutUnit(), c);
}
if (!style()->clipBottom().isAuto())
clipRect.contract(LayoutUnit(),
size().height() - valueForLength(style()->clipBottom(),
size().height()));
return clipRect;
}
static LayoutUnit portionOfMarginNotConsumedByFloat(LayoutUnit childMargin,
LayoutUnit contentSide,
LayoutUnit offset) {
if (childMargin <= 0)
return LayoutUnit();
LayoutUnit contentSideWithMargin = contentSide + childMargin;
if (offset > contentSideWithMargin)
return childMargin;
return offset - contentSide;
}
LayoutUnit LayoutBox::shrinkLogicalWidthToAvoidFloats(
LayoutUnit childMarginStart,
LayoutUnit childMarginEnd,
const LayoutBlockFlow* cb) const {
LayoutUnit logicalTopPosition = logicalTop();
LayoutUnit startOffsetForContent = cb->startOffsetForContent();
LayoutUnit endOffsetForContent = cb->endOffsetForContent();
LayoutUnit logicalHeight = cb->logicalHeightForChild(*this);
LayoutUnit startOffsetForLine = cb->startOffsetForLine(
logicalTopPosition, DoNotIndentText, logicalHeight);
LayoutUnit endOffsetForLine =
cb->endOffsetForLine(logicalTopPosition, DoNotIndentText, logicalHeight);
// If there aren't any floats constraining us then allow the margins to
// shrink/expand the width as much as they want.
if (startOffsetForContent == startOffsetForLine &&
endOffsetForContent == endOffsetForLine)
return cb->availableLogicalWidthForLine(logicalTopPosition, DoNotIndentText,
logicalHeight) -
childMarginStart - childMarginEnd;
LayoutUnit width = cb->availableLogicalWidthForLine(
logicalTopPosition, DoNotIndentText, logicalHeight) -
std::max(LayoutUnit(), childMarginStart) -
std::max(LayoutUnit(), childMarginEnd);
// We need to see if margins on either the start side or the end side can
// contain the floats in question. If they can, then just using the line width
// is inaccurate. In the case where a float completely fits, we don't need to
// use the line offset at all, but can instead push all the way to the content
// edge of the containing block. In the case where the float doesn't fit, we
// can use the line offset, but we need to grow it by the margin to reflect
// the fact that the margin was "consumed" by the float. Negative margins
// aren't consumed by the float, and so we ignore them.
width += portionOfMarginNotConsumedByFloat(
childMarginStart, startOffsetForContent, startOffsetForLine);
width += portionOfMarginNotConsumedByFloat(
childMarginEnd, endOffsetForContent, endOffsetForLine);
return width;
}
LayoutUnit LayoutBox::containingBlockLogicalHeightForGetComputedStyle() const {
if (hasOverrideContainingBlockLogicalHeight())
return overrideContainingBlockContentLogicalHeight();
if (!isPositioned())
return containingBlockLogicalHeightForContent(ExcludeMarginBorderPadding);
LayoutBoxModelObject* cb = toLayoutBoxModelObject(container());
LayoutUnit height = containingBlockLogicalHeightForPositioned(cb);
if (styleRef().position() != EPosition::kAbsolute)
height -= cb->paddingLogicalHeight();
return height;
}
LayoutUnit LayoutBox::containingBlockLogicalWidthForContent() const {
if (hasOverrideContainingBlockLogicalWidth())
return overrideContainingBlockContentLogicalWidth();
LayoutBlock* cb = containingBlock();
if (isOutOfFlowPositioned())
return cb->clientLogicalWidth();
return cb->availableLogicalWidth();
}
LayoutUnit LayoutBox::containingBlockLogicalHeightForContent(
AvailableLogicalHeightType heightType) const {
if (hasOverrideContainingBlockLogicalHeight())
return overrideContainingBlockContentLogicalHeight();
LayoutBlock* cb = containingBlock();
return cb->availableLogicalHeight(heightType);
}
LayoutUnit LayoutBox::containingBlockAvailableLineWidth() const {
LayoutBlock* cb = containingBlock();
if (cb->isLayoutBlockFlow())
return toLayoutBlockFlow(cb)->availableLogicalWidthForLine(
logicalTop(), DoNotIndentText,
availableLogicalHeight(IncludeMarginBorderPadding));
return LayoutUnit();
}
LayoutUnit LayoutBox::perpendicularContainingBlockLogicalHeight() const {
if (hasOverrideContainingBlockLogicalHeight())
return overrideContainingBlockContentLogicalHeight();
LayoutBlock* cb = containingBlock();
if (cb->hasOverrideLogicalContentHeight())
return cb->overrideLogicalContentHeight();
const ComputedStyle& containingBlockStyle = cb->styleRef();
Length logicalHeightLength = containingBlockStyle.logicalHeight();
// FIXME: For now just support fixed heights. Eventually should support
// percentage heights as well.
if (!logicalHeightLength.isFixed()) {
LayoutUnit fillFallbackExtent =
LayoutUnit(containingBlockStyle.isHorizontalWritingMode()
? view()->frameView()->visibleContentSize().height()
: view()->frameView()->visibleContentSize().width());
LayoutUnit fillAvailableExtent =
containingBlock()->availableLogicalHeight(ExcludeMarginBorderPadding);
if (fillAvailableExtent == -1)
return fillFallbackExtent;
return std::min(fillAvailableExtent, fillFallbackExtent);
}
// Use the content box logical height as specified by the style.
return cb->adjustContentBoxLogicalHeightForBoxSizing(
LayoutUnit(logicalHeightLength.value()));
}
void LayoutBox::mapLocalToAncestor(const LayoutBoxModelObject* ancestor,
TransformState& transformState,
MapCoordinatesFlags mode) const {
bool isFixedPos = style()->position() == EPosition::kFixed;
// If this box has a transform or contains paint, it acts as a fixed position
// container for fixed descendants, and may itself also be fixed position. So
// propagate 'fixed' up only if this box is fixed position.
if (canContainFixedPositionObjects() && !isFixedPos)
mode &= ~IsFixed;
else if (isFixedPos)
mode |= IsFixed;
LayoutBoxModelObject::mapLocalToAncestor(ancestor, transformState, mode);
}
void LayoutBox::mapAncestorToLocal(const LayoutBoxModelObject* ancestor,
TransformState& transformState,
MapCoordinatesFlags mode) const {
if (this == ancestor)
return;
bool isFixedPos = style()->position() == EPosition::kFixed;
// If this box has a transform or contains paint, it acts as a fixed position
// container for fixed descendants, and may itself also be fixed position. So
// propagate 'fixed' up only if this box is fixed position.
if (canContainFixedPositionObjects() && !isFixedPos)
mode &= ~IsFixed;
else if (isFixedPos)
mode |= IsFixed;
LayoutBoxModelObject::mapAncestorToLocal(ancestor, transformState, mode);
}
LayoutSize LayoutBox::offsetFromContainer(const LayoutObject* o) const {
DCHECK_EQ(o, container());
LayoutSize offset;
if (isInFlowPositioned())
offset += offsetForInFlowPosition();
offset += physicalLocationOffset();
if (o->hasOverflowClip())
offset -= toLayoutBox(o)->scrolledContentOffset();
if (style()->position() == EPosition::kAbsolute && o->isInFlowPositioned() &&
o->isLayoutInline())
offset += toLayoutInline(o)->offsetForInFlowPositionedInline(*this);
return offset;
}
InlineBox* LayoutBox::createInlineBox() {
return new InlineBox(LineLayoutItem(this));
}
void LayoutBox::dirtyLineBoxes(bool fullLayout) {
if (m_inlineBoxWrapper) {
if (fullLayout) {
m_inlineBoxWrapper->destroy();
m_inlineBoxWrapper = nullptr;
} else {
m_inlineBoxWrapper->dirtyLineBoxes();
}
}
}
void LayoutBox::positionLineBox(InlineBox* box) {
if (isOutOfFlowPositioned()) {
// Cache the x position only if we were an INLINE type originally.
bool originallyInline = style()->isOriginalDisplayInlineType();
if (originallyInline) {
// The value is cached in the xPos of the box. We only need this value if
// our object was inline originally, since otherwise it would have ended
// up underneath the inlines.
RootInlineBox& root = box->root();
root.block().setStaticInlinePositionForChild(LineLayoutBox(this),
box->logicalLeft());
} else {
// Our object was a block originally, so we make our normal flow position
// be just below the line box (as though all the inlines that came before
// us got wrapped in an anonymous block, which is what would have happened
// had we been in flow). This value was cached in the y() of the box.
layer()->setStaticBlockPosition(box->logicalTop());
}
if (container()->isLayoutInline())
moveWithEdgeOfInlineContainerIfNecessary(box->isHorizontal());
// Nuke the box.
box->remove(DontMarkLineBoxes);
box->destroy();
} else if (isAtomicInlineLevel()) {
setLocationAndUpdateOverflowControlsIfNeeded(box->location());
setInlineBoxWrapper(box);
}
}
void LayoutBox::moveWithEdgeOfInlineContainerIfNecessary(bool isHorizontal) {
DCHECK(isOutOfFlowPositioned());
DCHECK(container()->isLayoutInline());
DCHECK(container()->isInFlowPositioned());
// If this object is inside a relative positioned inline and its inline
// position is an explicit offset from the edge of its container then it will
// need to move if its inline container has changed width. We do not track if
// the width has changed but if we are here then we are laying out lines
// inside it, so it probably has - mark our object for layout so that it can
// move to the new offset created by the new width.
if (!normalChildNeedsLayout() &&
!style()->hasStaticInlinePosition(isHorizontal))
setChildNeedsLayout(MarkOnlyThis);
}
void LayoutBox::deleteLineBoxWrapper() {
if (m_inlineBoxWrapper) {
if (!documentBeingDestroyed())
m_inlineBoxWrapper->remove();
m_inlineBoxWrapper->destroy();
m_inlineBoxWrapper = nullptr;
}
}
void LayoutBox::setSpannerPlaceholder(
LayoutMultiColumnSpannerPlaceholder& placeholder) {
// Not expected to change directly from one spanner to another.
CHECK(!m_rareData || !m_rareData->m_spannerPlaceholder);
ensureRareData().m_spannerPlaceholder = &placeholder;
}
void LayoutBox::clearSpannerPlaceholder() {
if (!m_rareData)
return;
m_rareData->m_spannerPlaceholder = nullptr;
}
void LayoutBox::setPaginationStrut(LayoutUnit strut) {
if (!strut && !m_rareData)
return;
ensureRareData().m_paginationStrut = strut;
}
bool LayoutBox::isBreakBetweenControllable(EBreakBetween breakValue) const {
if (breakValue == EBreakBetween::kAuto)
return true;
// We currently only support non-auto break-before and break-after values on
// in-flow block level elements, which is the minimum requirement according to
// the spec.
if (isInline() || isFloatingOrOutOfFlowPositioned())
return false;
const LayoutBlock* curr = containingBlock();
if (!curr || !curr->isLayoutBlockFlow())
return false;
const LayoutView* layoutView = view();
bool viewIsPaginated = layoutView->fragmentationContext();
if (!viewIsPaginated && !flowThreadContainingBlock())
return false;
while (curr) {
if (curr == layoutView) {
return viewIsPaginated && breakValue != EBreakBetween::kColumn &&
breakValue != EBreakBetween::kAvoidColumn;
}
if (curr->isLayoutFlowThread()) {
if (breakValue ==
EBreakBetween::kAvoid) // Valid in any kind of fragmentation context.
return true;
bool isMulticolValue = breakValue == EBreakBetween::kColumn ||
breakValue == EBreakBetween::kAvoidColumn;
if (toLayoutFlowThread(curr)->isLayoutPagedFlowThread())
return !isMulticolValue;
if (isMulticolValue)
return true;
// If this is a flow thread for a multicol container, and we have a break
// value for paged, we need to keep looking.
}
if (curr->isFloatingOrOutOfFlowPositioned())
return false;
curr = curr->containingBlock();
}
NOTREACHED();
return false;
}
bool LayoutBox::isBreakInsideControllable(EBreakInside breakValue) const {
if (breakValue == EBreakInside::kAuto)
return true;
// First check multicol.
const LayoutFlowThread* flowThread = flowThreadContainingBlock();
// 'avoid-column' is only valid in a multicol context.
if (breakValue == EBreakInside::kAvoidColumn)
return flowThread && !flowThread->isLayoutPagedFlowThread();
// 'avoid' is valid in any kind of fragmentation context.
if (breakValue == EBreakInside::kAvoid && flowThread)
return true;
DCHECK(breakValue == EBreakInside::kAvoidPage ||
breakValue == EBreakInside::kAvoid);
if (view()->fragmentationContext())
return true; // The view is paginated, probably because we're printing.
if (!flowThread)
return false; // We're not inside any pagination context
// We're inside a flow thread. We need to be contained by a flow thread for
// paged overflow in order for pagination values to be valid, though.
for (const LayoutBlock* ancestor = flowThread; ancestor;
ancestor = ancestor->containingBlock()) {
if (ancestor->isLayoutFlowThread() &&
toLayoutFlowThread(ancestor)->isLayoutPagedFlowThread())
return true;
}
return false;
}
EBreakBetween LayoutBox::breakAfter() const {
EBreakBetween breakValue = style()->breakAfter();
if (breakValue == EBreakBetween::kAuto ||
isBreakBetweenControllable(breakValue))
return breakValue;
return EBreakBetween::kAuto;
}
EBreakBetween LayoutBox::breakBefore() const {
EBreakBetween breakValue = style()->breakBefore();
if (breakValue == EBreakBetween::kAuto ||
isBreakBetweenControllable(breakValue))
return breakValue;
return EBreakBetween::kAuto;
}
EBreakInside LayoutBox::breakInside() const {
EBreakInside breakValue = style()->breakInside();
if (breakValue == EBreakInside::kAuto ||
isBreakInsideControllable(breakValue))
return breakValue;
return EBreakInside::kAuto;
}
// At a class A break point [1], the break value with the highest precedence
// wins. If the two values have the same precedence (e.g. "left" and "right"),
// the value specified on a latter object wins.
//
// [1] https://drafts.csswg.org/css-break/#possible-breaks
static inline int fragmentainerBreakPrecedence(EBreakBetween breakValue) {
// "auto" has the lowest priority.
// "avoid*" values win over "auto".
// "avoid-page" wins over "avoid-column".
// "avoid" wins over "avoid-page".
// Forced break values win over "avoid".
// Any forced page break value wins over "column" forced break.
// More specific break values (left, right, recto, verso) wins over generic
// "page" values.
switch (breakValue) {
default:
NOTREACHED();
// fall-through
case EBreakBetween::kAuto:
return 0;
case EBreakBetween::kAvoidColumn:
return 1;
case EBreakBetween::kAvoidPage:
return 2;
case EBreakBetween::kAvoid:
return 3;
case EBreakBetween::kColumn:
return 4;
case EBreakBetween::kPage:
return 5;
case EBreakBetween::kLeft:
case EBreakBetween::kRight:
case EBreakBetween::kRecto:
case EBreakBetween::kVerso:
return 6;
}
}
EBreakBetween LayoutBox::joinFragmentainerBreakValues(
EBreakBetween firstValue,
EBreakBetween secondValue) {
if (fragmentainerBreakPrecedence(secondValue) >=
fragmentainerBreakPrecedence(firstValue))
return secondValue;
return firstValue;
}
EBreakBetween LayoutBox::classABreakPointValue(
EBreakBetween previousBreakAfterValue) const {
// First assert that we're at a class A break point.
DCHECK(isBreakBetweenControllable(previousBreakAfterValue));
return joinFragmentainerBreakValues(previousBreakAfterValue, breakBefore());
}
bool LayoutBox::needsForcedBreakBefore(
EBreakBetween previousBreakAfterValue) const {
// Forced break values are only honored when specified on in-flow objects, but
// floats and out-of-flow positioned objects may be affected by a break-after
// value of the previous in-flow object, even though we're not at a class A
// break point.
EBreakBetween breakValue =
isFloatingOrOutOfFlowPositioned()
? previousBreakAfterValue
: classABreakPointValue(previousBreakAfterValue);
return isForcedFragmentainerBreakValue(breakValue);
}
bool LayoutBox::paintedOutputOfObjectHasNoEffectRegardlessOfSize() const {
if (hasNonCompositedScrollbars() || getSelectionState() != SelectionNone ||
hasBoxDecorationBackground() || styleRef().hasBoxDecorations() ||
styleRef().hasVisualOverflowingEffect())
return false;
// Both mask and clip-path generates drawing display items that depends on
// the size of the box.
if (hasMask() || hasClipPath())
return false;
// If the box has any kind of clip, we need issue paint invalidation to cover
// the changed part of children when the box got resized. In SPv2 this is
// handled by detecting paint property changes.
if (!RuntimeEnabledFeatures::slimmingPaintV2Enabled()) {
if (hasClipRelatedProperty())
return false;
}
// If the box paints into its own backing, we can assume that it's painting
// may have some effect. For example, honoring the border-radius clip on
// a composited child paints into a mask for an otherwise non-painting
// element, because children of that element will require the mask.
if (hasLayer() && layer()->compositingState() == PaintsIntoOwnBacking)
return false;
return true;
}
LayoutRect LayoutBox::localVisualRect() const {
if (style()->visibility() != EVisibility::kVisible)
return LayoutRect();
if (hasMask() && !RuntimeEnabledFeatures::slimmingPaintV2Enabled())
return LayoutRect(layer()->boxForFilterOrMask());
return selfVisualOverflowRect();
}
void LayoutBox::inflateVisualRectForFilterUnderContainer(
TransformState& transformState,
const LayoutObject& container,
const LayoutBoxModelObject* ancestorToStopAt) const {
transformState.flatten();
// Apply visual overflow caused by reflections and filters defined on objects
// between this object and container (not included) or ancestorToStopAt
// (included).
LayoutSize offsetFromContainer = this->offsetFromContainer(&container);
transformState.move(offsetFromContainer);
for (LayoutObject* parent = this->parent(); parent && parent != container;
parent = parent->parent()) {
if (parent->isBox()) {
// Convert rect into coordinate space of parent to apply parent's
// reflection and filter.
LayoutSize parentOffset = parent->offsetFromAncestorContainer(&container);
transformState.move(-parentOffset);
toLayoutBox(parent)->inflateVisualRectForFilter(transformState);
transformState.move(parentOffset);
}
if (parent == ancestorToStopAt)
break;
}
transformState.move(-offsetFromContainer);
}
bool LayoutBox::mapToVisualRectInAncestorSpaceInternal(
const LayoutBoxModelObject* ancestor,
TransformState& transformState,
VisualRectFlags visualRectFlags) const {
inflateVisualRectForFilter(transformState);
if (ancestor == this)
return true;
AncestorSkipInfo skipInfo(ancestor, true);
LayoutObject* container = this->container(&skipInfo);
LayoutBox* tableRowContainer = nullptr;
// Skip table row because cells and rows are in the same coordinate space (see
// below, however for more comments about when |ancestor| is the table row).
if (isTableCell()) {
DCHECK(container->isTableRow());
DCHECK_EQ(parentBox(), container);
if (container != ancestor)
container = container->parent();
else
tableRowContainer = toLayoutBox(container);
}
if (!container)
return true;
LayoutPoint containerOffset;
if (container->isBox()) {
containerOffset.moveBy(physicalLocation(toLayoutBox(container)));
// If the row is the ancestor, however, add its offset back in. In effect,
// this passes from the joint <td> / <tr> coordinate space to the parent
// space, then back to <tr> / <td>.
if (tableRowContainer) {
containerOffset.moveBy(
-tableRowContainer->physicalLocation(toLayoutBox(container)));
}
} else if (container->isRuby()) {
// TODO(wkorman): Generalize Ruby specialization and/or document more
// clearly. See the accompanying specialization in
// LayoutInline::mapToVisualRectInAncestorSpaceInternal.
containerOffset.moveBy(physicalLocation());
} else {
containerOffset.moveBy(location());
}
const ComputedStyle& styleToUse = styleRef();
EPosition position = styleToUse.position();
if (position == EPosition::kAbsolute && container->isInFlowPositioned() &&
container->isLayoutInline()) {
containerOffset.move(
toLayoutInline(container)->offsetForInFlowPositionedInline(*this));
} else if (styleToUse.hasInFlowPosition() && layer()) {
// Apply the relative position offset when invalidating a rectangle. The
// layer is translated, but the layout box isn't, so we need to do this to
// get the right dirty rect. Since this is called from
// LayoutObject::setStyle, the relative position flag on the LayoutObject
// has been cleared, so use the one on the style().
containerOffset.move(layer()->offsetForInFlowPosition());
}
bool preserve3D = container->style()->preserves3D();
TransformState::TransformAccumulation accumulation =
preserve3D ? TransformState::AccumulateTransform
: TransformState::FlattenTransform;
if (skipInfo.filterSkipped()) {
inflateVisualRectForFilterUnderContainer(transformState, *container,
ancestor);
}
// We are now in our parent container's coordinate space. Apply our transform
// to obtain a bounding box in the parent's coordinate space that encloses us.
if (shouldUseTransformFromContainer(container)) {
TransformationMatrix t;
getTransformFromContainer(container, toLayoutSize(containerOffset), t);
transformState.applyTransform(t, accumulation);
// Use enclosingBoundingBox because we cannot properly compute pixel
// snapping for painted elements within the transform since we don't know
// the desired subpixel accumulation at this point, and the transform may
// include a scale.
if (!preserve3D) {
transformState.flatten();
transformState.setQuad(
FloatQuad(transformState.lastPlanarQuad().enclosingBoundingBox()));
}
} else {
transformState.move(toLayoutSize(containerOffset), accumulation);
}
// FIXME: We ignore the lightweight clipping rect that controls use, since if
// |o| is in mid-layout, its controlClipRect will be wrong. For overflow clip
// we use the values cached by the layer.
if (container->isBox() && container != ancestor &&
!toLayoutBox(container)->mapScrollingContentsRectToBoxSpace(
transformState, accumulation, visualRectFlags))
return false;
if (skipInfo.ancestorSkipped()) {
// If the ancestor is below the container, then we need to map the rect into
// ancestor's coordinates.
LayoutSize containerOffset =
ancestor->offsetFromAncestorContainer(container);
transformState.move(-containerOffset, accumulation);
// If the ancestor is fixed, then the rect is already in its coordinates so
// doesn't need viewport-adjusting.
if (ancestor->style()->position() != EPosition::kFixed &&
container->isLayoutView() && position == EPosition::kFixed) {
transformState.move(toLayoutView(container)->offsetForFixedPosition(true),
accumulation);
}
return true;
}
if (container->isLayoutView())
return toLayoutView(container)->mapToVisualRectInAncestorSpaceInternal(
ancestor, transformState, position == EPosition::kFixed ? IsFixed : 0,
visualRectFlags);
else
return container->mapToVisualRectInAncestorSpaceInternal(
ancestor, transformState, visualRectFlags);
}
void LayoutBox::inflateVisualRectForFilter(
TransformState& transformState) const {
if (!layer() || !layer()->paintsWithFilters())
return;
transformState.flatten();
LayoutRect rect(transformState.lastPlanarQuad().boundingBox());
transformState.setQuad(
FloatQuad(FloatRect(layer()->mapLayoutRectForFilter(rect))));
}
void LayoutBox::updateLogicalWidth() {
LogicalExtentComputedValues computedValues;
computeLogicalWidth(computedValues);
setLogicalWidth(computedValues.m_extent);
setLogicalLeft(computedValues.m_position);
setMarginStart(computedValues.m_margins.m_start);
setMarginEnd(computedValues.m_margins.m_end);
}
static float getMaxWidthListMarker(const LayoutBox* layoutObject) {
#if DCHECK_IS_ON()
DCHECK(layoutObject);
Node* parentNode = layoutObject->generatingNode();
DCHECK(parentNode);
DCHECK(isHTMLOListElement(parentNode) || isHTMLUListElement(parentNode));
DCHECK_NE(layoutObject->style()->textAutosizingMultiplier(), 1);
#endif
float maxWidth = 0;
for (LayoutObject* child = layoutObject->slowFirstChild(); child;
child = child->nextSibling()) {
if (!child->isListItem())
continue;
LayoutBox* listItem = toLayoutBox(child);
for (LayoutObject* itemChild = listItem->slowFirstChild(); itemChild;
itemChild = itemChild->nextSibling()) {
if (!itemChild->isListMarker())
continue;
LayoutBox* itemMarker = toLayoutBox(itemChild);
// Make sure to compute the autosized width.
if (itemMarker->needsLayout())
itemMarker->layout();
maxWidth = std::max<float>(
maxWidth, toLayoutListMarker(itemMarker)->logicalWidth().toFloat());
break;
}
}
return maxWidth;
}
DISABLE_CFI_PERF
void LayoutBox::computeLogicalWidth(
LogicalExtentComputedValues& computedValues) const {
computedValues.m_extent =
style()->containsSize() ? borderAndPaddingLogicalWidth() : logicalWidth();
computedValues.m_position = logicalLeft();
computedValues.m_margins.m_start = marginStart();
computedValues.m_margins.m_end = marginEnd();
// The parent box is flexing us, so it has increased or decreased our
// width. Use the width from the style context.
if (hasOverrideLogicalContentWidth()) {
computedValues.m_extent =
overrideLogicalContentWidth() + borderAndPaddingLogicalWidth();
return;
}
if (isOutOfFlowPositioned()) {
computePositionedLogicalWidth(computedValues);
return;
}
// FIXME: Account for writing-mode in flexible boxes.
// https://bugs.webkit.org/show_bug.cgi?id=46418
bool inVerticalBox = parent()->isDeprecatedFlexibleBox() &&
(parent()->style()->boxOrient() == VERTICAL);
bool stretching = (parent()->style()->boxAlign() == BSTRETCH);
// TODO (lajava): Stretching is the only reason why we don't want the box to
// be treated as a replaced element, so we could perhaps refactor all this
// logic, not only for flex and grid since alignment is intended to be applied
// to any block.
bool treatAsReplaced = shouldComputeSizeAsReplaced() &&
(!inVerticalBox || !stretching) &&
(!isGridItem() || !hasStretchedLogicalWidth());
const ComputedStyle& styleToUse = styleRef();
Length logicalWidthLength = treatAsReplaced
? Length(computeReplacedLogicalWidth(), Fixed)
: styleToUse.logicalWidth();
LayoutBlock* cb = containingBlock();
LayoutUnit containerLogicalWidth =
std::max(LayoutUnit(), containingBlockLogicalWidthForContent());
bool hasPerpendicularContainingBlock =
cb->isHorizontalWritingMode() != isHorizontalWritingMode();
if (isInline() && !isInlineBlockOrInlineTable()) {
// just calculate margins
computedValues.m_margins.m_start =
minimumValueForLength(styleToUse.marginStart(), containerLogicalWidth);
computedValues.m_margins.m_end =
minimumValueForLength(styleToUse.marginEnd(), containerLogicalWidth);
if (treatAsReplaced)
computedValues.m_extent =
std::max(LayoutUnit(floatValueForLength(logicalWidthLength, 0)) +
borderAndPaddingLogicalWidth(),
minPreferredLogicalWidth());
return;
}
LayoutUnit containerWidthInInlineDirection = containerLogicalWidth;
if (hasPerpendicularContainingBlock)
containerWidthInInlineDirection =
perpendicularContainingBlockLogicalHeight();
// Width calculations
if (treatAsReplaced) {
computedValues.m_extent =
LayoutUnit(logicalWidthLength.value()) + borderAndPaddingLogicalWidth();
} else {
LayoutUnit preferredWidth =
computeLogicalWidthUsing(MainOrPreferredSize, styleToUse.logicalWidth(),
containerWidthInInlineDirection, cb);
computedValues.m_extent = constrainLogicalWidthByMinMax(
preferredWidth, containerWidthInInlineDirection, cb);
}
// Margin calculations.
computeMarginsForDirection(
InlineDirection, cb, containerLogicalWidth, computedValues.m_extent,
computedValues.m_margins.m_start, computedValues.m_margins.m_end,
style()->marginStart(), style()->marginEnd());
if (!hasPerpendicularContainingBlock && containerLogicalWidth &&
containerLogicalWidth !=
(computedValues.m_extent + computedValues.m_margins.m_start +
computedValues.m_margins.m_end) &&
!isFloating() && !isInline() && !cb->isFlexibleBoxIncludingDeprecated() &&
!cb->isLayoutGrid()) {
LayoutUnit newMargin = containerLogicalWidth - computedValues.m_extent -
cb->marginStartForChild(*this);
bool hasInvertedDirection = cb->style()->isLeftToRightDirection() !=
style()->isLeftToRightDirection();
if (hasInvertedDirection)
computedValues.m_margins.m_start = newMargin;
else
computedValues.m_margins.m_end = newMargin;
}
if (styleToUse.textAutosizingMultiplier() != 1 &&
styleToUse.marginStart().type() == Fixed) {
Node* parentNode = generatingNode();
if (parentNode &&
(isHTMLOListElement(*parentNode) || isHTMLUListElement(*parentNode))) {
// Make sure the markers in a list are properly positioned (i.e. not
// chopped off) when autosized.
const float adjustedMargin =
(1 - 1.0 / styleToUse.textAutosizingMultiplier()) *
getMaxWidthListMarker(this);
bool hasInvertedDirection = cb->style()->isLeftToRightDirection() !=
style()->isLeftToRightDirection();
if (hasInvertedDirection)
computedValues.m_margins.m_end += adjustedMargin;
else
computedValues.m_margins.m_start += adjustedMargin;
}
}
}
LayoutUnit LayoutBox::fillAvailableMeasure(
LayoutUnit availableLogicalWidth) const {
LayoutUnit marginStart;
LayoutUnit marginEnd;
return fillAvailableMeasure(availableLogicalWidth, marginStart, marginEnd);
}
LayoutUnit LayoutBox::fillAvailableMeasure(LayoutUnit availableLogicalWidth,
LayoutUnit& marginStart,
LayoutUnit&