/* * Copyright (C) 1999 Lars Knoll (knoll@kde.org) * (C) 1999 Antti Koivisto (koivisto@kde.org) * (C) 2005 Allan Sandfeld Jensen (kde@carewolf.com) * Copyright (C) 2005-2025 Samuel Weinig (sam@webkit.org) * Copyright (C) 2005-2025 Apple Inc. All rights reserved. * Copyright (C) 2015-2019 Google Inc. 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 "config.h" #include "RenderBox.h" #include "AnchorPositionEvaluator.h" #include "BackgroundPainter.h" #include "BorderPainter.h" #include "BorderShape.h" #include "ContainerNodeInlines.h" #include "CSSFilter.h" #include "CSSFontSelector.h" #include "Document.h" #include "EditingInlines.h" #include "EventHandler.h" #include "FloatQuad.h" #include "FloatRoundedRect.h" #include "FloatingObjects.h" #include "FontBaseline.h" #include "GraphicsContext.h" #include "HTMLBodyElement.h" #include "HTMLButtonElement.h" #include "HTMLFieldSetElement.h" #include "HTMLFrameOwnerElement.h" #include "HTMLHtmlElement.h" #include "HTMLImageElement.h" #include "HTMLInputElement.h" #include "HTMLLegendElement.h" #include "HTMLNames.h" #include "HTMLSelectElement.h" #include "HTMLTextAreaElement.h" #include "HitTestResult.h" #include "InlineIteratorBoxInlines.h" #include "InlineIteratorInlineBox.h" #include "InlineIteratorLineBox.h" #include "InlineRunAndOffset.h" #include "LayoutIntegrationLineLayout.h" #include "LegacyRenderSVGResourceClipper.h" #include "LocalFrame.h" #include "LocalFrameViewInlines.h" #include "Page.h" #include "PaintInfo.h" #include "PositionedLayoutConstraints.h" #include "RenderBlockFlowInlines.h" #include "RenderBlockInlines.h" #include "RenderBoxFragmentInfo.h" #include "RenderBoxInlines.h" #include "RenderChildIterator.h" #include "RenderDeprecatedFlexibleBox.h" #include "RenderElementStyleInlines.h" #include "RenderFlexibleBox.h" #include "RenderFragmentContainer.h" #include "RenderGeometryMap.h" #include "RenderGrid.h" #include "RenderImage.h" #include "RenderInline.h" #include "RenderIterator.h" #include "RenderLayerCompositor.h" #include "RenderLayerInlines.h" #include "RenderLayerScrollableArea.h" #include "RenderLayoutState.h" #include "RenderListMarker.h" #include "RenderMathMLBlock.h" #include "RenderMultiColumnFlow.h" #include "RenderObjectInlines.h" #include "RenderSVGResourceClipper.h" #include "RenderTableCellInlines.h" #include "RenderTableCell.h" #include "RenderTheme.h" #include "RenderView.h" #include "ResizeObserverSize.h" #include "SVGClipPathElement.h" #include "SVGElementTypeHelpers.h" #include "ScrollAnimator.h" #include "ScrollbarTheme.h" #include "ScrollbarsController.h" #include "Settings.h" #include "StyleBoxShadow.h" #include "StyleComputedStyle+GettersInlines.h" #include "StyleComputedStyle+InitialInlines.h" #include "StylePrimitiveNumericTypes+Evaluation.h" #include "StyleTransformResolver.h" #include "TransformOperationData.h" #include "TransformState.h" #include #include #include #include #include #include namespace WebCore { WTF_MAKE_TZONE_ALLOCATED_IMPL(RenderBox); struct SameSizeAsRenderBox : public RenderBoxModelObject { virtual ~SameSizeAsRenderBox() = default; LayoutRect frameRect; LayoutBoxExtent marginBox; LayoutUnit preferredLogicalWidths[2]; void* pointers[1]; }; static_assert(sizeof(RenderBox) == sizeof(SameSizeAsRenderBox), "RenderBox should stay small"); using namespace HTMLNames; using OverrideSizeMap = SingleThreadWeakHashMap; static OverrideSizeMap* gOverridingLogicalHeightMap = nullptr; static OverrideSizeMap* gOverridingLogicalWidthMap = nullptr; using OverridingPreferredSizeMap = SingleThreadWeakHashMap; static OverridingPreferredSizeMap* gOverridingLogicalHeightMapForFlexBasisComputation = nullptr; static OverridingPreferredSizeMap* gOverridingLogicalWidthMapForFlexBasisComputation = nullptr; // FIXME: We should store these based on physical direction. using OverrideOptionalSizeMap = SingleThreadWeakHashMap; static OverrideOptionalSizeMap* gGridAreaContentLogicalHeightMap = nullptr; static OverrideOptionalSizeMap* gGridAreaContentLogicalWidthMap = 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; bool RenderBox::s_hadNonVisibleOverflow = false; RenderBox::RenderBox(Type type, Element& element, Style::ComputedStyle&& style, OptionSet flags, TypeSpecificFlags typeSpecificFlags) : RenderBoxModelObject(type, element, WTF::move(style), flags | TypeFlag::IsBox, typeSpecificFlags) { ASSERT(isRenderBox()); } RenderBox::RenderBox(Type type, Document& document, Style::ComputedStyle&& style, OptionSet flags, TypeSpecificFlags typeSpecificFlags) : RenderBoxModelObject(type, document, WTF::move(style), flags | TypeFlag::IsBox, typeSpecificFlags) { ASSERT(isRenderBox()); } // Do not add any code in below destructor. Add it to willBeDestroyed() instead. RenderBox::~RenderBox() = default; void RenderBox::willBeDestroyed() { if (frame().eventHandler().autoscrollRenderer() == this) frame().eventHandler().stopAutoscrollTimer(true); if (hasInitializedStyle()) { if (!style().scrollSnapAlign().isNone()) view().unregisterBoxWithScrollSnapPositions(*this); if (style().containerType().hasSizeContainment()) view().unregisterContainerQueryBox(*this); if (!style().anchorNames().isNone()) view().unregisterAnchor(*this); if (!style().positionTryFallbacks().isNone()) view().unregisterPositionTryBox(*this); if (Style::AnchorPositionEvaluator::isAnchorPositioned(style())) // Keep this in sync with styleDidChange(). layoutContext().unregisterAnchorScrollAdjusterFor(*this); if (hasPotentiallyScrollableOverflow()) // Keep this in sync with AnchorScrollAdjuster::addSnapshot() calls. layoutContext().removeScrollerFromAnchorScrollAdjusters(*this); } RenderBoxModelObject::willBeDestroyed(); } RenderFragmentContainer* RenderBox::clampToStartAndEndFragments(RenderFragmentContainer* fragment) const { CheckedPtr fragmentedFlow = enclosingFragmentedFlow(); ASSERT(isRenderView() || (fragment && fragmentedFlow)); if (isRenderView()) return fragment; // We need to clamp to the block, since we want any lines or blocks that overflow out of the // logical top or logical bottom of the block to size as though the border box in the first and // last fragments extended infinitely. Otherwise the lines are going to size according to the fragments // they overflow into, which makes no sense when this block doesn't exist in |fragment| at all. RenderFragmentContainer* startFragment = nullptr; RenderFragmentContainer* endFragment = nullptr; if (!fragmentedFlow->getFragmentRangeForBox(*this, startFragment, endFragment)) return fragment; if (fragment->logicalTopForFragmentedFlowContent() < startFragment->logicalTopForFragmentedFlowContent()) return startFragment; if (fragment->logicalTopForFragmentedFlowContent() > endFragment->logicalTopForFragmentedFlowContent()) return endFragment; return fragment; } bool RenderBox::hasFragmentRangeInFragmentedFlow() const { if (CheckedPtr fragmentedFlow = enclosingFragmentedFlow(); fragmentedFlow && fragmentedFlow->hasValidFragmentInfo()) return fragmentedFlow->hasCachedFragmentRangeForBox(*this); return false; } static RenderBlockFlow* NODELETE outermostBlockContainingFloatingObject(RenderBox& box) { ASSERT(box.isFloating()); RenderBlockFlow* parentBlock = nullptr; for (auto& ancestor : ancestorsOfType(box)) { if (!parentBlock || ancestor.containsFloat(box)) parentBlock = &ancestor; } return parentBlock; } void RenderBox::removeFloatingAndInvalidateForLayout() { ASSERT(isFloating()); if (renderTreeBeingDestroyed()) return; if (auto* ancestor = outermostBlockContainingFloatingObject(*this)) { ancestor->markSiblingsWithFloatsForLayout(this); ancestor->markAllDescendantsWithFloatsForLayout(this, false); } } void RenderBox::removeFloatingOrOutOfFlowChildFromBlockLists() { ASSERT(!renderTreeBeingDestroyed()); if (isFloating()) return removeFloatingAndInvalidateForLayout(); if (isOutOfFlowPositioned()) return RenderBlock::removeOutOfFlowBox(*this); ASSERT_NOT_REACHED(); } void RenderBox::styleWillChange(Style::Difference diff, const Style::ComputedStyle& newStyle) { s_hadNonVisibleOverflow = hasNonVisibleOverflow(); const Style::ComputedStyle* oldStyle = hasInitializedStyle() ? &style() : nullptr; if (oldStyle) { // The background of the root element or the body element could propagate up to // the canvas. Issue full repaint, when our style changes substantially. if (diff >= Style::DifferenceResult::Repaint && (isDocumentElementRenderer() || isBody())) { view().repaintRootContents(); if (Style::hasEntirelyFixedBackground(oldStyle->backgroundLayers()) != Style::hasEntirelyFixedBackground(newStyle.backgroundLayers())) view().compositor().rootLayerConfigurationChanged(); } // When a layout hint happens and an object's position style changes, we have to do a layout // to dirty the render tree using the old position value now. if (diff == Style::DifferenceResult::Layout && parent() && oldStyle->position() != newStyle.position()) { if (!oldStyle->hasOutOfFlowPosition() && newStyle.hasOutOfFlowPosition()) { // We are about to go out of flow. Before that takes place, we need to mark the // current containing block chain for preferred widths recalculation. setNeedsLayoutAndInvalidateContentLogicalWidths(); if (CheckedPtr flexContainer = dynamicDowncast(parent())) flexContainer->flexItemWillBeRemoved(*this); if (isInTopLayerOrBackdrop(style(), element())) { // Since top layer's containing block is driven by the associated element's state (see Element::isInTopLayerOrBackdrop) // and this state is set before styleWillChange call, dirtying ancestors starting from _this_ fails to mark the current ancestor chain properly. parent()->setChildNeedsLayout(); } } else scheduleLayout(markContainingBlocksForLayout()); if (oldStyle->position() != PositionType::Static && newStyle.hasOutOfFlowPosition()) parent()->setChildNeedsLayout(); if (isFloating() && !isOutOfFlowPositioned() && newStyle.hasOutOfFlowPosition()) removeFloatingOrOutOfFlowChildFromBlockLists(); } } else if (isBody()) view().repaintRootContents(); bool boxContributesSnapPositions = !newStyle.scrollSnapAlign().isNone(); if (boxContributesSnapPositions || (oldStyle && !oldStyle->scrollSnapAlign().isNone())) { if (boxContributesSnapPositions) view().registerBoxWithScrollSnapPositions(*this); else view().unregisterBoxWithScrollSnapPositions(*this); } if (newStyle.containerType().hasSizeContainment()) view().registerContainerQueryBox(*this); else if (oldStyle && oldStyle->containerType().hasSizeContainment()) view().unregisterContainerQueryBox(*this); if (!newStyle.positionTryFallbacks().isNone() && newStyle.hasOutOfFlowPosition()) view().registerPositionTryBox(*this); else if (oldStyle && !oldStyle->positionTryFallbacks().isNone() && oldStyle->hasOutOfFlowPosition()) view().unregisterPositionTryBox(*this); if (oldStyle && Style::AnchorPositionEvaluator::isAnchorPositioned(newStyle) != Style::AnchorPositionEvaluator::isAnchorPositioned(*oldStyle)) view().frameView().clearCachedHasAnchorPositionedViewportConstrainedObjects(); RenderBoxModelObject::styleWillChange(diff, newStyle); } void RenderBox::invalidateAncestorBackgroundObscurationStatus() { auto parentToInvalidate = parent(); for (unsigned i = 0; i < backgroundObscurationTestMaxDepth && parentToInvalidate; ++i) { parentToInvalidate->invalidateBackgroundObscurationStatus(); parentToInvalidate = parentToInvalidate->parent(); } } void RenderBox::styleDidChange(Style::Difference diff, const Style::ComputedStyle* oldStyle) { // Horizontal writing mode definition is updated in RenderBoxModelObject::updateFromStyle, // (as part of the RenderBoxModelObject::styleDidChange call below). So, we can safely cache the horizontal // writing mode value before style change here. bool oldHorizontalWritingMode = isHorizontalWritingMode(); RenderBoxModelObject::styleDidChange(diff, oldStyle); const Style::ComputedStyle& newStyle = style(); if (needsLayout() && oldStyle) { RenderBlock::removePercentHeightDescendant(*this); // Normally we can do optimized positioning layout for absolute/fixed positioned objects. There is one special case, however, which is // when the positioned object's margin-before is changed. In this case the parent has to get a layout in order to run margin collapsing // to determine the new static position. if (isOutOfFlowPositioned() && newStyle.hasStaticBlockPosition(isHorizontalWritingMode()) && oldStyle->marginBefore() != newStyle.marginBefore() && parent() && !parent()->normalChildNeedsLayout()) parent()->setChildNeedsLayout(); } if (RenderBlock::hasPercentHeightContainerMap() && firstChild() && oldHorizontalWritingMode != isHorizontalWritingMode()) RenderBlock::clearPercentHeightDescendantsFrom(*this); // If our zoom factor changes and we have a defined scrollLeft/Top, we need to adjust that value into the // new zoomed coordinate space. if (hasNonVisibleOverflow() && layer() && oldStyle && oldStyle->usedZoom() != newStyle.usedZoom()) { if (CheckedPtr scrollableArea = layer()->scrollableArea()) { ScrollPosition scrollPosition = scrollableArea->scrollPosition(); float zoomScaleFactor = newStyle.usedZoom() / oldStyle->usedZoom(); scrollPosition.scale(zoomScaleFactor); scrollableArea->setPostLayoutScrollPosition(scrollPosition); } } if (layer() && oldStyle && oldStyle->shouldPlaceVerticalScrollbarOnLeft() != newStyle.shouldPlaceVerticalScrollbarOnLeft()) { if (CheckedPtr scrollableArea = layer()->scrollableArea()) scrollableArea->scrollbarsController().scrollbarLayoutDirectionChanged(shouldPlaceVerticalScrollbarOnLeft() ? UserInterfaceLayoutDirection::RTL : UserInterfaceLayoutDirection::LTR); } bool isDocElementRenderer = isDocumentElementRenderer(); if (layer() && oldStyle && oldStyle->scrollbarWidth() != newStyle.scrollbarWidth()) { if (isDocElementRenderer) view().frameView().scrollbarWidthChanged(Style::toPlatform(newStyle.scrollbarWidth())); else if (CheckedPtr scrollableArea = layer()->scrollableArea()) scrollableArea->scrollbarWidthChanged(Style::toPlatform(newStyle.scrollbarWidth())); } if (layer() && oldStyle && oldStyle->scrollbarColor() != newStyle.scrollbarColor()) { std::optional scrollbarColor; if (auto value = newStyle.scrollbarColor().tryValue()) { Style::ColorResolver colorResolver { newStyle }; scrollbarColor = ScrollbarColor { .thumbColor = colorResolver.colorResolvingCurrentColor(value->thumb), .trackColor = colorResolver.colorResolvingCurrentColor(value->track) }; } if (isDocElementRenderer) view().frameView().scrollbarColorDidChange(scrollbarColor); else if (auto* scrollableArea = layer()->scrollableArea()) scrollableArea->scrollbarColorDidChange(scrollbarColor); } #if ENABLE(DARK_MODE_CSS) if (layer() && oldStyle && oldStyle->colorScheme() != newStyle.colorScheme()) { if (CheckedPtr scrollableArea = layer()->scrollableArea()) scrollableArea->invalidateScrollbars(); } #endif // Our opaqueness might have changed without triggering layout. if (diff >= Style::DifferenceResult::Repaint && diff <= Style::DifferenceResult::RepaintLayer) invalidateAncestorBackgroundObscurationStatus(); bool isBodyRenderer = isBody(); if (isDocElementRenderer || isBodyRenderer) { view().frameView().recalculateScrollbarOverlayStyle(); if (diff != Style::DifferenceResult::Equal) view().compositor().rootOrBodyStyleChanged(*this, oldStyle); } if ((oldStyle && !oldStyle->shapeOutside().isNone()) || !style().shapeOutside().isNone()) updateShapeOutsideInfoAfterStyleChange(style(), oldStyle, diff); updateGridPositionAfterStyleChange(style(), oldStyle); // Changing the position from/to absolute can potentially create/remove flex/grid items, as absolutely positioned // children of a flex/grid box are out-of-flow, and thus, not flex/grid items. This means that we need to clear // any override content size set by our container, because it would likely be incorrect after the style change. if (isOutOfFlowPositioned() && parent() && parent()->style().display().isFlexibleBoxIncludingDeprecatedOrGridFormattingContextBox()) clearOverridingSize(); if (oldStyle && oldStyle->hasOutOfFlowPosition() != style().hasOutOfFlowPosition()) { clearGridAreaContentSize(); if (auto* containingBlock = this->containingBlock(); containingBlock && oldStyle->hasOutOfFlowPosition()) { // When going from out-of-flow to inflow, the containing block gains new descendant content and its preferred width becomes invalid. containingBlock->setNeedsLayoutAndInvalidateContentLogicalWidths(); } } if (oldStyle && Style::AnchorPositionEvaluator::isAnchorPositioned(*oldStyle) && !Style::AnchorPositionEvaluator::isAnchorPositioned(style())) { // Only out-of-flow code manages these, so if we're changing we need to clear them. // Keep this in sync with willBeDestroyed(). layoutContext().unregisterAnchorScrollAdjusterFor(*this); } if ((layer() && diff == Style::DifferenceResult::Layout && hasNonVisibleOverflow()) || (oldStyle && oldStyle->isOverflowVisible() != style().isOverflowVisible())) layoutContext().invalidateAnchorDependenciesForScroller(*this); } static bool NODELETE hasEquivalentGridPositioningStyle(const Style::ComputedStyle& style, const Style::ComputedStyle& oldStyle) { return oldStyle.gridItemColumnStart() == style.gridItemColumnStart() && oldStyle.gridItemColumnEnd() == style.gridItemColumnEnd() && oldStyle.gridItemRowStart() == style.gridItemRowStart() && oldStyle.gridItemRowEnd() == style.gridItemRowEnd() && oldStyle.order() == style.order() && oldStyle.hasOutOfFlowPosition() == style.hasOutOfFlowPosition() && (oldStyle.gridTemplateColumns().subgrid == style.gridTemplateColumns().subgrid || style.gridTemplateColumns().orderedNamedLines.map.isEmpty()) && (oldStyle.gridTemplateRows().subgrid == style.gridTemplateRows().subgrid || style.gridTemplateRows().orderedNamedLines.map.isEmpty()); } void RenderBox::updateGridPositionAfterStyleChange(const Style::ComputedStyle& style, const Style::ComputedStyle* oldStyle) { if (!oldStyle) return; CheckedPtr parentGrid = dynamicDowncast(parent()); if (!parentGrid) 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()) || hasEquivalentGridPositioningStyle(style, *oldStyle)) 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. parentGrid->setNeedsItemPlacement(); } void RenderBox::updateShapeOutsideInfoAfterStyleChange(const Style::ComputedStyle& style, const Style::ComputedStyle* oldStyle, Style::Difference diff) { Style::ShapeOutside shapeOutside = style.shapeOutside(); Style::ShapeOutside oldShapeOutside = oldStyle ? oldStyle->shapeOutside() : Style::ComputedStyle::initialShapeOutside(); if (diff <= Style::DifferenceResult::RecompositeLayer) return; if (shapeOutside.isNone()) removeShapeOutsideInfo(); else ensureShapeOutsideInfo().markShapeAsDirty(); if (!shapeOutside.isNone() || shapeOutside != oldShapeOutside) markShapeOutsideDependentsForLayout(); } void RenderBox::updateFromStyle() { RenderBoxModelObject::updateFromStyle(); const Style::ComputedStyle& styleToUse = style(); bool isDocElementRenderer = isDocumentElementRenderer(); bool isViewObject = isRenderView(); // The root and the RenderView always paint their backgrounds/borders. if (isDocElementRenderer || isViewObject) setHasVisibleBoxDecorations(true); setFloating(!isOutOfFlowPositioned() && styleToUse.floating() != Float::None); // We also handle and , whose overflow applies to the viewport. if (!(effectiveOverflowX() == Overflow::Visible && effectiveOverflowY() == Overflow::Visible) && !isDocElementRenderer && isRenderBlock()) { bool boxHasNonVisibleOverflow = true; if (isBody()) { // Overflow on the body can propagate to the viewport under the following conditions. // (1) The root element is . // (2) We are the primary (can be checked by looking at document.body). // (3) The root element has visible overflow. // (4) No containment is set either on the body or on the html document element. Ref documentElement = *document().documentElement(); auto& documentElementRenderer = *documentElement->renderer(); if (is(documentElement) && document().body() == element() && documentElementRenderer.effectiveOverflowX() == Overflow::Visible && styleToUse.usedContain().isNone() && documentElementRenderer.style().usedContain().isNone()) { boxHasNonVisibleOverflow = false; } } // Check for overflow clip. // It's sufficient to just check one direction, since it's illegal to have visible on only one overflow value. if (boxHasNonVisibleOverflow) { if (!s_hadNonVisibleOverflow && hasRenderOverflow()) { // Erase the overflow. // Overflow changes have to result in immediate repaints of the entire layout overflow area because // repaints issued by removal of descendants get clipped using the updated style when they shouldn't. issueRepaint(visualOverflowRect(), ClipRepaintToLayer::Yes, ForceRepaint::Yes); issueRepaint(layoutOverflowRect(), ClipRepaintToLayer::Yes, ForceRepaint::Yes); } setHasNonVisibleOverflow(); } } setHasTransformRelatedProperty(computeHasTransformRelatedProperty(styleToUse)); setHasReflection(!styleToUse.boxReflect().isNone()); } bool RenderBox::computeHasTransformRelatedProperty(const Style::ComputedStyle& styleToUse) const { if (styleToUse.hasTransformRelatedProperty()) return true; if (!settings().css3DTransformBackfaceVisibilityInteroperabilityEnabled()) return false; if (styleToUse.backfaceVisibility() != BackfaceVisibility::Hidden) return false; if (!element()) return false; auto* parent = element()->parentElement(); if (!parent) return false; auto* parentRenderer = parent->renderer(); if (!parentRenderer) return false; return parentRenderer->style().usedTransformStyle3D() == TransformStyle3D::Preserve3D; } void RenderBox::layout() { StackStats::LayoutCheckPoint layoutCheckPoint; ASSERT(needsLayout()); RenderObject* child = firstChild(); if (!child) { clearNeedsLayout(); return; } LayoutStateMaintainer statePusher(*this, locationOffset(), writingMode().isBlockFlipped()); while (child) { if (child->needsLayout()) downcast(*child).layout(); ASSERT(!child->needsLayout()); child = child->nextSibling(); } invalidateBackgroundObscurationStatus(); clearNeedsLayout(); } // More IE extensions. clientWidth and clientHeight represent the interior of an object // excluding border and scrollbar. LayoutUnit RenderBox::clientWidth() const { return paddingBoxWidth(); } LayoutUnit RenderBox::clientHeight() const { return paddingBoxHeight(); } int RenderBox::scrollWidth() const { if (hasPotentiallyScrollableOverflow() && layer()) return layer()->scrollWidth(); // For objects with visible overflow, this matches IE. if (writingMode().isLogicalLeftInlineStart()) { // FIXME: This should use snappedIntSize() instead with absolute coordinates. return roundToInt(std::max(clientWidth(), layoutOverflowRect().maxX() - borderLeft())); } return roundToInt(clientWidth() - std::min(0, layoutOverflowRect().x() - borderLeft())); } int RenderBox::scrollHeight() const { if (hasPotentiallyScrollableOverflow() && layer()) return layer()->scrollHeight(); // For objects with visible overflow, this matches IE. // FIXME: Need to work right with writing modes. // FIXME: This should use snappedIntSize() instead with absolute coordinates. return roundToInt(std::max(clientHeight(), layoutOverflowRect().maxY() - borderTop())); } int RenderBox::scrollLeft() const { CheckedPtr scrollableArea = layer() ? layer()->scrollableArea() : nullptr; return (hasNonVisibleOverflow() && scrollableArea) ? scrollableArea->scrollPosition().x() : 0; } int RenderBox::scrollTop() const { CheckedPtr scrollableArea = layer() ? layer()->scrollableArea() : nullptr; return (hasNonVisibleOverflow() && scrollableArea) ? scrollableArea->scrollPosition().y() : 0; } bool RenderBox::shouldResetLogicalHeightBeforeLayout() const { auto* flexBoxParent = dynamicDowncast(parent()); return flexBoxParent && flexBoxParent->shouldResetFlexItemLogicalHeightBeforeLayout(); } void RenderBox::resetLogicalHeightBeforeLayoutIfNeeded() { if (shouldResetLogicalHeightBeforeLayout()) setLogicalHeight(0_lu); } static void setupWheelEventMonitor(RenderLayerScrollableArea& scrollableArea) { Ref page = scrollableArea.layer().renderer().page(); if (!page->isMonitoringWheelEvents()) return; scrollableArea.scrollAnimator().setWheelEventTestMonitor(page->wheelEventTestMonitor()); } void RenderBox::setScrollLeft(int newLeft, const ScrollPositionChangeOptions& options) { if (!hasPotentiallyScrollableOverflow() || !layer()) return; CheckedPtr scrollableArea = layer()->scrollableArea(); ASSERT(scrollableArea); setupWheelEventMonitor(*scrollableArea); scrollableArea->scrollToXPosition(newLeft, options); } void RenderBox::setScrollTop(int newTop, const ScrollPositionChangeOptions& options) { if (!hasPotentiallyScrollableOverflow() || !layer()) return; CheckedPtr scrollableArea = layer()->scrollableArea(); ASSERT(scrollableArea); setupWheelEventMonitor(*scrollableArea); scrollableArea->scrollToYPosition(newTop, options); } void RenderBox::setScrollPosition(const ScrollPosition& position, const ScrollPositionChangeOptions& options) { if (!hasPotentiallyScrollableOverflow() || !layer()) return; CheckedPtr scrollableArea = layer()->scrollableArea(); ASSERT(scrollableArea); setupWheelEventMonitor(*scrollableArea); scrollableArea->setScrollPosition(position, options); } void RenderBox::boundingRects(Vector& rects, const LayoutPoint& accumulatedOffset) const { rects.append({ accumulatedOffset, size() }); } void RenderBox::absoluteQuads(Vector& quads, bool* wasFixed) const { if (CheckedPtr fragmentedFlow = enclosingFragmentedFlow(); fragmentedFlow && fragmentedFlow->absoluteQuadsForBox(quads, wasFixed, *this)) return; auto localRect = FloatRect { 0, 0, width(), height() }; quads.append(localToAbsoluteQuad(localRect, MapCoordinatesMode::UseTransforms, wasFixed)); } void RenderBox::applyTransform(TransformationMatrix& t, const Style::ComputedStyle& style, const FloatRect& boundingBox, OptionSet options) const { Style::TransformResolver::applyTransform(t, style, TransformOperationData(boundingBox, this), options); } void RenderBox::constrainLogicalMinMaxSizesByAspectRatio(LayoutUnit& computedMinSize, LayoutUnit& computedMaxSize, LayoutUnit computedSize, MinimumSizeIsAutomaticContentBased minimumSizeType, ConstrainDimension dimension) const { // TODO: Here we use isSpecified() to present the definite value. This is not quite correct, for the definite value should also include // a size of the initial containing block and the “stretch-fit” sizing of non-replaced blocks if they have definite values. // See https://www.w3.org/TR/css-sizing-3/#definite const Style::ComputedStyle& styleToUse = style(); ASSERT(styleToUse.aspectRatio().hasRatio() || preferredAspectRatio().value_or(0.0)); auto logicalSize = dimension == ConstrainDimension::Width ? styleToUse.logicalWidth() : styleToUse.logicalHeight(); // https://www.w3.org/TR/css-sizing-4/#aspect-ratio-minimum if (minimumSizeType == MinimumSizeIsAutomaticContentBased::Yes) { // Only use Automatic Content-based Minimum Sizes in the ratio-dependent axis. if (logicalSize.isSpecified()) computedMinSize = std::min(computedMinSize, computedSize); computedMinSize = std::min(computedMinSize, computedMaxSize); } if (logicalSize.isSpecified()) return; // Sizing constraints in either axis (the origin axis) should be transferred through the preferred aspect ratio. See https://www.w3.org/TR/css-sizing-4/#aspect-ratio-size-transfers bool shouldCheckTransferredMinSize = dimension == ConstrainDimension::Width ? !styleToUse.logicalMinWidth().isSpecified() : !styleToUse.logicalMinHeight().isSpecified(); bool shouldCheckTransferredMaxSize = dimension == ConstrainDimension::Width ? !styleToUse.logicalMaxWidth().isSpecified() : !styleToUse.logicalMaxHeight().isSpecified(); if (!shouldCheckTransferredMaxSize && !shouldCheckTransferredMinSize) return; auto [transferredLogicalMinSize, transferredLogicalMaxSize] = dimension == ConstrainDimension::Width ? computeMinMaxLogicalWidthFromAspectRatio() : computeMinMaxLogicalHeightFromAspectRatio(); if (shouldCheckTransferredMaxSize && transferredLogicalMaxSize != LayoutUnit::max()) { // The transferred max size should be floored by the definite minimum size. if (!shouldCheckTransferredMinSize && minimumSizeType == MinimumSizeIsAutomaticContentBased::No) transferredLogicalMaxSize = std::max(transferredLogicalMaxSize, computedMinSize); computedMaxSize = std::min(computedMaxSize, transferredLogicalMaxSize); } if (shouldCheckTransferredMinSize && transferredLogicalMinSize > LayoutUnit()) { // The transferred min size should be capped by the definite maximum size. if (!shouldCheckTransferredMaxSize) transferredLogicalMinSize = std::min(transferredLogicalMinSize, computedMaxSize); computedMinSize = std::max(computedMinSize, transferredLogicalMinSize); } } LayoutUnit RenderBox::constrainLogicalWidthByMinMax(LayoutUnit logicalWidth, LayoutUnit availableWidth, const RenderBlock& cb, AllowIntrinsic allowIntrinsic) const { auto& styleToUse = style(); auto computedMaxWidth = LayoutUnit::max(); if (!styleToUse.logicalMaxWidth().isNone() && (allowIntrinsic == AllowIntrinsic::Yes || !styleToUse.logicalMaxWidth().isIntrinsic())) computedMaxWidth = computeLogicalWidthUsing(styleToUse.logicalMaxWidth(), availableWidth, cb); if (allowIntrinsic == AllowIntrinsic::No && styleToUse.logicalMinWidth().isIntrinsic()) return std::min(logicalWidth, computedMaxWidth); auto logicalMinWidth = styleToUse.logicalMinWidth(); auto minimumSizeType = MinimumSizeIsAutomaticContentBased::No; auto shouldComputeAutomaticMinimumFromAspectRatio = [&] { // "The automatic minimum size in the ratio-dependent axis of a box with a preferred aspect ratio // that is neither a replaced element nor a scroll container is its min-content size capped by // its maximum size." https://www.w3.org/TR/css-sizing-4/#aspect-ratio-minimum if (!logicalMinWidth.isAuto()) return false; if (!shouldComputeLogicalWidthFromAspectRatio()) return false; if (is(*this)) return false; auto& width = styleToUse.logicalWidth(); if (!width.isAuto() && !width.isMinContent() && !width.isMaxContent()) return false; if (effectiveOverflowInlineDirection() != Overflow::Visible) return false; // A stretched flex/grid item's cross size is determined by the container, not the aspect ratio. if (hasStretchedLogicalWidth()) return false; return true; }; if (shouldComputeAutomaticMinimumFromAspectRatio()) { logicalMinWidth = CSS::Keyword::MinContent { }; minimumSizeType = MinimumSizeIsAutomaticContentBased::Yes; } auto computedMinWidth = computeLogicalWidthUsing(logicalMinWidth, availableWidth, cb); if (styleToUse.aspectRatio().hasRatio()) constrainLogicalMinMaxSizesByAspectRatio(computedMinWidth, computedMaxWidth, logicalWidth, minimumSizeType, ConstrainDimension::Width); logicalWidth = std::min(logicalWidth, computedMaxWidth); return std::max(logicalWidth, computedMinWidth); } LayoutUnit RenderBox::constrainLogicalHeightByMinMax(LayoutUnit logicalHeight, std::optional intrinsicContentHeight, IsComputingIntrinsicSize isComputingIntrinsicSize) const { auto& styleToUse = style(); std::optional computedLogicalMaxHeight; if (!styleToUse.logicalMaxHeight().isNone()) { // Per CSS Sizing 3 section 5.1 rule (a), the entire value of a max-size // property containing a cyclic percentage is treated as the initial value // (none) for intrinsic size contributions. if (isComputingIntrinsicSize == IsComputingIntrinsicSize::No || !styleToUse.logicalMaxHeight().isPercentOrCalculated()) computedLogicalMaxHeight = computeLogicalHeightUsing(styleToUse.logicalMaxHeight(), intrinsicContentHeight); } auto logicalMinHeight = styleToUse.logicalMinHeight(); auto minimumSizeType = MinimumSizeIsAutomaticContentBased::No; auto shouldComputeAutomaticMinimumFromAspectRatio = [&] { // "The automatic minimum size in the ratio-dependent axis of a box with a preferred aspect ratio // that is neither a replaced element nor a scroll container is its min-content size capped by // its maximum size." https://www.w3.org/TR/css-sizing-4/#aspect-ratio-minimum if (!logicalMinHeight.isAuto()) return false; if (!shouldComputeLogicalHeightFromAspectRatio()) return false; if (is(*this)) return false; if (!intrinsicContentHeight) return false; if (effectiveOverflowBlockDirection() != Overflow::Visible) return false; // Stretch only overrides the automatic minimum when the container has a definite cross size. if (hasStretchedLogicalHeight() && containingBlock()->hasDefiniteLogicalHeight()) return false; return true; }; if (shouldComputeAutomaticMinimumFromAspectRatio()) { auto heightFromAspectRatio = blockSizeFromAspectRatio(borderAndPaddingLogicalWidth(), borderAndPaddingLogicalHeight(), style().logicalAspectRatio(), style().boxSizingForAspectRatio(), logicalWidth(), style().aspectRatio(), isRenderReplaced()) - borderAndPaddingLogicalHeight(); if (firstChild()) heightFromAspectRatio = std::max(heightFromAspectRatio, *intrinsicContentHeight); logicalMinHeight = Style::MinimumSize::Fixed { heightFromAspectRatio / styleToUse.usedZoomForLength().value }; minimumSizeType = MinimumSizeIsAutomaticContentBased::Yes; } if (logicalMinHeight.isMinContent() || logicalMinHeight.isMaxContent()) logicalMinHeight = CSS::Keyword::Auto { }; auto computedLogicalMinHeight = [&]() -> std::optional { if (isComputingIntrinsicSize == IsComputingIntrinsicSize::Yes && logicalMinHeight.isPercentOrCalculated()) { // Per CSS Sizing 3, cyclic percentage min-size values resolve with the // percentage treated as 0 during intrinsic size contributions. return adjustBorderBoxLogicalHeightForBoxSizing(Style::evaluateMinimum(logicalMinHeight, 0_lu, styleToUse.usedZoomForLength())); } return computeLogicalHeightUsing(logicalMinHeight, intrinsicContentHeight); }(); auto maxHeight = computedLogicalMaxHeight.value_or(LayoutUnit::max()); auto minHeight = computedLogicalMinHeight.value_or(0_lu); if (styleToUse.aspectRatio().hasRatio()) constrainLogicalMinMaxSizesByAspectRatio(minHeight, maxHeight, logicalHeight, minimumSizeType, ConstrainDimension::Height); logicalHeight = std::min(logicalHeight, maxHeight); return std::max(logicalHeight, minHeight); } LayoutUnit RenderBox::constrainContentBoxLogicalHeightByMinMax(LayoutUnit logicalHeight, std::optional 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. auto& styleToUse = style(); auto percentageLogicalHeight = styleToUse.logicalHeight().tryPercentage(); auto& logicalMaxHeight = styleToUse.logicalMaxHeight(); if (!logicalMaxHeight.isNone()) { if (auto percentageLogicalMaxHeight = logicalMaxHeight.tryPercentage(); percentageLogicalMaxHeight && percentageLogicalHeight) { auto availableLogicalHeight = logicalHeight / percentageLogicalHeight->value * 100; logicalHeight = std::min(logicalHeight, Style::evaluate(*percentageLogicalMaxHeight, LayoutUnit(availableLogicalHeight))); } else { if (auto computedContentLogicalMaxHeight = computeContentLogicalHeight(logicalMaxHeight, intrinsicContentHeight)) logicalHeight = std::min(logicalHeight, *computedContentLogicalMaxHeight); } } auto& logicalMinHeight = styleToUse.logicalMinHeight(); if (auto percentageLogicalMinHeight = logicalMinHeight.tryPercentage(); percentageLogicalMinHeight && percentageLogicalHeight) { auto availableLogicalHeight = logicalHeight / percentageLogicalHeight->value * 100; logicalHeight = std::max(logicalHeight, Style::evaluate(*percentageLogicalMinHeight, LayoutUnit(availableLogicalHeight))); } else { if (auto computedContentLogicalMinHeight = computeContentLogicalHeight(logicalMinHeight, intrinsicContentHeight)) logicalHeight = std::max(logicalHeight, *computedContentLogicalMinHeight); } return logicalHeight; } // FIXME: Despite the name, this returns rounded borders based on the padding box, which seems wrong. LayoutRoundedRect::Radii RenderBox::borderRadii() const { auto borderShape = BorderShape::shapeForBorderRect(style(), paddingBoxRectIncludingScrollbar()); return borderShape.deprecatedRoundedRect().radii(); } LayoutRect RenderBox::paddingBoxRect() const { auto offsetForScrollbar = 0_lu; auto verticalScrollbarWidth = 0_lu; auto horizontalScrollbarHeight = 0_lu; if (hasNonVisibleOverflow()) { verticalScrollbarWidth = this->verticalScrollbarWidth(); offsetForScrollbar = shouldPlaceVerticalScrollbarOnLeft() ? verticalScrollbarWidth : 0_lu; horizontalScrollbarHeight = this->horizontalScrollbarHeight(); } auto borderWidths = this->borderWidths(); return LayoutRect { borderWidths.left() + offsetForScrollbar, borderWidths.top(), width() - borderWidths.left() - borderWidths.right() - verticalScrollbarWidth, height() - borderWidths.top() - borderWidths.bottom() - horizontalScrollbarHeight }; } LayoutPoint RenderBox::contentBoxLocation() const { LayoutUnit verticalScrollbarSpace = (shouldPlaceVerticalScrollbarOnLeft() || style().scrollbarGutter().isStableBothEdges()) ? verticalScrollbarWidth() : 0; LayoutUnit horizontalScrollbarSpace = style().scrollbarGutter().isStableBothEdges() ? horizontalScrollbarHeight() : 0; return { borderLeft() + paddingLeft() + verticalScrollbarSpace, borderTop() + paddingTop() + horizontalScrollbarSpace }; } FloatRect RenderBox::referenceBoxRect(CSSBoxType boxType) const { switch (boxType) { case CSSBoxType::ContentBox: case CSSBoxType::FillBox: return contentBoxRect(); case CSSBoxType::PaddingBox: return paddingBoxRect(); case CSSBoxType::MarginBox: return marginBoxRect(); // stroke-box, view-box compute to border-box for HTML elements. case CSSBoxType::StrokeBox: case CSSBoxType::ViewBox: case CSSBoxType::BorderBox: case CSSBoxType::BoxMissing: return borderBoxRect(); } ASSERT_NOT_REACHED(); return { }; } IntRect RenderBox::absoluteContentBox() const { // This is wrong with transforms and flipped writing modes. IntRect rect = snappedIntRect(contentBoxRect()); FloatPoint absPos = localToAbsolute(); rect.move(absPos.x(), absPos.y()); return rect; } FloatQuad RenderBox::absoluteContentQuad() const { LayoutRect rect = contentBoxRect(); return localToAbsoluteQuad(FloatRect(rect)); } LayoutRect RenderBox::localOutlineBoundsRepaintRect() const { auto box = borderBoundingBox(); return applyVisualEffectOverflow(box); } LayoutRect RenderBox::outlineBoundsForRepaint(const RenderLayerModelObject* repaintContainer, const RenderGeometryMap* geometryMap) const { auto box = localOutlineBoundsRepaintRect(); if (repaintContainer != this) { FloatQuad containerRelativeQuad; if (geometryMap) containerRelativeQuad = geometryMap->mapToContainer(box, repaintContainer); else containerRelativeQuad = localToContainerQuad(FloatRect(box), repaintContainer); box = LayoutRect(containerRelativeQuad.boundingBox()); } // FIXME: layoutDelta needs to be applied in parts before/after transforms and // repaint containers. https://bugs.webkit.org/show_bug.cgi?id=23308 box.move(view().frameView().layoutContext().layoutDelta()); return LayoutRect(snapRectToDevicePixels(box, document().deviceScaleFactor())); } int RenderBox::reflectionOffset() const { auto reflection = style().boxReflect().tryReflection(); if (!reflection) return 0; if (reflection->direction == ReflectionDirection::Left || reflection->direction == ReflectionDirection::Right) return Style::evaluate(reflection->offset, borderBoxRect().width(), Style::ZoomNeeded { }); return Style::evaluate(reflection->offset, borderBoxRect().height(), Style::ZoomNeeded { }); } LayoutRect RenderBox::reflectedRect(const LayoutRect& r) const { auto reflection = style().boxReflect().tryReflection(); if (!reflection) return LayoutRect(); LayoutRect box = borderBoxRect(); LayoutRect result = r; switch (reflection->direction) { case ReflectionDirection::Below: result.setY(box.maxY() + reflectionOffset() + (box.maxY() - r.maxY())); break; case ReflectionDirection::Above: result.setY(box.y() - reflectionOffset() - box.height() + (box.maxY() - r.maxY())); break; case ReflectionDirection::Left: result.setX(box.x() - reflectionOffset() - box.width() + (box.maxX() - r.maxX())); break; case ReflectionDirection::Right: result.setX(box.maxX() + reflectionOffset() + (box.maxX() - r.maxX())); break; } return result; } bool RenderBox::fixedElementLaysOutRelativeToFrame(const LocalFrameView& frameView) const { return isFixedPositioned() && container()->isRenderView() && frameView.fixedElementsLayoutRelativeToFrame(); } bool RenderBox::includeVerticalScrollbarSize() const { return hasNonVisibleOverflow() && layer() && !layer()->hasOverlayScrollbars() && (style().overflowY() == Overflow::Scroll || style().overflowY() == Overflow::Auto || (style().overflowY() == Overflow::Hidden && !style().scrollbarGutter().isAuto())); } bool RenderBox::includeHorizontalScrollbarSize() const { return hasNonVisibleOverflow() && layer() && !layer()->hasOverlayScrollbars() && (style().overflowX() == Overflow::Scroll || style().overflowX() == Overflow::Auto || (style().overflowX() == Overflow::Hidden && !style().scrollbarGutter().isAuto())); } int RenderBox::verticalScrollbarWidth() const { CheckedPtr scrollableArea = layer() ? layer()->scrollableArea() : nullptr; if (!scrollableArea) return 0; return includeVerticalScrollbarSize() ? scrollableArea->verticalScrollbarWidth(OverlayScrollbarSizeRelevancy::IgnoreOverlayScrollbarSize, isHorizontalWritingMode()) : 0; } int RenderBox::horizontalScrollbarHeight() const { CheckedPtr scrollableArea = layer() ? layer()->scrollableArea() : nullptr; if (!scrollableArea) return 0; return includeHorizontalScrollbarSize() ? scrollableArea->horizontalScrollbarHeight(OverlayScrollbarSizeRelevancy::IgnoreOverlayScrollbarSize, isHorizontalWritingMode()) : 0; } int RenderBox::intrinsicScrollbarLogicalWidthIncludingGutter() const { if (!hasNonVisibleOverflow()) return 0; auto shouldIncludeScrollbarGutter = [](Style::ScrollbarGutter gutter, bool hasVisibleOverflow, Overflow overflow) { return (overflow == Overflow::Auto && (!gutter.isAuto() || hasVisibleOverflow)) || (overflow == Overflow::Hidden && !gutter.isAuto()); }; if (isHorizontalWritingMode() && ((style().overflowY() == Overflow::Scroll || shouldIncludeScrollbarGutter(style().scrollbarGutter(), hasScrollableOverflowY(), style().overflowY())) && !canUseOverlayScrollbars())) return style().scrollbarGutter().isStableBothEdges() ? verticalScrollbarWidth() * 2 : verticalScrollbarWidth(); if (!isHorizontalWritingMode() && ((style().overflowX() == Overflow::Scroll || shouldIncludeScrollbarGutter(style().scrollbarGutter(), hasScrollableOverflowX(), style().overflowX())) && !canUseOverlayScrollbars())) return style().scrollbarGutter().isStableBothEdges() ? horizontalScrollbarHeight() * 2 : horizontalScrollbarHeight(); return 0; } bool RenderBox::scrollLayer(ScrollDirection direction, ScrollGranularity granularity, unsigned stepCount, Element** stopElement) { CheckedPtr scrollableArea = layer() ? layer()->scrollableArea() : nullptr; if (scrollableArea && scrollableArea->scroll(direction, granularity, stepCount)) { if (stopElement) *stopElement = element(); return true; } return false; } bool RenderBox::scroll(ScrollDirection direction, ScrollGranularity granularity, unsigned stepCount, Element** stopElement, RenderBox* startBox, const IntPoint& wheelEventAbsolutePoint) { if (scrollLayer(direction, granularity, stepCount, stopElement)) return true; if (stopElement && *stopElement && *stopElement == element()) return true; RenderBlock* nextScrollBlock = containingBlock(); if (nextScrollBlock && !nextScrollBlock->isRenderView()) return nextScrollBlock->scroll(direction, granularity, stepCount, stopElement, startBox, wheelEventAbsolutePoint); return false; } bool RenderBox::logicalScroll(ScrollLogicalDirection direction, ScrollGranularity granularity, unsigned stepCount, Element** stopElement) { bool scrolled = false; if (CheckedPtr scrollableArea = layer() ? layer()->scrollableArea() : nullptr) { #if PLATFORM(COCOA) // On Mac only we reset the inline direction position when doing a document scroll (e.g., hitting Home/End). if (granularity == ScrollGranularity::Document) scrolled = scrollableArea->scroll(logicalToPhysical(ScrollInlineDirectionBackward, isHorizontalWritingMode(), writingMode().isBlockFlipped()), ScrollGranularity::Document, stepCount); #endif if (scrollableArea->scroll(logicalToPhysical(direction, isHorizontalWritingMode(), writingMode().isBlockFlipped()), granularity, stepCount)) scrolled = true; if (scrolled) { if (stopElement) *stopElement = element(); return true; } } if (stopElement && *stopElement && *stopElement == element()) return true; RenderBlock* b = containingBlock(); if (b && !b->isRenderView()) return b->logicalScroll(direction, granularity, stepCount, stopElement); return false; } bool RenderBox::canBeScrolledAndHasScrollableArea() const { return canBeProgramaticallyScrolled() && (hasHorizontalOverflow() || hasVerticalOverflow()); } bool RenderBox::isScrollableOrRubberbandableBox() const { return canBeScrolledAndHasScrollableArea(); } bool RenderBox::requiresLayerWithScrollableArea() const { // FIXME: This is wrong; these boxes' layers should not need ScrollableAreas via RenderLayer. if (isRenderView() || isDocumentElementRenderer()) return true; if (hasPotentiallyScrollableOverflow()) return true; if (style().resize() != Style::Resize::None) return true; if (isHTMLMarquee() && style().marqueeBehavior() != MarqueeBehavior::None) return true; return false; } // FIXME: This is badly named. overflow:hidden can be programmatically scrolled, yet this returns false in that case. bool RenderBox::canBeProgramaticallyScrolled() const { if (isRenderView()) return true; if (!hasPotentiallyScrollableOverflow()) return false; if (hasScrollableOverflowX() || hasScrollableOverflowY()) return true; return element() && element()->hasEditableStyle(); } bool RenderBox::usesCompositedScrolling() const { return hasNonVisibleOverflow() && hasLayer() && layer()->usesCompositedScrolling(); } void RenderBox::autoscroll(const IntPoint& position) { if (layer()) layer()->autoscroll(position); } // There are two kinds of renderer that can autoscroll. bool RenderBox::canAutoscroll() const { if (isRenderView()) return view().frameView().isScrollable(); // Check for a box that can be scrolled in its own right. if (canBeScrolledAndHasScrollableArea()) return true; return false; } // If specified point is in border belt, returned offset denotes direction of // scrolling. IntSize RenderBox::calculateAutoscrollDirection(const IntPoint& windowPoint) const { IntRect box(absoluteBoundingBoxRect()); box.moveBy(view().frameView().scrollPosition()); IntRect windowBox = view().frameView().contentsToWindow(box); IntPoint windowAutoscrollPoint = windowPoint; 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 - windowPoint; } RenderBox* RenderBox::findAutoscrollable(RenderObject* renderer) { while (renderer) { if (auto* box = dynamicDowncast(*renderer); box && box->canAutoscroll()) break; if (is(*renderer) && renderer->document().ownerElement()) renderer = renderer->document().ownerElement()->renderer(); else renderer = renderer->parent(); } return dynamicDowncast(renderer); } void RenderBox::panScroll(const IntPoint& source) { if (CheckedPtr scrollableArea = layer() ? layer()->scrollableArea() : nullptr) scrollableArea->panScrollFromPoint(source); } bool RenderBox::canUseOverlayScrollbars() const { return !style().usesLegacyScrollbarStyle() && ScrollbarTheme::theme().usesOverlayScrollbars(); } bool RenderBox::hasAutoScrollbar(ScrollbarOrientation orientation) const { if (!hasNonVisibleOverflow()) return false; auto isAutoOrScrollWithOverlayScrollbar = [&](Overflow overflow) { return overflow == Overflow::Auto || (overflow == Overflow::Scroll && canUseOverlayScrollbars()); }; switch (orientation) { case ScrollbarOrientation::Horizontal: return isAutoOrScrollWithOverlayScrollbar(style().overflowX()); case ScrollbarOrientation::Vertical: return isAutoOrScrollWithOverlayScrollbar(style().overflowY()); } return false; } bool RenderBox::hasAlwaysPresentScrollbar(ScrollbarOrientation orientation) const { if (!hasNonVisibleOverflow()) return false; auto isAlwaysVisibleScrollbar = [&](Overflow overflow) { return overflow == Overflow::Scroll && !canUseOverlayScrollbars(); }; switch (orientation) { case ScrollbarOrientation::Horizontal: return isAlwaysVisibleScrollbar(style().overflowX()); case ScrollbarOrientation::Vertical: return isAlwaysVisibleScrollbar(style().overflowY()); } return false; } bool RenderBox::shouldInvalidateContentWidths() const { return style().paddingStart().isPercentOrCalculated() || style().paddingEnd().isPercentOrCalculated() || (style().aspectRatio().hasRatio() && (hasRelativeLogicalHeight() || (isFlexItem() && hasStretchedLogicalHeight()))); } ScrollPosition RenderBox::scrollPosition() const { if (!hasPotentiallyScrollableOverflow()) return { 0, 0 }; ASSERT(hasLayer()); CheckedPtr scrollableArea = layer()->scrollableArea(); if (!scrollableArea) return { 0, 0 }; return scrollableArea->scrollPosition(); } ScrollPosition RenderBox::constrainedScrollPosition() const { if (!hasPotentiallyScrollableOverflow()) return { 0, 0 }; ASSERT(hasLayer()); auto* scrollableArea = layer()->scrollableArea(); if (!scrollableArea) return { 0, 0 }; return scrollableArea->constrainedScrollPosition(scrollableArea->scrollPosition()); } LayoutSize RenderBox::cachedSizeForOverflowClip() const { ASSERT(hasNonVisibleOverflow()); ASSERT(hasLayer()); return layer()->size(); } bool RenderBox::applyCachedClipAndScrollPosition(RepaintRects& rects, const RenderLayerModelObject* container, VisibleRectContext context) const { flipForWritingMode(rects); if (context.options.contains(VisibleRectContext::Option::ApplyCompositedContainerScrolls) || this != container || !usesCompositedScrolling()) rects.moveBy(-scrollPosition()); // For overflow:auto/scroll/hidden. // Do not clip scroll layer contents to reduce the number of repaints while scrolling. if ((!context.options.contains(VisibleRectContext::Option::ApplyCompositedClips) && usesCompositedScrolling()) || (!context.options.contains(VisibleRectContext::Option::ApplyContainerClip) && this == container)) { flipForWritingMode(rects); return true; } // height() is inaccurate if we're in the middle of a layout of this RenderBox, so use the // layer's size instead. Even if the layer's size is wrong, the layer itself will repaint // anyway if its size does change. LayoutRect clipRect(LayoutPoint(), cachedSizeForOverflowClip()); if (effectiveOverflowX() == Overflow::Visible) clipRect.expandToInfiniteX(); if (effectiveOverflowY() == Overflow::Visible) clipRect.expandToInfiniteY(); if (context.scrollMargin && (isScrollContainerX() || isScrollContainerY())) { auto borderWidths = this->borderWidths(); clipRect.contract(borderWidths); auto scrollMarginEdges = LayoutBoxExtent { Style::evaluate(context.scrollMargin->top(), clipRect.height(), Style::ZoomNeeded { }), Style::evaluate(context.scrollMargin->right(), clipRect.width(), Style::ZoomNeeded { }), Style::evaluate(context.scrollMargin->bottom(), clipRect.height(), Style::ZoomNeeded { }), Style::evaluate(context.scrollMargin->left(), clipRect.width(), Style::ZoomNeeded { }) }; clipRect.expand(scrollMarginEdges); } bool intersects; if (context.options.contains(VisibleRectContext::Option::UseEdgeInclusiveIntersection)) intersects = rects.edgeInclusiveIntersect(clipRect); else intersects = rects.intersect(clipRect); flipForWritingMode(rects); return intersects; } LayoutUnit RenderBox::minContentLogicalWidthContribution() const { if (hasInvalidContentLogicalWidths()) { SetLayoutNeededForbiddenScope layoutForbiddenScope(*this); const_cast(*this).computeIntrinsicLogicalWidthContributions(); } return m_minContentLogicalWidthContribution; } LayoutUnit RenderBox::maxContentLogicalWidthContribution() const { if (hasInvalidContentLogicalWidths()) { SetLayoutNeededForbiddenScope layoutForbiddenScope(*this); const_cast(*this).computeIntrinsicLogicalWidthContributions(); } return m_maxContentLogicalWidthContribution; } void RenderBox::setOverridingBorderBoxLogicalHeight(LayoutUnit height) { if (!gOverridingLogicalHeightMap) gOverridingLogicalHeightMap = new OverrideSizeMap(); gOverridingLogicalHeightMap->set(*this, height); } void RenderBox::setOverridingBorderBoxLogicalWidth(LayoutUnit width) { if (!gOverridingLogicalWidthMap) gOverridingLogicalWidthMap = new OverrideSizeMap(); gOverridingLogicalWidthMap->set(*this, width); } void RenderBox::clearOverridingBorderBoxLogicalHeight() { if (gOverridingLogicalHeightMap) gOverridingLogicalHeightMap->remove(*this); } void RenderBox::clearOverridingBorderBoxLogicalWidth() { if (gOverridingLogicalWidthMap) gOverridingLogicalWidthMap->remove(*this); } void RenderBox::clearOverridingSize() { clearOverridingBorderBoxLogicalHeight(); clearOverridingBorderBoxLogicalWidth(); } std::optional RenderBox::overridingBorderBoxLogicalWidth() const { if (!gOverridingLogicalWidthMap) return { }; if (auto result = gOverridingLogicalWidthMap->find(*this); result != gOverridingLogicalWidthMap->end()) return result->value; return { }; } std::optional RenderBox::overridingBorderBoxLogicalHeight() const { if (!gOverridingLogicalHeightMap) return { }; if (auto result = gOverridingLogicalHeightMap->find(*this); result != gOverridingLogicalHeightMap->end()) return result->value; return { }; } std::optional RenderBox::gridAreaContentWidth(WritingMode writingMode) const { if (writingMode.isHorizontal()) return gridAreaContentLogicalWidth(); return gridAreaContentLogicalHeight(); } std::optional RenderBox::gridAreaContentHeight(WritingMode writingMode) const { if (writingMode.isHorizontal()) return gridAreaContentLogicalHeight(); return gridAreaContentLogicalWidth(); } std::optional RenderBox::gridAreaContentLogicalWidth() const { if (!gGridAreaContentLogicalWidthMap) return { }; if (auto result = gGridAreaContentLogicalWidthMap->find(*this); result != gGridAreaContentLogicalWidthMap->end()) return result->value; return { }; } std::optional RenderBox::gridAreaContentLogicalHeight() const { if (!gGridAreaContentLogicalHeightMap) return { }; if (auto result = gGridAreaContentLogicalHeightMap->find(*this); result != gGridAreaContentLogicalHeightMap->end()) return result->value; return { }; } void RenderBox::setGridAreaContentLogicalWidth(GridAreaSize logicalWidth) { if (!gGridAreaContentLogicalWidthMap) gGridAreaContentLogicalWidthMap = new OverrideOptionalSizeMap; gGridAreaContentLogicalWidthMap->set(*this, logicalWidth); } void RenderBox::setGridAreaContentLogicalHeight(GridAreaSize logicalHeight) { if (!gGridAreaContentLogicalHeightMap) gGridAreaContentLogicalHeightMap = new OverrideOptionalSizeMap; gGridAreaContentLogicalHeightMap->set(*this, logicalHeight); } void RenderBox::clearGridAreaContentSize() { if (gGridAreaContentLogicalWidthMap) gGridAreaContentLogicalWidthMap->remove(*this); clearGridAreaContentLogicalHeight(); } void RenderBox::clearGridAreaContentLogicalHeight() { if (gGridAreaContentLogicalHeightMap) gGridAreaContentLogicalHeightMap->remove(*this); } std::optional RenderBox::overridingLogicalHeightForFlexBasisComputation() const { if (!gOverridingLogicalHeightMapForFlexBasisComputation) return { }; if (auto result = gOverridingLogicalHeightMapForFlexBasisComputation->find(*this); result != gOverridingLogicalHeightMapForFlexBasisComputation->end()) return result->value; return { }; } void RenderBox::setOverridingBorderBoxLogicalHeightForFlexBasisComputation(const Style::PreferredSize& logicalHeight) { if (!gOverridingLogicalHeightMapForFlexBasisComputation) gOverridingLogicalHeightMapForFlexBasisComputation = new OverridingPreferredSizeMap(); gOverridingLogicalHeightMapForFlexBasisComputation->set(*this, logicalHeight); } void RenderBox::clearOverridingLogicalHeightForFlexBasisComputation() { if (gOverridingLogicalHeightMapForFlexBasisComputation) gOverridingLogicalHeightMapForFlexBasisComputation->remove(*this); } std::optional RenderBox::overridingLogicalWidthForFlexBasisComputation() const { if (!gOverridingLogicalWidthMapForFlexBasisComputation) return { }; if (auto result = gOverridingLogicalWidthMapForFlexBasisComputation->find(*this); result != gOverridingLogicalWidthMapForFlexBasisComputation->end()) return result->value; return { }; } void RenderBox::setOverridingBorderBoxLogicalWidthForFlexBasisComputation(const Style::PreferredSize& logicalWidth) { if (!gOverridingLogicalWidthMapForFlexBasisComputation) gOverridingLogicalWidthMapForFlexBasisComputation = new OverridingPreferredSizeMap(); gOverridingLogicalWidthMapForFlexBasisComputation->set(*this, logicalWidth); } void RenderBox::clearOverridingLogicalWidthForFlexBasisComputation() { if (gOverridingLogicalWidthMapForFlexBasisComputation) gOverridingLogicalWidthMapForFlexBasisComputation->remove(*this); } void RenderBox::markMarginAsTrimmed(Style::MarginTrimSide newTrimmedMargin) { auto& rareData = ensureRareData(); rareData.trimmedMargins = rareData.trimmedMargins | newTrimmedMargin; } void RenderBox::clearTrimmedMarginsMarkings() { ASSERT(hasRareData()); ensureRareData().trimmedMargins = { }; } bool RenderBox::hasTrimmedMargin(std::optional marginTrimType) const { if (!isInFlow()) return false; if (!hasRareData()) return false; return marginTrimType ? rareData().trimmedMargins.contains(*marginTrimType) : !rareData().trimmedMargins.isEmpty(); } LayoutUnit RenderBox::adjustBorderBoxLogicalWidthForBoxSizing(const Style::Length& logicalWidth) const { auto width = LayoutUnit { logicalWidth.resolveZoom(style().usedZoomForLength()) }; auto bordersPlusPadding = borderAndPaddingLogicalWidth(); if (style().boxSizing() == BoxSizing::ContentBox) return width + bordersPlusPadding; return std::max(width, bordersPlusPadding); } LayoutUnit RenderBox::adjustBorderBoxLogicalWidthForBoxSizing(LayoutUnit computedLogicalWidth) const { auto bordersAndPadding = borderAndPaddingLogicalWidth(); if (style().boxSizing() == BoxSizing::ContentBox) return computedLogicalWidth + bordersAndPadding; return std::max(computedLogicalWidth, bordersAndPadding); } LayoutUnit RenderBox::adjustBorderBoxLogicalHeightForBoxSizing(LayoutUnit height) const { LayoutUnit bordersPlusPadding = borderAndPaddingLogicalHeight(); if (style().boxSizing() == BoxSizing::ContentBox) return height + bordersPlusPadding; return std::max(height, bordersPlusPadding); } LayoutUnit RenderBox::adjustContentBoxLogicalWidthForBoxSizing(const Style::Length& logicalWidth) const { auto width = LayoutUnit { logicalWidth.resolveZoom(style().usedZoomForLength()) }; if (style().boxSizing() == BoxSizing::ContentBox) return std::max(0_lu, width); return std::max(0_lu, width - borderAndPaddingLogicalWidth()); } LayoutUnit RenderBox::adjustContentBoxLogicalWidthForBoxSizing(LayoutUnit computedLogicalWidth) const { if (style().boxSizing() == BoxSizing::ContentBox) return std::max(0_lu, computedLogicalWidth); return std::max(0_lu, computedLogicalWidth - borderAndPaddingLogicalWidth()); } LayoutUnit RenderBox::adjustContentBoxLogicalHeightForBoxSizing(std::optional height) const { if (!height) return 0; LayoutUnit result = height.value(); if (style().boxSizing() == BoxSizing::BorderBox) result -= borderAndPaddingLogicalHeight(); return std::max(0_lu, result); } LayoutUnit RenderBox::adjustIntrinsicLogicalHeightForBoxSizing(LayoutUnit height) const { if (style().boxSizing() == BoxSizing::BorderBox) return height + borderAndPaddingLogicalHeight(); return height; } // Hit Testing bool RenderBox::hitTestVisualOverflow(const HitTestLocation& hitTestLocation, const LayoutPoint& accumulatedOffset) const { if (isRenderView()) return true; LayoutPoint adjustedLocation = accumulatedOffset + location(); LayoutRect overflowBox = visualOverflowRect(); flipForWritingMode(overflowBox); overflowBox.moveBy(adjustedLocation); return hitTestLocation.intersects(overflowBox); } bool RenderBox::hitTestClipPath(const HitTestLocation& hitTestLocation, const LayoutPoint& accumulatedOffset) const { auto offsetFromHitTestRoot = toLayoutSize(accumulatedOffset + location()); auto hitTestLocationInLocalCoordinates = hitTestLocation.point() - offsetFromHitTestRoot; auto hitsClipContent = [&](Element& element) -> bool { if (CheckedPtr clipper = dynamicDowncast(element.renderer())) return clipper->hitTestClipContent( FloatRect { borderBoxRect() }, hitTestLocationInLocalCoordinates); CheckedRef clipper = downcast(*element.renderer()); return clipper->hitTestClipContent( FloatRect { borderBoxRect() }, FloatPoint { hitTestLocationInLocalCoordinates }); }; return WTF::switchOn(style().clipPath(), [&](const Style::BasicShapePath& clipPath) { return Style::path(clipPath.shape(), referenceBoxRect(clipPath.referenceBox()), style().usedZoomForLength()).contains(hitTestLocationInLocalCoordinates, Style::windRule(clipPath.shape())); }, [&](const Style::ReferencePath& clipPath) { RefPtr element = document().getElementById(clipPath.fragment()); return !is(element) || !element->renderer() || hitsClipContent(*element); }, [&](const Style::BoxPath&) { return true; }, [&](const CSS::Keyword::None&) { return true; } ); } bool RenderBox::hitTestBorderRadius(const HitTestLocation& hitTestLocation, const LayoutPoint& accumulatedOffset) const { if (isRenderView() || !style().border().hasBorderRadius()) return true; LayoutPoint adjustedLocation = accumulatedOffset + location(); LayoutRect borderRect = borderBoxRect(); borderRect.moveBy(adjustedLocation); auto borderShape = BorderShape::shapeForBorderRect(style(), borderRect); // To handle non-round corners, BorderShape should do the hit-testing. return hitTestLocation.intersects(borderShape.deprecatedRoundedRect()); } bool RenderBox::nodeAtPoint(const HitTestRequest& request, HitTestResult& result, const HitTestLocation& locationInContainer, const LayoutPoint& accumulatedOffset, HitTestAction action) { LayoutPoint adjustedLocation = accumulatedOffset + location(); // Check kids first. for (RenderObject* child = lastChild(); child; child = child->previousSibling()) { if (!child->hasLayer() && child->nodeAtPoint(request, result, locationInContainer, adjustedLocation, action)) { updateHitTestResult(result, locationInContainer.point() - toLayoutSize(adjustedLocation)); return true; } } // Check our bounds next. For this purpose always assume that we can only be hit in the // foreground phase (which is true for replaced elements like images). LayoutRect boundsRect = borderBoxRect(); boundsRect.moveBy(adjustedLocation); if (visibleToHitTesting(request) && action == HitTestAction::Foreground && locationInContainer.intersects(boundsRect)) { if (!hitTestVisualOverflow(locationInContainer, accumulatedOffset)) return false; if (!hitTestClipPath(locationInContainer, accumulatedOffset)) return false; if (!hitTestBorderRadius(locationInContainer, accumulatedOffset)) return false; updateHitTestResult(result, locationInContainer.point() - toLayoutSize(adjustedLocation)); if (result.addNodeToListBasedTestResult(protect(nodeForHitTest()).get(), request, locationInContainer, boundsRect) == HitTestProgress::Stop) return true; } return RenderBoxModelObject::nodeAtPoint(request, result, locationInContainer, accumulatedOffset, action); } // --------------------- painting stuff ------------------------------- BleedAvoidance RenderBox::determineBleedAvoidance(GraphicsContext& context) const { if (context.paintingDisabled()) return BleedAvoidance::None; const Style::ComputedStyle& style = this->style(); if (!style.hasBackground() || !style.border().hasBorder() || !style.border().hasBorderRadius() || borderImageIsLoadedAndCanBeRendered()) return BleedAvoidance::None; if (!theme().mayNeedBleedAvoidance(style)) return BleedAvoidance::None; AffineTransform ctm = context.getCTM(); FloatSize contextScaling(static_cast(ctm.xScale()), static_cast(ctm.yScale())); // Because LayoutRoundedRect uses IntRect internally the inset applied by the // BleedAvoidance::ShrinkBackground strategy cannot be less than one integer // layout coordinate, even with subpixel layout enabled. To take that into // account, we clamp the contextScaling to 1.0 for the following test so // that borderObscuresBackgroundEdge can only return true if the border // widths are greater than 2 in both layout coordinates and screen // coordinates. // This precaution will become obsolete if LayoutRoundedRect is ever promoted to // a sub-pixel representation. if (contextScaling.width() > 1) contextScaling.setWidth(1); if (contextScaling.height() > 1) contextScaling.setHeight(1); if (borderObscuresBackgroundEdge(contextScaling)) return BleedAvoidance::ShrinkBackground; if (!style.hasUsedAppearance() && borderObscuresBackground() && backgroundHasOpaqueTopLayer()) return BleedAvoidance::BackgroundOverBorder; return BleedAvoidance::UseTransparencyLayer; } ControlPart* RenderBox::ensureControlPart() { auto& rareData = ensureRareData(); auto type = style().usedAppearance(); // Some form-controls may change because of zooming without recreating // a new renderer (e.g Menulist <-> MenulistButton). if (!rareData.controlPart || type != rareData.controlPart->type()) rareData.controlPart = theme().createControlPart(*this); return rareData.controlPart.get(); } ControlPart* RenderBox::ensureControlPartForRenderer() { return theme().canCreateControlPartForRenderer(*this) ? ensureControlPart() : nullptr; } ControlPart* RenderBox::ensureControlPartForBorderOnly() { return theme().canCreateControlPartForBorderOnly(*this) ? ensureControlPart() : nullptr; } ControlPart* RenderBox::ensureControlPartForDecorations() { return theme().canCreateControlPartForDecorations(*this) ? ensureControlPart() : nullptr; } void RenderBox::paintBoxDecorations(PaintInfo& paintInfo, const LayoutPoint& paintOffset) { if (!paintInfo.shouldPaintWithinRoot(*this)) return; LayoutRect paintRect = borderBoxRect(); paintRect.moveBy(paintOffset); adjustBorderBoxRectForPainting(paintRect); paintRect = theme().adjustedPaintRect(*this, paintRect); auto bleedAvoidance = determineBleedAvoidance(paintInfo.context()); BackgroundPainter backgroundPainter { *this, paintInfo }; // FIXME: Should eventually give the theme control over whether the box shadow should paint, since controls could have // custom shadows of their own. if (!BackgroundPainter::boxShadowShouldBeAppliedToBackground(*this, paintRect.location(), bleedAvoidance, { })) backgroundPainter.paintBoxShadow(paintRect, style(), Style::ShadowStyle::Normal); GraphicsContextStateSaver stateSaver(paintInfo.context(), false); if (bleedAvoidance == BleedAvoidance::UseTransparencyLayer) { // To avoid the background color bleeding out behind the border, we'll render background and border // into a transparency layer, and then clip that in one go (which requires setting up the clip before // beginning the layer). stateSaver.save(); auto borderShape = BorderShape::shapeForBorderRect(style(), paintRect); borderShape.clipToOuterShape(paintInfo.context(), document().deviceScaleFactor()); paintInfo.context().beginTransparencyLayer(1); } // If we have a native theme appearance, paint that before painting our background. // The theme will tell us whether or not we should also paint the CSS background. bool borderOrBackgroundPaintingIsNeeded = true; if (style().hasUsedAppearance()) { if (RefPtr control = ensureControlPartForRenderer()) borderOrBackgroundPaintingIsNeeded = theme().paint(*this, *control, paintInfo, paintRect); else borderOrBackgroundPaintingIsNeeded = theme().paint(*this, paintInfo, paintRect); } BorderPainter borderPainter { *this, paintInfo }; if (borderOrBackgroundPaintingIsNeeded) { if (bleedAvoidance == BleedAvoidance::BackgroundOverBorder) borderPainter.paintBorder(paintRect, style(), bleedAvoidance); backgroundPainter.paintBackground(paintRect, bleedAvoidance); if (style().hasUsedAppearance()) { if (RefPtr control = ensureControlPartForDecorations()) theme().paint(*this, *control, paintInfo, paintRect); else theme().paintDecorations(*this, paintInfo, paintRect); } } backgroundPainter.paintBoxShadow(paintRect, style(), Style::ShadowStyle::Inset); if (bleedAvoidance != BleedAvoidance::BackgroundOverBorder) { bool paintCSSBorder = false; if (!style().hasUsedAppearance()) paintCSSBorder = true; else if (borderOrBackgroundPaintingIsNeeded) { // The theme will tell us whether or not we should also paint the CSS border. if (RefPtr control = ensureControlPartForBorderOnly()) paintCSSBorder = theme().paint(*this, *control, paintInfo, paintRect); else paintCSSBorder = theme().paintBorderOnly(*this, paintInfo); } if (paintCSSBorder && style().border().hasVisibleBorderDecoration()) borderPainter.paintBorder(paintRect, style(), bleedAvoidance); } if (bleedAvoidance == BleedAvoidance::UseTransparencyLayer) paintInfo.context().endTransparencyLayer(); } bool RenderBox::getBackgroundPaintedExtent(const LayoutPoint& paintOffset, LayoutRect& paintedExtent) const { ASSERT(hasBackground()); LayoutRect backgroundRect = snappedIntRect(borderBoxRect()); Color backgroundColor = style().visitedDependentBackgroundColorApplyingColorFilter(); if (backgroundColor.isVisible()) { paintedExtent = backgroundRect; return true; } auto& layers = style().backgroundLayers(); if (!layers.usedFirst().hasImage() || layers.usedLength() > 1) { paintedExtent = backgroundRect; return true; } auto geometry = BackgroundPainter::calculateFillLayerImageGeometry(*this, nullptr, layers.usedFirst(), style().usedZoomForLength(), paintOffset, backgroundRect); paintedExtent = geometry.destinationRect; return !geometry.hasNonLocalGeometry; } bool RenderBox::backgroundIsKnownToBeOpaqueInRect(const LayoutRect& localRect) const { if (!BackgroundPainter::paintsOwnBackground(*this)) return false; Color backgroundColor = style().visitedDependentBackgroundColorApplyingColorFilter(); if (!backgroundColor.isOpaque()) 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().hasUsedAppearance()) return false; // FIXME: Check the opaqueness of background images. if (hasClip() || hasClipPath()) return false; // FIXME: Use rounded rect if border radius is present. if (style().border().hasBorderRadius()) return false; // FIXME: The background color clip is defined by the last layer. if (style().backgroundLayers().usedLength() > 1) return false; LayoutRect backgroundRect; switch (style().backgroundLayers().usedFirst().clip()) { case FillBox::BorderBox: backgroundRect = borderBoxRect(); break; case FillBox::PaddingBox: backgroundRect = paddingBoxRect(); break; case FillBox::ContentBox: backgroundRect = contentBoxRect(); break; default: break; } return backgroundRect.contains(localRect); } static bool isCandidateForOpaquenessTest(const RenderBox& childBox) { const Style::ComputedStyle& childStyle = childBox.style(); if (childStyle.position() != PositionType::Static && childBox.containingBlock() != childBox.parent()) return false; if (childStyle.usedVisibility() != Visibility::Visible) return false; if (!childStyle.shapeOutside().isNone()) return false; if (!childBox.width() || !childBox.height()) return false; if (CheckedPtr childLayer = childBox.layer()) { if (childLayer->isComposited()) return false; // FIXME: Deal with z-index. if (!childStyle.usedZIndex().isAuto()) return false; if (childLayer->isTransformed() || childLayer->isTransparent() || childLayer->hasFilter()) return false; if (!childBox.scrollPosition().isZero()) return false; } return true; } bool RenderBox::foregroundIsKnownToBeOpaqueInRect(const LayoutRect& localRect, unsigned maxDepthToTest) const { ASSERT(!isSkippedContentRoot(*this)); if (!maxDepthToTest) return false; for (auto& childBox : childrenOfType(*this)) { if (!isCandidateForOpaquenessTest(childBox)) continue; LayoutPoint childLocation = childBox.location(); if (childBox.isRelativelyPositioned()) childLocation.move(childBox.relativePositionOffset()); 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.style().position() == PositionType::Static) return false; continue; } if (childLocalRect.maxY() > childBox.height() || childLocalRect.maxX() > childBox.width()) continue; if (childBox.backgroundIsKnownToBeOpaqueInRect(childLocalRect)) return true; if (isSkippedContentRoot(childBox)) continue; if (childBox.foregroundIsKnownToBeOpaqueInRect(childLocalRect, maxDepthToTest - 1)) return true; } return false; } bool RenderBox::computeBackgroundIsKnownToBeObscured(const LayoutPoint& paintOffset) { ASSERT(!isSkippedContentRoot(*this)); // Test to see if the children trivially obscure the background. // FIXME: This test can be much more comprehensive. if (!hasBackground()) return false; // Root background painting is special. if (isDocumentElementRenderer()) return false; LayoutRect backgroundRect; if (!getBackgroundPaintedExtent(paintOffset, backgroundRect)) return false; if (CheckedPtr scrollableArea = layer() ? layer()->scrollableArea() : nullptr) { if (scrollableArea->scrollingMayRevealBackground()) return false; } return foregroundIsKnownToBeOpaqueInRect(backgroundRect, backgroundObscurationTestMaxDepth); } bool RenderBox::backgroundHasOpaqueTopLayer() const { auto& topLayer = style().backgroundLayers().usedFirst(); if (topLayer.clip() != FillBox::BorderBox) return false; // Clipped with local scrolling if (hasNonVisibleOverflow() && topLayer.attachment() == FillAttachment::LocalBackground) return false; if (topLayer.hasOpaqueImage(*this) && topLayer.hasRepeatXY() && topLayer.image().tryStyleImage()->canRender(this, style().usedZoom())) return true; // If there is only one layer and no image, check whether the background color is opaque. if (style().backgroundLayers().usedLength() == 1 && !topLayer.hasImage()) { Color bgColor = style().visitedDependentBackgroundColorApplyingColorFilter(); if (bgColor.isOpaque()) return true; } return false; } void RenderBox::paintMask(PaintInfo& paintInfo, const LayoutPoint& paintOffset) { if (!paintInfo.shouldPaintWithinRoot(*this) || style().usedVisibility() != Visibility::Visible || paintInfo.phase != PaintPhase::Mask || paintInfo.context().paintingDisabled()) return; LayoutRect paintRect = LayoutRect(paintOffset, size()); adjustBorderBoxRectForPainting(paintRect); paintMaskImages(paintInfo, paintRect); } void RenderBox::paintClippingMask(PaintInfo& paintInfo, const LayoutPoint& paintOffset) { if (!paintInfo.shouldPaintWithinRoot(*this) || style().usedVisibility() != Visibility::Visible || paintInfo.phase != PaintPhase::ClippingMask || paintInfo.context().paintingDisabled()) return; LayoutRect paintRect = LayoutRect(paintOffset, size()); if (document().settings().layerBasedSVGEngineEnabled() && WTF::holdsAlternative(style().clipPath())) { paintSVGClippingMask(paintInfo, paintRect); return; } paintInfo.context().fillRect(snappedIntRect(paintRect), Color::black); } void RenderBox::paintMaskImages(const PaintInfo& paintInfo, const LayoutRect& paintRect) { // Figure out if we need to push a transparency layer to render our mask. bool pushTransparencyLayer = false; bool compositedMask = hasLayer() && layer()->hasCompositedMask(); bool flattenCompositingLayers = paintInfo.paintBehavior.contains(PaintBehavior::FlattenCompositingLayers); CompositeOperator compositeOp = CompositeOperator::SourceOver; bool allMaskImagesLoaded = true; if (!compositedMask || flattenCompositingLayers) { pushTransparencyLayer = true; // Don't render a masked element until all the mask images have loaded, to prevent a flash of unmasked content. if (RefPtr maskBorder = style().maskBorderSource().tryStyleImage()) allMaskImagesLoaded &= maskBorder->isLoaded(this); allMaskImagesLoaded &= Style::imagesAreLoaded(style().maskLayers(), *this); paintInfo.context().setCompositeOperation(CompositeOperator::DestinationIn); paintInfo.context().beginTransparencyLayer(1); compositeOp = CompositeOperator::SourceOver; } if (allMaskImagesLoaded) { BackgroundPainter { *this, paintInfo }.paintFillLayers(Color(), style().maskLayers(), style().usedZoomForLength(), paintRect, BleedAvoidance::None, compositeOp); BorderPainter { *this, paintInfo }.paintNinePieceImage(paintRect, style(), style().maskBorder(), compositeOp); } if (pushTransparencyLayer) paintInfo.context().endTransparencyLayer(); } LayoutRect RenderBox::maskClipRect(const LayoutPoint& paintOffset) { auto& maskBorder = style().maskBorder(); if (!maskBorder.source().isNone()) { LayoutRect borderImageRect = borderBoxRect(); // Apply outsets to the border box. borderImageRect.expand(style().maskBorderOutsets()); return borderImageRect; } auto zoom = style().usedZoomForLength(); LayoutRect result; LayoutRect borderBox = borderBoxRect(); for (auto& maskLayer : style().maskLayers().usedValues()) { if (maskLayer.hasImage()) { // Masks should never have fixed attachment, so it's OK for paintContainer to be null. result.unite(BackgroundPainter::calculateFillLayerImageGeometry(*this, nullptr, maskLayer, zoom, paintOffset, borderBox).destinationRect); } } return result; } void RenderBox::imageChanged(WrappedImagePtr image, const IntRect*) { if (RefPtr source = style().borderImageSource().tryStyleImage(); source && source->data() == image) { if (parent()) repaint(); return; } if (RefPtr source = style().maskBorderSource().tryStyleImage(); source && source->data() == image) { if (parent()) repaint(); return; } if (!view().frameView().layoutContext().isInRenderTreeLayout() && isFloating()) { if (RefPtr shapeOutsideImage = style().shapeOutside().image(); shapeOutsideImage && shapeOutsideImage->data() == image) { ensureShapeOutsideInfo().markShapeAsDirty(); markShapeOutsideDependentsForLayout(); } } bool didFullRepaint = false; auto repaintForBackgroundAndMask = [&](auto& style) { if (!parent()) return; if (!didFullRepaint) didFullRepaint = repaintLayerRectsForImage(image, style.backgroundLayers(), style.usedZoomForLength(), true); if (!didFullRepaint) didFullRepaint = repaintLayerRectsForImage(image, style.maskLayers(), style.usedZoomForLength(), false); }; repaintForBackgroundAndMask(style()); if (auto* firstLineStyle = style().pseudoElementStyle({ PseudoElementType::FirstLine })) repaintForBackgroundAndMask(*firstLineStyle); bool isNonEmpty; RefPtr styleImage = Style::findLayerUsedImage(style().backgroundLayers(), image, isNonEmpty); if (styleImage && isNonEmpty) { incrementVisuallyNonEmptyPixelCountIfNeeded(flooredIntSize(styleImage->imageSize(this, style().usedZoom()))); if (auto styleable = Styleable::fromRenderer(*this)) protect(document())->didLoadImage(protect(styleable->element).get(), styleImage->cachedImage()); } if (!isComposited()) return; if (layer()->hasCompositedMask() && Style::findLayerUsedImage(style().maskLayers(), image, isNonEmpty)) layer()->contentChanged(ContentChangeType::MaskImage); if (styleImage) layer()->contentChanged(ContentChangeType::BackgroundImage); } void RenderBox::incrementVisuallyNonEmptyPixelCountIfNeeded(const IntSize& size) { if (didContibuteToVisuallyNonEmptyPixelCount()) return; view().frameView().incrementVisuallyNonEmptyPixelCount(size); setDidContibuteToVisuallyNonEmptyPixelCount(); } template bool RenderBox::repaintLayerRectsForImage(WrappedImagePtr image, const Layers& layers, Style::ZoomFactor zoom, bool drawingBackground) { LayoutRect rendererRect; RenderBox* layerRenderer = nullptr; for (auto& layer : layers.usedValues()) { if (RefPtr layerImage = layer.image().tryStyleImage(); layerImage && layerImage->data() == image && (layerImage->isLoaded(this) || layerImage->canRender(this, style().usedZoom()))) { // Now that we know this image is being used, compute the renderer and the rect if we haven't already. bool drawingRootBackground = drawingBackground && (isDocumentElementRenderer() || (isBody() && !document().documentElement()->renderer()->hasBackground())); if (!layerRenderer) { if (drawingRootBackground) { layerRenderer = &view(); auto& renderView = downcast(*layerRenderer); LayoutUnit rw = renderView.frameView().contentsWidth(); LayoutUnit rh = renderView.frameView().contentsHeight(); rendererRect = LayoutRect(-layerRenderer->marginLeft(), -layerRenderer->marginTop(), std::max(layerRenderer->width() + layerRenderer->horizontalMarginExtent() + layerRenderer->borderLeft() + layerRenderer->borderRight(), rw), std::max(layerRenderer->height() + layerRenderer->verticalMarginExtent() + layerRenderer->borderTop() + layerRenderer->borderBottom(), rh)); // If we're drawing the root background, then we want to use the bounds of the view // (since root backgrounds cover the canvas, not just the element). If the root element // is composited though, we need to issue the repaint to that root element. auto documentElementRenderer = downcast(document().documentElement()->renderer()); CheckedPtr rendererLayer = documentElementRenderer->layer(); if (rendererLayer && rendererLayer->isComposited()) layerRenderer = documentElementRenderer; } else { layerRenderer = this; rendererRect = borderBoxRect(); } } // FIXME: Figure out how to pass absolute position to calculateFillLayerImageGeometry (for pixel snapping) auto geometry = BackgroundPainter::calculateFillLayerImageGeometry(*layerRenderer, nullptr, layer, zoom, LayoutPoint(), rendererRect); if (geometry.hasNonLocalGeometry) { // Rather than incur the costs of computing the paintContainer for renderers with fixed backgrounds // in order to get the right destRect, just repaint the entire renderer. layerRenderer->repaint(); return true; } LayoutRect rectToRepaint = geometry.destinationRect; bool shouldClipToLayer = true; // If this is the root background layer, we may need to extend the repaintRect if the FrameView has an // extendedBackground. We should only extend the rect if it is already extending the full width or height // of the rendererRect. if (drawingRootBackground && view().frameView().hasExtendedBackgroundRectForPainting()) { shouldClipToLayer = false; IntRect extendedBackgroundRect = view().frameView().extendedBackgroundRectForPainting(); if (rectToRepaint.width() == rendererRect.width()) { rectToRepaint.move(extendedBackgroundRect.x(), 0); rectToRepaint.setWidth(extendedBackgroundRect.width()); } if (rectToRepaint.height() == rendererRect.height()) { rectToRepaint.move(0, extendedBackgroundRect.y()); rectToRepaint.setHeight(extendedBackgroundRect.height()); } } layerRenderer->repaintRectangle(rectToRepaint, shouldClipToLayer); if (geometry.destinationRect == rendererRect) return true; } } return false; } void RenderBox::clipToPaddingBoxShape(GraphicsContext& context, const LayoutPoint& accumulatedOffset, float deviceScaleFactor) const { auto borderShape = BorderShape::shapeForBorderRect(style(), LayoutRect(accumulatedOffset, size())); borderShape.clipToInnerShape(context, deviceScaleFactor); } void RenderBox::clipToContentBoxShape(GraphicsContext& context, const LayoutPoint& accumulatedOffset, float deviceScaleFactor) const { auto borderShape = borderShapeForContentClipping(LayoutRect { accumulatedOffset, size() }); borderShape.clipToInnerShape(context, deviceScaleFactor); } bool RenderBox::pushContentsClip(PaintInfo& paintInfo, const LayoutPoint& accumulatedOffset) { if (paintInfo.phase == PaintPhase::BlockBackground || paintInfo.phase == PaintPhase::SelfOutline || paintInfo.phase == PaintPhase::Mask) return false; bool isControlClip = paintInfo.phase != PaintPhase::EventRegion && hasControlClip(); bool isOverflowClip = hasNonVisibleOverflow() && !layer()->isSelfPaintingLayer(); if (!isControlClip && !isOverflowClip) return false; if (paintInfo.phase == PaintPhase::Outline) paintInfo.phase = PaintPhase::ChildOutlines; else if (paintInfo.phase == PaintPhase::ChildBlockBackground) { paintInfo.phase = PaintPhase::BlockBackground; paintObject(paintInfo, accumulatedOffset); paintInfo.phase = PaintPhase::ChildBlockBackgrounds; } float deviceScaleFactor = document().deviceScaleFactor(); FloatRect clipRect = snapRectToDevicePixels((isControlClip ? controlClipRect(accumulatedOffset) : overflowClipRect(accumulatedOffset, OverlayScrollbarSizeRelevancy::IgnoreOverlayScrollbarSize, paintInfo.phase)), deviceScaleFactor); paintInfo.context().save(); if (style().border().hasBorderRadius()) clipToPaddingBoxShape(paintInfo.context(), accumulatedOffset, deviceScaleFactor); paintInfo.context().clip(clipRect); if (paintInfo.phase == PaintPhase::EventRegion || paintInfo.phase == PaintPhase::Accessibility) paintInfo.regionContext->pushClip(enclosingIntRect(clipRect)); return true; } void RenderBox::popContentsClip(PaintInfo& paintInfo, PaintPhase originalPhase, const LayoutPoint& accumulatedOffset) { ASSERT(hasControlClip() || (hasNonVisibleOverflow() && !layer()->isSelfPaintingLayer())); if (paintInfo.phase == PaintPhase::EventRegion || paintInfo.phase == PaintPhase::Accessibility) paintInfo.regionContext->popClip(); paintInfo.context().restore(); if (originalPhase == PaintPhase::Outline) { paintInfo.phase = PaintPhase::SelfOutline; paintObject(paintInfo, accumulatedOffset); paintInfo.phase = originalPhase; } else if (originalPhase == PaintPhase::ChildBlockBackground) paintInfo.phase = originalPhase; } LayoutRect RenderBox::overflowClipRect(const LayoutPoint& location, OverlayScrollbarSizeRelevancy relevancy, PaintPhase) const { LayoutRect clipRect = borderBoxRect(); clipRect.setLocation(location + clipRect.location() + LayoutSize(borderLeft(), borderTop())); clipRect.setSize(clipRect.size() - LayoutSize(borderLeft() + borderRight(), borderTop() + borderBottom())); if (style().overflowX() == Overflow::Clip && style().overflowY() == Overflow::Visible) clipRect.expandToInfiniteY(); else if (style().overflowY() == Overflow::Clip && style().overflowX() == Overflow::Visible) clipRect.expandToInfiniteX(); // Subtract out scrollbars if we have them. if (CheckedPtr scrollableArea = layer() ? layer()->scrollableArea() : nullptr) { if (shouldPlaceVerticalScrollbarOnLeft()) clipRect.move(scrollableArea->verticalScrollbarWidth(relevancy, isHorizontalWritingMode()), 0); clipRect.contract(scrollableArea->verticalScrollbarWidth(relevancy, isHorizontalWritingMode()), scrollableArea->horizontalScrollbarHeight(relevancy, isHorizontalWritingMode())); } return clipRect; } LayoutRect RenderBox::clipRect(const LayoutPoint& location) const { auto borderBoxRect = this->borderBoxRect(); auto clipRect = LayoutRect(borderBoxRect.location() + location, borderBoxRect.size()); return WTF::switchOn(style().clip(), [&](const CSS::Keyword::Auto&) { return clipRect; }, [&](const Style::ClipRect& rect) { if (auto clipLeft = rect.value->left().tryLength()) { auto c = LayoutUnit { clipLeft->resolveZoom(Style::ZoomNeeded { }) }; clipRect.move(c, 0_lu); clipRect.contract(c, 0_lu); } // We don't use the fragment-specific border box's width and height since clip offsets are (stupidly) specified // from the left and top edges. Therefore it's better to avoid constraining to smaller widths and heights. if (auto clipRight = rect.value->right().tryLength()) clipRect.contract(width() - LayoutUnit { clipRight->resolveZoom(Style::ZoomNeeded { }) }, 0_lu); if (auto clipTop = rect.value->top().tryLength()) { auto c = LayoutUnit { clipTop->resolveZoom(Style::ZoomNeeded { }) }; clipRect.move(0_lu, c); clipRect.contract(0_lu, c); } if (auto clipBottom = rect.value->bottom().tryLength()) clipRect.contract(0_lu, height() - LayoutUnit { clipBottom->resolveZoom(Style::ZoomNeeded { }) }); return clipRect; } ); } LayoutUnit RenderBox::shrinkLogicalWidthToAvoidFloats(LayoutUnit childMarginStart, LayoutUnit childMarginEnd, const RenderBlock& containingBlock, std::optional logicalHeight) const { LayoutUnit logicalTopPosition = logicalTop(); auto resolvedLogicalHeight = logicalHeight.value_or(containingBlock.logicalHeightForChild(*this)); LayoutUnit result = containingBlock.availableLogicalWidthForLine(logicalTopPosition, resolvedLogicalHeight) - childMarginStart - 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. if (childMarginStart > 0) { LayoutUnit startContentSide = containingBlock.startOffsetForContent(); LayoutUnit startContentSideWithMargin = startContentSide + childMarginStart; LayoutUnit startOffset = containingBlock.startOffsetForLine(logicalTopPosition, resolvedLogicalHeight); if (startOffset > startContentSideWithMargin) result += childMarginStart; else result += startOffset - startContentSide; } if (childMarginEnd > 0) { LayoutUnit endContentSide = containingBlock.endOffsetForContent(); LayoutUnit endContentSideWithMargin = endContentSide + childMarginEnd; LayoutUnit endOffset = containingBlock.endOffsetForLine(logicalTopPosition, resolvedLogicalHeight); if (endOffset > endContentSideWithMargin) result += childMarginEnd; else result += endOffset - endContentSide; } return result; } LayoutUnit RenderBox::containingBlockLogicalWidthForContent() const { CheckedPtr containingBlock = this->containingBlock(); if (!containingBlock) { // Should not be called on detached renderer (e.g. during initial style setting). ASSERT_NOT_REACHED(); return { }; } if (isOutOfFlowPositioned()) { PositionedLayoutConstraints constraints(*this, LogicalBoxAxis::Inline); return constraints.containingInlineSize(); } if (isGridItem()) { ASSERT(is(containingBlock)); if (auto gridAreaContentLogicalWidth = this->gridAreaContentLogicalWidth()) return gridAreaContentLogicalWidth->value_or(0_lu); } return containingBlock->contentBoxLogicalWidth(); } LayoutUnit RenderBox::containingBlockLogicalHeightForContent(AvailableLogicalHeightType heightType) const { if (isGridItem()) { if (auto gridAreaContentLogicalHeight = this->gridAreaContentLogicalHeight(); gridAreaContentLogicalHeight && *gridAreaContentLogicalHeight) { // FIXME: Containing block for a grid item is the grid area it's located in. We need to return whatever // height value we get from gridAreaContentLogicalHeight() here, including std::nullopt. return gridAreaContentLogicalHeight->value(); } } if (auto* containingBlock = this->containingBlock()) return containingBlock->availableLogicalHeight(heightType); ASSERT_NOT_REACHED(); return 0_lu; } LayoutUnit RenderBox::containingBlockAvailableLineWidth() const { return containingBlock()->availableLogicalWidthForLine(logicalTop(), availableLogicalHeight(AvailableLogicalHeightType::IncludeMarginBorderPadding)); } LayoutUnit RenderBox::perpendicularContainingBlockLogicalHeight() const { if (isGridItem()) { if (auto gridAreaContentLogicalHeight = this->gridAreaContentLogicalHeight(); gridAreaContentLogicalHeight && *gridAreaContentLogicalHeight) return gridAreaContentLogicalHeight->value(); } auto* containingBlock = this->containingBlock(); if (auto overridingLogicalHeight = containingBlock->overridingBorderBoxLogicalHeight()) return containingBlock->contentBoxLogicalHeight(*overridingLogicalHeight); auto& containingBlockStyle = containingBlock->style(); auto& logicalHeight = containingBlockStyle.logicalHeight(); // FIXME: For now just support fixed heights. Eventually should support percentage heights as well. if (auto fixedLogicalHeight = logicalHeight.tryFixed()) { // Use the content box logical height as specified by the style. return containingBlock->adjustContentBoxLogicalHeightForBoxSizing(LayoutUnit { fixedLogicalHeight->resolveZoom(containingBlockStyle.usedZoomForLength()) }); } LayoutUnit fillFallbackExtent = containingBlockStyle.writingMode().isHorizontal() ? view().frameView().layoutSize().height() : view().frameView().layoutSize().width(); auto containingBlockHasIndefiniteHeight = [&] { // When the containing block's block size is indefinite, the orthogonal // child's available inline space is indefinite. Use the viewport fallback // per CSS Writing Modes 4, 7.3.1. if (!logicalHeight.isAuto()) return false; // max-height provides a constraint per the spec's fallback rules. if (!containingBlockStyle.logicalMaxHeight().isNone() && !containingBlockStyle.logicalMaxHeight().isMaxContent()) return false; // Quirks mode percentage walk can make auto-height blocks effectively definite. if (document().inQuirksMode()) return false; // Multicol flow threads get their height from the column. if (containingBlock->isRenderMultiColumnFlow()) return false; // Aspect ratio gives a definite height even with auto. if (containingBlock->shouldComputeLogicalHeightFromAspectRatio()) return false; return true; }; if (containingBlockHasIndefiniteHeight()) { view().addPercentHeightDescendant(const_cast(*this)); return fillFallbackExtent; } LayoutUnit fillAvailableExtent = containingBlock->availableLogicalHeight(AvailableLogicalHeightType::ExcludeMarginBorderPadding); view().addPercentHeightDescendant(const_cast(*this)); // FIXME: https://bugs.webkit.org/show_bug.cgi?id=158286 We also need to perform the same percentHeightDescendant treatment to the element which dictates the return value for containingBlock()->availableLogicalHeight() above. return std::min(fillAvailableExtent, fillFallbackExtent); } void RenderBox::mapLocalToContainer(const RenderLayerModelObject* ancestorContainer, TransformState& transformState, OptionSet mode, bool* wasFixed) const { if (ancestorContainer == this) return; if (!ancestorContainer && view().frameView().layoutContext().isPaintOffsetCacheEnabled()) { auto* layoutState = view().frameView().layoutContext().layoutState(); LayoutSize offset = layoutState->paintOffset() + locationOffset(); if (style().hasInFlowPosition() && layer()) offset += layer()->offsetForInFlowPosition(); transformState.move(offset); return; } bool containerSkipped; RenderElement* container = this->container(ancestorContainer, containerSkipped); if (!container) return; bool isFixedPos = isFixedPositioned(); // If this box has a transform, 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 (isFixedPos) mode.add(MapCoordinatesMode::IsFixed); else if (mode.contains(MapCoordinatesMode::IsFixed) && canContainFixedPositionObjects()) mode.remove(MapCoordinatesMode::IsFixed); if (wasFixed) *wasFixed = mode.contains(MapCoordinatesMode::IsFixed); LayoutSize containerOffset = offsetFromContainer(*container, LayoutPoint(transformState.mappedPoint())); // Remove sticky positioning from the offset if it should be ignored. This is done here in // order to avoid piping this flag down the method chain. if (mode.contains(MapCoordinatesMode::IgnoreStickyOffsets) && isStickilyPositioned()) containerOffset -= stickyPositionOffset(); // Clamp overscroll if requested, so we don't layout into it. if (mode.contains(MapCoordinatesMode::ClampOverscroll)) { if (CheckedPtr boxContainer = dynamicDowncast(container); boxContainer && boxContainer->hasPotentiallyScrollableOverflow()) containerOffset += boxContainer->scrollPosition() - boxContainer->constrainedScrollPosition(); } pushOntoTransformState(transformState, mode, ancestorContainer, container, containerOffset, containerSkipped); if (containerSkipped) return; mode.remove(MapCoordinatesMode::ApplyContainerFlip); container->mapLocalToContainer(ancestorContainer, transformState, mode, wasFixed); } const RenderElement* RenderBox::pushMappingToContainer(const RenderLayerModelObject* ancestorToStopAt, RenderGeometryMap& geometryMap) const { ASSERT(ancestorToStopAt != this); bool ancestorSkipped; RenderElement* container = this->container(ancestorToStopAt, ancestorSkipped); if (!container) return nullptr; pushOntoGeometryMap(geometryMap, ancestorToStopAt, container, ancestorSkipped); return ancestorSkipped ? ancestorToStopAt : container; } void RenderBox::mapAbsoluteToLocalPoint(OptionSet mode, TransformState& transformState) const { bool isFixedPos = isFixedPositioned(); if (isFixedPos) mode.add(MapCoordinatesMode::IsFixed); else if (mode.contains(MapCoordinatesMode::IsFixed) && canContainFixedPositionObjects()) { // If this box has a transform, 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. mode.remove(MapCoordinatesMode::IsFixed); } RenderBoxModelObject::mapAbsoluteToLocalPoint(mode, transformState); } LayoutSize RenderBox::offsetFromContainer(const RenderElement& container, const LayoutPoint&, bool* offsetDependsOnPoint) const { // A fragment "has" boxes inside it without being their container. ASSERT(&container == this->container() || is(container)); LayoutSize offset; if (isInFlowPositioned()) offset += offsetForInFlowPosition(); if (!isInline() || isBlockLevelReplacedOrAtomicInline()) offset += topLeftLocationOffset(); if (auto* boxContainer = dynamicDowncast(container)) offset -= toLayoutSize(boxContainer->scrollPosition()); if (isAbsolutelyPositioned() && container.isInFlowPositioned()) { if (auto* inlineContainer = dynamicDowncast(container)) offset += inlineContainer->offsetForInFlowPositionedInline(this); } if (offsetDependsOnPoint) *offsetDependsOnPoint |= is(container); return offset; } auto RenderBox::localRectsForRepaint(RepaintOutlineBounds repaintOutlineBounds) const -> RepaintRects { if (isInsideEntirelyHiddenLayer()) return { }; auto overflowRect = visualOverflowRect(); // FIXME: layoutDelta needs to be applied in parts before/after transforms and // repaint containers. https://bugs.webkit.org/show_bug.cgi?id=23308 overflowRect.move(view().frameView().layoutContext().layoutDelta()); auto rects = RepaintRects { overflowRect }; if (repaintOutlineBounds == RepaintOutlineBounds::Yes) rects.outlineBoundsRect = localOutlineBoundsRepaintRect(); return rects; } auto RenderBox::computeVisibleRectsUsingPaintOffset(const RepaintRects& rects) const -> RepaintRects { auto adjustedRects = rects; auto* layoutState = view().frameView().layoutContext().layoutState(); if (hasLayer() && layer()->transform()) adjustedRects.transform(*layer()->transform(), document().deviceScaleFactor()); // We can't trust the bits on RenderObject, because this might be called while re-resolving style. if (style().hasInFlowPosition() && layer()) adjustedRects.move(layer()->offsetForInFlowPosition()); adjustedRects.moveBy(location()); adjustedRects.move(layoutState->paintOffset()); if (layoutState->isClipped()) adjustedRects.clippedOverflowRect.intersect(layoutState->clipRect()); return adjustedRects; } auto RenderBox::computeVisibleRectsInContainer(const RepaintRects& rects, const RenderLayerModelObject* container, VisibleRectContext context) const -> std::optional { // The rect we compute at each step is shifted by our x/y offset in the parent container's coordinate space. // Only when we cross a writing mode boundary will we have to possibly flipForWritingMode (to convert into a more appropriate // offset corner for the enclosing container). This allows for a fully RL or BT document to repaint // properly even during layout, since the rect remains flipped all the way until the end. // // RenderView::computeVisibleRectInContainer then converts the rect to physical coordinates. We also convert to // physical when we hit a repaint container boundary. Therefore the final rect returned is always in the // physical coordinate space of the container. const Style::ComputedStyle& styleToUse = style(); // Paint offset cache is only valid for root-relative, non-fixed position repainting if (view().frameView().layoutContext().isPaintOffsetCacheEnabled() && !container && styleToUse.position() != PositionType::Fixed && !context.options.contains(VisibleRectContext::Option::UseEdgeInclusiveIntersection)) return computeVisibleRectsUsingPaintOffset(rects); auto adjustedRects = rects; if (hasReflection()) { auto reflectedRects = RepaintRects { reflectedRect(adjustedRects.clippedOverflowRect) }; adjustedRects.unite(reflectedRects); } if (container == this) { if (container->writingMode().isBlockFlipped()) flipForWritingMode(adjustedRects); if (context.descendantNeedsEnclosingIntRect) adjustedRects.encloseToIntRects(); return adjustedRects; } bool containerIsSkipped; auto* localContainer = this->container(container, containerIsSkipped); if (!localContainer) return adjustedRects; if (isWritingModeRoot()) { if (!isOutOfFlowPositioned() || !context.dirtyRectIsFlipped) { flipForWritingMode(adjustedRects); context.dirtyRectIsFlipped = true; } } auto locationOffset = this->locationOffset(); // FIXME: This is needed as long as RenderWidget snaps to integral size/position. // is() is a fast bit check, is() is a virtual function call. if (is(this) && is(this)) { LayoutSize flooredLocationOffset = flooredIntSize(locationOffset); adjustedRects.expand(locationOffset - flooredLocationOffset); locationOffset = flooredLocationOffset; context.descendantNeedsEnclosingIntRect = true; } else if (auto* columnFlow = dynamicDowncast(*this)) { // We won't normally run this code. Only when the container is null (i.e., we're trying // to get the rect in view coordinates) will we come in here, since normally container // will be set and we'll stop at the flow thread. This case is mainly hit by the check for whether // or not images should animate. // FIXME: Just as with offsetFromContainer, we aren't really handling objects that span multiple columns properly. LayoutPoint physicalPoint(flipForWritingMode(adjustedRects.clippedOverflowRect.location())); if (auto* fragment = columnFlow->physicalTranslationFromFlowToFragment((physicalPoint))) { adjustedRects.clippedOverflowRect.setLocation(fragment->flipForWritingMode(physicalPoint)); return fragment->computeVisibleRectsInContainer(adjustedRects, container, context); } } // 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. auto position = styleToUse.position(); if (hasLayer() && layer()->isTransformed()) { context.hasPositionFixedDescendant = position == PositionType::Fixed; adjustedRects.transform(layer()->currentTransform(), document().deviceScaleFactor()); } else if (position == PositionType::Fixed) context.hasPositionFixedDescendant = true; adjustedRects.move(locationOffset); if (position == PositionType::Absolute && localContainer->isInFlowPositioned() && is(*localContainer)) { auto offsetForInFlowPosition = downcast(*localContainer).offsetForInFlowPositionedInline(this); adjustedRects.move(offsetForInFlowPosition); } else if (styleToUse.hasInFlowPosition() && layer()) { // Apply the relative position offset when invalidating a rectangle. The layer // is translated, but the render box isn't, so we need to do this to get the // right dirty rect. Since this is called from RenderObject::setStyle, the relative position // flag on the RenderObject has been cleared, so use the one on the style(). auto offsetForInFlowPosition = layer()->offsetForInFlowPosition(); adjustedRects.move(offsetForInFlowPosition); } if (localContainer->hasNonVisibleOverflow()) { bool isEmpty = !downcast(*localContainer).applyCachedClipAndScrollPosition(adjustedRects, container, context); if (isEmpty) { if (context.options.contains(VisibleRectContext::Option::UseEdgeInclusiveIntersection)) return std::nullopt; return adjustedRects; } } if (containerIsSkipped) { // If the container is below localContainer, then we need to map the rect into container's coordinates. LayoutSize containerOffset = container->offsetFromAncestorContainer(*localContainer); adjustedRects.move(-containerOffset); return adjustedRects; } return localContainer->computeVisibleRectsInContainer(adjustedRects, container, context); } void RenderBox::repaintDuringLayoutIfMoved(const LayoutRect& oldRect) { if (oldRect.location() != m_frameRect.location()) { LayoutRect newRect = m_frameRect; // The child moved. Invalidate the object's old and new positions. We have to do this // since the object may not have gotten a layout. m_frameRect = oldRect; repaint(); repaintOverhangingFloats(true); m_frameRect = newRect; repaint(); repaintOverhangingFloats(true); } } void RenderBox::repaintOverhangingFloats(bool) { } void RenderBox::updateLogicalWidth() { LogicalExtentComputedValues computedValues; computeLogicalWidth(computedValues); setLogicalWidth(computedValues.extent); setLogicalLeft(computedValues.position); setMarginStart(computedValues.margins.start); setMarginEnd(computedValues.margins.end); } static LayoutUnit inlineSizeFromAspectRatio(LayoutUnit borderPaddingInlineSum, LayoutUnit borderPaddingBlockSum, double aspectRatioValue, BoxSizing boxSizing, LayoutUnit blockSize, const Style::AspectRatio& aspectRatio, bool isRenderReplaced) { if (boxSizing == BoxSizing::BorderBox && aspectRatio.isRatio() && !isRenderReplaced) return std::max(borderPaddingInlineSum, LayoutUnit(blockSize * aspectRatioValue)); return LayoutUnit((blockSize - borderPaddingBlockSum) * aspectRatioValue) + borderPaddingInlineSum; } static bool NODELETE shouldMarginInlineEndContributeToScrollableOverflow(auto& renderer) { auto isSupportedContent = renderer.isGridItem() || renderer.isFlexItemIncludingDeprecated() || (renderer.isInFlow() && renderer.parent()->isBlockContainer()); if (!isSupportedContent) return false; auto& parentStyle = renderer.parent()->style(); if (parentStyle.overflowX() != Overflow::Visible && parentStyle.overflowX() != Overflow::Clip) return true; return parentStyle.overflowY() != Overflow::Visible && parentStyle.overflowY() != Overflow::Clip; } void RenderBox::computeLogicalWidth(LogicalExtentComputedValues& computedValues) const { computedValues.extent = logicalWidth(); computedValues.position = logicalLeft(); computedValues.margins.start = marginStart(); computedValues.margins.end = marginEnd(); if (isOutOfFlowPositioned()) { ASSERT(!overridingBorderBoxLogicalWidth()); ASSERT(!overridingLogicalWidthForFlexBasisComputation()); // FIXME: This calculation is not patched for block-flow yet. // https://bugs.webkit.org/show_bug.cgi?id=46500 computeOutOfFlowPositionedLogicalWidth(computedValues); return; } // The parent box is flexing us, so it has increased or decreased our width. Use the width from the style context. // FIXME: Account for block-flow in flexible boxes (webkit.org/b/46418) if (auto logicalWidth = (parent()->isFlexibleBoxIncludingDeprecated() ? this->overridingBorderBoxLogicalWidth() : std::nullopt)) { computedValues.extent = *logicalWidth; return; } // FIXME: 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. auto treatAsReplaced = [&] { // FIXME: Account for block-flow in flexible boxes. // https://bugs.webkit.org/show_bug.cgi?id=46418 auto& parent = *this->parent(); bool inVerticalBox = parent.isRenderDeprecatedFlexibleBox() && (parent.style().boxOrient() == BoxOrient::Vertical); bool stretching = (parent.style().boxAlign() == BoxAlignment::Stretch); auto isReplaced = is(*this) && (!inVerticalBox || !stretching); if (!isReplaced) return false; return !isGridItem() || !hasStretchedLogicalWidth(); }(); auto usedLogicalWidthLength = [&] -> Style::PreferredSize { if (auto overridingLogicalWidthLength = overridingLogicalWidthForFlexBasisComputation()) return *overridingLogicalWidthLength; return style().logicalWidth(); }(); auto containerLogicalWidth = std::max(0_lu, containingBlockLogicalWidthForContent()); auto& styleToUse = style(); if (isInline() && is(*this)) { // just calculate margins computedValues.margins.start = Style::evaluateMinimum(styleToUse.marginStart(), containerLogicalWidth, styleToUse.usedZoomForLength()); computedValues.margins.end = Style::evaluateMinimum(styleToUse.marginEnd(), containerLogicalWidth, styleToUse.usedZoomForLength()); if (treatAsReplaced) { auto evaluatedWidth = downcast(*this).computeReplacedLogicalWidth(); auto totalWidth = evaluatedWidth + borderAndPaddingLogicalWidth(); computedValues.extent = std::max(totalWidth, minContentLogicalWidthContribution()); } return; } auto& containingBlock = *this->containingBlock(); bool hasPerpendicularContainingBlock = containingBlock.isHorizontalWritingMode() != isHorizontalWritingMode(); // Width calculations auto logicalWidth = [&] { if (auto overridingLogicalWidth = this->overridingBorderBoxLogicalWidth()) return *overridingLogicalWidth; if (treatAsReplaced) return downcast(*this).computeReplacedLogicalWidth() + borderAndPaddingLogicalWidth(); auto containerWidthInInlineDirection = !hasPerpendicularContainingBlock ? containerLogicalWidth : perpendicularContainingBlockLogicalHeight(); // CSS Sizing 4 section 5.1: "A preferred aspect ratio only ever has an effect if at least one of the // box's sizes is automatic." Width is the ratio-dependent axis here; it must be auto for the // aspect ratio to determine it. (Height as ratio-dependent is handled by shouldComputeLogicalHeightFromAspectRatio.) if (style().logicalWidth().isAuto() && shouldComputeLogicalWidthFromAspectRatio()) return constrainLogicalWidthByMinMax(computeLogicalWidthFromAspectRatio(), containerWidthInInlineDirection, containingBlock, AllowIntrinsic::No); auto preferredWidth = computeLogicalWidthUsing(usedLogicalWidthLength, containerWidthInInlineDirection, containingBlock); return constrainLogicalWidthByMinMax(preferredWidth, containerWidthInInlineDirection, containingBlock); }; computedValues.extent = logicalWidth(); // Margin calculations. if (hasPerpendicularContainingBlock || isFloating() || isInline()) { computedValues.margins.start = computeOrTrimInlineMargin(containingBlock, Style::MarginTrimSide::BlockStart, [&] { return Style::evaluateMinimum(styleToUse.marginStart(), containerLogicalWidth, styleToUse.usedZoomForLength()); }); computedValues.margins.end = computeOrTrimInlineMargin(containingBlock, Style::MarginTrimSide::BlockEnd, [&] { return Style::evaluateMinimum(styleToUse.marginEnd(), containerLogicalWidth, styleToUse.usedZoomForLength()); }); } else { auto containerLogicalWidthForAutoMargins = containerLogicalWidth; if (avoidsFloats() && containingBlock.containsFloats()) containerLogicalWidthForAutoMargins = containingBlockAvailableLineWidth(); bool hasInvertedDirection = containingBlock.writingMode().isInlineOpposing(writingMode()); computeInlineDirectionMargins(containingBlock, containerLogicalWidth, containerLogicalWidthForAutoMargins, computedValues.extent, hasInvertedDirection ? computedValues.margins.end : computedValues.margins.start, hasInvertedDirection ? computedValues.margins.start : computedValues.margins.end); } auto shouldIgnoreOverconstrainedMargin = [&] { if (isGridItem() || isFlexItemIncludingDeprecated()) return true; // Is this replaced inline? if (isFloating() || isInline()) return true; #if ENABLE(MATHML) // RenderMathMLBlocks take the size of their content so we must not adjust the margin to fill the container size. if (containingBlock.isRenderMathMLBlock()) return true; #endif if (hasPerpendicularContainingBlock) return true; if (shouldMarginInlineEndContributeToScrollableOverflow(*this)) return true; return !containerLogicalWidth || containerLogicalWidth == (computedValues.extent + computedValues.margins.start + computedValues.margins.end); }; if (!shouldIgnoreOverconstrainedMargin()) { auto availableSpaceForMargin = containerLogicalWidth - computedValues.extent; bool hasInvertedDirection = containingBlock.writingMode().isInlineOpposing(writingMode()); if (hasInvertedDirection) computedValues.margins.start = availableSpaceForMargin - computedValues.margins.end; else computedValues.margins.end = availableSpaceForMargin - computedValues.margins.start; } } LayoutUnit RenderBox::fillAvailableMeasure(LayoutUnit availableLogicalWidth) const { LayoutUnit marginStart; LayoutUnit marginEnd; return fillAvailableMeasure(availableLogicalWidth, marginStart, marginEnd); } LayoutUnit RenderBox::fillAvailableMeasure(LayoutUnit availableLogicalWidth, LayoutUnit& marginStart, LayoutUnit& marginEnd) const { auto* container = containingBlock(); bool isOrthogonalElement = isHorizontalWritingMode() != container->isHorizontalWritingMode(); auto& marginStartLength = style().marginStart(); auto& marginEndLength = style().marginEnd(); LayoutUnit availableSizeForResolvingMargin = isOrthogonalElement ? containingBlockLogicalWidthForContent() : availableLogicalWidth; marginStart = computeOrTrimInlineMargin(*container, Style::MarginTrimSide::InlineStart, [&] { return Style::evaluateMinimum(marginStartLength, availableSizeForResolvingMargin, style().usedZoomForLength()); }); marginEnd = computeOrTrimInlineMargin(*container, Style::MarginTrimSide::InlineEnd, [&] { return Style::evaluateMinimum(marginEndLength, availableSizeForResolvingMargin, style().usedZoomForLength()); }); return availableLogicalWidth - marginStart - marginEnd; } template std::pair RenderBox::computeIntrinsicKeywordLogicalWidths(Keyword, LayoutUnit borderAndPadding) const { if constexpr (std::same_as) return computeIntrinsicKeywordLogicalWidths(); if (shouldComputeLogicalWidthFromAspectRatio()) { auto maxLogicalWidth = computeLogicalWidthFromAspectRatio() - borderAndPadding; auto minLogicalWidth = maxLogicalWidth; applyAutomaticContentBasedMinimumSize(minLogicalWidth, maxLogicalWidth); return { minLogicalWidth, maxLogicalWidth }; } if (CheckedPtr renderReplaced = dynamicDowncast(*this)) { // For replaced elements with an intrinsic aspect ratio (e.g. ) and a // specified block size, compute the transferred min/max-content inline size // through the intrinsic ratio rather than using the raw natural width. auto preferredRatio = renderReplaced->preferredAspectRatioAsSize().aspectRatioDouble(); if (preferredRatio && style().logicalHeight().isSpecified()) { auto computedValues = computeLogicalHeight(logicalHeight(), logicalTop()); auto contentBlockSize = std::max(0_lu, computedValues.extent - borderAndPaddingLogicalHeight()); auto maxLogicalWidth = LayoutUnit { contentBlockSize * preferredRatio }; auto minLogicalWidth = maxLogicalWidth; return { minLogicalWidth, maxLogicalWidth }; } } return computeIntrinsicKeywordLogicalWidths(); } static inline bool NODELETE isOrthogonal(const RenderBox& renderer, const RenderElement& ancestor) { return renderer.isHorizontalWritingMode() != ancestor.isHorizontalWritingMode(); } // CSS Sizing 4 §6.1: in block layout, if the parent has no block-start/end border or // padding and is not an independent formatting context, treat the element's margin on // that side as zero for the purpose of calculating the stretch size. static LayoutUnit marginForStretchInParentBlockAxis(const RenderBox& box, const RenderBlock& containingBlock, LayoutUnit marginOnParentBlockStart, LayoutUnit marginOnParentBlockEnd) { if (!box.isOutOfFlowPositioned() && is(containingBlock) && !containingBlock.childrenInline() && !containingBlock.createsNewFormattingContext()) { if (!containingBlock.borderBefore() && !containingBlock.paddingBefore()) marginOnParentBlockStart = 0_lu; if (!containingBlock.borderAfter() && !containingBlock.paddingAfter()) marginOnParentBlockEnd = 0_lu; } return marginOnParentBlockStart + marginOnParentBlockEnd; } LayoutUnit RenderBox::computeSizingKeywordLogicalWidthUsing(CSS::Keyword::Stretch, LayoutUnit availableLogicalWidth, LayoutUnit borderAndPadding) const { LayoutUnit marginStart; LayoutUnit marginEnd; auto logicalWidthResult = fillAvailableMeasure(availableLogicalWidth, marginStart, marginEnd); ASSERT(containingBlock()); CheckedRef containingBlock = *this->containingBlock(); // When the child is orthogonal, its inline axis maps to the parent's block axis. // Adjust margins per CSS Sizing 4 §6.1. if (isOrthogonal(*this, containingBlock)) { bool inlineMatchesBlock = writingMode().inlineDirection() == containingBlock->writingMode().blockDirection(); auto adjustedMargin = marginForStretchInParentBlockAxis(*this, containingBlock, inlineMatchesBlock ? marginStart : marginEnd, inlineMatchesBlock ? marginEnd : marginStart); logicalWidthResult += (marginStart + marginEnd) - adjustedMargin; } // Floats resolve stretch against the containing block (CSS Sizing 4, 6.1), not the space remaining after other floats. auto isNonFloatingBlockThatAvoidsFloats = !isFloating() && avoidsFloats(); if (isNonFloatingBlockThatAvoidsFloats && containingBlock->containsFloats()) logicalWidthResult = std::min(logicalWidthResult, shrinkLogicalWidthToAvoidFloats(marginStart, marginEnd, containingBlock)); return std::max(borderAndPadding, logicalWidthResult); } LayoutUnit RenderBox::computeSizingKeywordLogicalWidthUsing(CSS::Keyword::MaxContent keyword, LayoutUnit /*availableLogicalWidth*/, LayoutUnit borderAndPadding) const { auto [minLogicalWidth, maxLogicalWidth] = computeIntrinsicKeywordLogicalWidths(keyword, borderAndPadding); return maxLogicalWidth + borderAndPadding; } LayoutUnit RenderBox::computeSizingKeywordLogicalWidthUsing(CSS::Keyword::MinContent keyword, LayoutUnit /*availableLogicalWidth*/, LayoutUnit borderAndPadding) const { auto [minLogicalWidth, maxLogicalWidth] = computeIntrinsicKeywordLogicalWidths(keyword, borderAndPadding); return minLogicalWidth + borderAndPadding; } LayoutUnit RenderBox::computeSizingKeywordLogicalWidthUsing(CSS::Keyword::MinIntrinsic keyword, LayoutUnit /*availableLogicalWidth*/, LayoutUnit borderAndPadding) const { auto [minLogicalWidth, maxLogicalWidth] = computeIntrinsicKeywordLogicalWidths(keyword, borderAndPadding); return minLogicalWidth + borderAndPadding; } LayoutUnit RenderBox::computeSizingKeywordLogicalWidthUsing(CSS::Keyword::FitContent keyword, LayoutUnit availableLogicalWidth, LayoutUnit borderAndPadding) const { auto [minLogicalWidth, maxLogicalWidth] = computeIntrinsicKeywordLogicalWidths(keyword, borderAndPadding); return std::max(minLogicalWidth + borderAndPadding, std::min(maxLogicalWidth + borderAndPadding, fillAvailableMeasure(availableLogicalWidth))); } template LayoutUnit RenderBox::computeSizingKeywordLogicalWidthUsingGeneric(const SizeType& logicalWidth, LayoutUnit availableLogicalWidth, LayoutUnit borderAndPadding) const { if (logicalWidth.isStretch()) return computeSizingKeywordLogicalWidthUsing(CSS::Keyword::Stretch { }, availableLogicalWidth, borderAndPadding); if (logicalWidth.isMinIntrinsic()) return computeSizingKeywordLogicalWidthUsing(CSS::Keyword::MinContent { }, availableLogicalWidth, borderAndPadding); if (logicalWidth.isMaxContent()) return computeSizingKeywordLogicalWidthUsing(CSS::Keyword::MaxContent { }, availableLogicalWidth, borderAndPadding); if (logicalWidth.isMinContent()) return computeSizingKeywordLogicalWidthUsing(CSS::Keyword::MinContent { }, availableLogicalWidth, borderAndPadding); if (logicalWidth.isFitContent()) return computeSizingKeywordLogicalWidthUsing(CSS::Keyword::FitContent { }, availableLogicalWidth, borderAndPadding); ASSERT_NOT_REACHED(); return 0; } LayoutUnit RenderBox::computeSizingKeywordLogicalWidthUsing(const Style::PreferredSize& logicalWidth, LayoutUnit availableLogicalWidth, LayoutUnit borderAndPadding) const { return computeSizingKeywordLogicalWidthUsingGeneric(logicalWidth, availableLogicalWidth, borderAndPadding); } LayoutUnit RenderBox::computeSizingKeywordLogicalWidthUsing(const Style::MinimumSize& logicalWidth, LayoutUnit availableLogicalWidth, LayoutUnit borderAndPadding) const { return computeSizingKeywordLogicalWidthUsingGeneric(logicalWidth, availableLogicalWidth, borderAndPadding); } LayoutUnit RenderBox::computeSizingKeywordLogicalWidthUsing(const Style::MaximumSize& logicalWidth, LayoutUnit availableLogicalWidth, LayoutUnit borderAndPadding) const { return computeSizingKeywordLogicalWidthUsingGeneric(logicalWidth, availableLogicalWidth, borderAndPadding); } LayoutUnit RenderBox::computeSizingKeywordLogicalWidthUsing(const Style::FlexBasis& logicalWidth, LayoutUnit availableLogicalWidth, LayoutUnit borderAndPadding) const { return computeSizingKeywordLogicalWidthUsingGeneric(logicalWidth, availableLogicalWidth, borderAndPadding); } template LayoutUnit RenderBox::computeLogicalWidthUsingGeneric(const SizeType& logicalWidth, LayoutUnit availableLogicalWidth, const RenderBlock& containingBlock) const { if constexpr (std::same_as) { if (logicalWidth.isAuto()) return borderAndPaddingLogicalWidth(); } if (logicalWidth.isSpecified()) { // FIXME: If the containing block flow is perpendicular to our direction we need to use the available logical height instead. return adjustBorderBoxLogicalWidthForBoxSizing(Style::evaluate(logicalWidth, availableLogicalWidth, style().usedZoomForLength())); } if (logicalWidth.isIntrinsicOrStretch() || logicalWidth.isMinIntrinsic()) return computeSizingKeywordLogicalWidthUsing(logicalWidth, availableLogicalWidth, borderAndPaddingLogicalWidth()); LayoutUnit marginStart; LayoutUnit marginEnd; LayoutUnit logicalWidthResult = fillAvailableMeasure(availableLogicalWidth, marginStart, marginEnd); if (shrinkToAvoidFloats() && containingBlock.containsFloats()) logicalWidthResult = std::min(logicalWidthResult, shrinkLogicalWidthToAvoidFloats(marginStart, marginEnd, containingBlock)); if constexpr (std::same_as || std::same_as) { if (sizesLogicalWidthToFitContent()) return std::max(minContentLogicalWidthContribution(), std::min(maxContentLogicalWidthContribution(), logicalWidthResult)); } return logicalWidthResult; } LayoutUnit RenderBox::computeLogicalWidthUsing(const Style::PreferredSize& logicalWidth, LayoutUnit availableLogicalWidth, const RenderBlock& containingBlock) const { return computeLogicalWidthUsingGeneric(logicalWidth, availableLogicalWidth, containingBlock); } LayoutUnit RenderBox::computeLogicalWidthUsing(const Style::MinimumSize& logicalWidth, LayoutUnit availableLogicalWidth, const RenderBlock& containingBlock) const { return computeLogicalWidthUsingGeneric(logicalWidth, availableLogicalWidth, containingBlock); } LayoutUnit RenderBox::computeLogicalWidthUsing(const Style::MaximumSize& logicalWidth, LayoutUnit availableLogicalWidth, const RenderBlock& containingBlock) const { return computeLogicalWidthUsingGeneric(logicalWidth, availableLogicalWidth, containingBlock); } LayoutUnit RenderBox::computeLogicalWidthUsing(const Style::FlexBasis& logicalWidth, LayoutUnit availableLogicalWidth, const RenderBlock& containingBlock) const { return computeLogicalWidthUsingGeneric(logicalWidth, availableLogicalWidth, containingBlock); } bool RenderBox::isStretchingColumnFlexItem() const { if (parent()->isRenderDeprecatedFlexibleBox() && parent()->style().boxOrient() == BoxOrient::Vertical && parent()->style().boxAlign() == BoxAlignment::Stretch) return true; // We don't stretch multiline flexboxes because they need to apply line spacing (align-content) first. if (is(*parent()) && parent()->style().flexWrap() == FlexWrap::NoWrap && parent()->style().isColumnFlexDirection() && hasStretchedLogicalWidth()) return true; return false; } bool RenderBox::sizesLogicalWidthToFitContent() const { // Marquees in WinIE are like a mixture of blocks and inline-blocks. They size as though they're blocks, // but they allow text to sit on the same line as the marquee. if (isFloating() || (isNonReplacedAtomicInlineLevelBox() && !isHTMLMarquee())) return true; if (isGridItem()) return !hasStretchedLogicalWidth(); // This code may look a bit strange. Basically width:intrinsic should clamp the size when testing both // min-width and width. max-width is only clamped if it is also intrinsic. auto& style = this->style(); auto& logicalWidth = style.logicalWidth(); if (logicalWidth.isIntrinsicKeyword()) return true; // Children of a horizontal marquee do not fill the container by default. // FIXME: Need to deal with MarqueeDirection::Auto value properly. It could be vertical. // FIXME: Think about block-flow here. Need to find out how marquee direction relates to // block-flow (as well as how marquee overflow should relate to block flow). // https://bugs.webkit.org/show_bug.cgi?id=46472 if (parent()->isHTMLMarquee()) { auto dir = parent()->style().marqueeDirection(); if (dir == MarqueeDirection::Auto || dir == MarqueeDirection::Forward || dir == MarqueeDirection::Backward || dir == MarqueeDirection::Left || dir == MarqueeDirection::Right) return true; } #if ENABLE(MATHML) // RenderMathMLBlocks take the size of their content, not of their container. if (parent()->isRenderMathMLBlock()) return true; #endif // Flexible box items should shrink wrap, so we lay them out at their intrinsic widths. // In the case of columns that have a stretch alignment, we layout at the stretched size // to avoid an extra layout when applying alignment. if (is(*parent())) { // For multiline columns, we need to apply align-content first, so we can't stretch now. if (!parent()->style().isColumnFlexDirection() || parent()->style().flexWrap() != FlexWrap::NoWrap) return true; if (!hasStretchedLogicalWidth()) return true; } // Flexible horizontal boxes lay out children at their intrinsic widths. Also vertical boxes // that don't stretch their kids lay out their children at their intrinsic widths. // FIXME: Think about block-flow here. // https://bugs.webkit.org/show_bug.cgi?id=46473 if (parent()->isRenderDeprecatedFlexibleBox() && (parent()->style().boxOrient() == BoxOrient::Horizontal || parent()->style().boxAlign() != BoxAlignment::Stretch)) return true; // Button, input, select, textarea, and legend treat width value of 'auto' as 'intrinsic' unless it's in a // stretching column flexbox. // FIXME: Think about block-flow here. // https://bugs.webkit.org/show_bug.cgi?id=46473 if (logicalWidth.isAuto() && !isStretchingColumnFlexItem() && isAnyOf(element())) return true; if (isHorizontalWritingMode() != containingBlock()->isHorizontalWritingMode()) return true; if (isOutOfFlowPositioned() && logicalWidth.isAuto() && !shouldComputeLogicalWidthFromAspectRatio()) return style.logicalLeft().isAuto() || style.logicalRight().isAuto(); return false; } template LayoutUnit RenderBox::computeOrTrimInlineMargin(const RenderBlock& containingBlock, Style::MarginTrimSide marginSide, NOESCAPE const Function& computeInlineMargin) const { if (containingBlock.shouldTrimChildMargin(marginSide, *this)) { // FIXME(255434): This should be set when the margin is being trimmed // within the context of its layout system (block, flex, grid) and should not // be done at this level within RenderBox. We should be able to leave the // trimming responsibility to each of those contexts and not need to // do any of it here (trimming the margin and setting the rare data bit) if (isGridItem() && (marginSide == Style::MarginTrimSide::InlineStart || marginSide == Style::MarginTrimSide::InlineEnd)) const_cast(*this).markMarginAsTrimmed(marginSide); return 0_lu; } return computeInlineMargin(); } void RenderBox::computeInlineDirectionMargins(const RenderBlock& containingBlock, LayoutUnit containerWidth, std::optional availableSpaceAdjustedWithFloats, LayoutUnit childWidth, LayoutUnit& marginStart, LayoutUnit& marginEnd) const { auto& containingBlockStyle = containingBlock.style(); auto marginStartLength = style().marginStart(containingBlockStyle.writingMode()); auto marginEndLength = style().marginEnd(containingBlockStyle.writingMode()); const auto& zoomFactor = style().usedZoomForLength(); if (isFloating()) { marginStart = Style::evaluateMinimum(marginStartLength, containerWidth, zoomFactor); marginEnd = Style::evaluateMinimum(marginEndLength, containerWidth, zoomFactor); return; } if (isInline()) { // Inline blocks/tables don't have their margins increased. marginStart = computeOrTrimInlineMargin(containingBlock, Style::MarginTrimSide::InlineStart, [&] { return Style::evaluateMinimum(marginStartLength, containerWidth, zoomFactor); }); marginEnd = computeOrTrimInlineMargin(containingBlock, Style::MarginTrimSide::InlineEnd, [&] { return Style::evaluateMinimum(marginEndLength, containerWidth, zoomFactor); }); return; } if (is(containingBlock)) { // We need to let flexbox handle the margin adjustment - otherwise, flexbox // will think we're wider than we actually are and calculate line sizes // wrong. See also http://dev.w3.org/csswg/css-flexbox/#auto-margins if (marginStartLength.isAuto()) marginStartLength = 0_css_px; if (marginEndLength.isAuto()) marginEndLength = 0_css_px; } if (auto* grid = dynamicDowncast(containingBlock); grid && grid->isComputingTrackSizes()) { if (marginStartLength.isAuto()) marginStartLength = 0_css_px; if (marginEndLength.isAuto()) marginEndLength = 0_css_px; } auto handleMarginAuto = [&] { auto containerWidthForMarginAuto = availableSpaceAdjustedWithFloats.value_or(containerWidth); // Case One: The object is being centered in the containing block's available logical width. auto marginAutoCenter = marginStartLength.isAuto() && marginEndLength.isAuto() && childWidth < containerWidthForMarginAuto; auto alignModeCenter = containingBlock.style().textAlign() == Style::TextAlign::WebKitCenter && !marginStartLength.isAuto() && !marginEndLength.isAuto(); if (marginAutoCenter || alignModeCenter) { // Other browsers center the margin box for align=center elements so we match them here. marginStart = computeOrTrimInlineMargin(containingBlock, Style::MarginTrimSide::InlineStart, [&] { auto marginStartWidth = Style::evaluateMinimum(marginStartLength, containerWidthForMarginAuto, zoomFactor); auto marginEndWidth = Style::evaluateMinimum(marginEndLength, containerWidthForMarginAuto, zoomFactor); auto centeredMarginBoxStart = std::max(0, (containerWidthForMarginAuto - childWidth - marginStartWidth - marginEndWidth) / 2); return centeredMarginBoxStart + marginStartWidth; }); marginEnd = computeOrTrimInlineMargin(containingBlock, Style::MarginTrimSide::InlineEnd, [&] { auto marginEndWidth = Style::evaluateMinimum(marginEndLength, containerWidthForMarginAuto, zoomFactor); return containerWidthForMarginAuto - childWidth - marginStart + marginEndWidth; }); return true; } // Case Two: The object is being pushed to the start of the containing block's available logical width. if (marginEndLength.isAuto() && childWidth < containerWidthForMarginAuto) { marginStart = Style::evaluate(marginStartLength, containerWidthForMarginAuto, zoomFactor); marginEnd = containerWidthForMarginAuto - childWidth - marginStart; return true; } // Case Three: The object is being pushed to the end of the containing block's available logical width. auto pushToEndFromTextAlign = !marginEndLength.isAuto() && ((!containingBlockStyle.writingMode().isBidiLTR() && containingBlockStyle.textAlign() == Style::TextAlign::WebKitLeft) || (containingBlockStyle.writingMode().isBidiLTR() && containingBlockStyle.textAlign() == Style::TextAlign::WebKitRight)); if ((marginStartLength.isAuto() || pushToEndFromTextAlign) && childWidth < containerWidthForMarginAuto) { marginEnd = computeOrTrimInlineMargin(containingBlock, Style::MarginTrimSide::InlineEnd, [&] { return Style::evaluate(marginEndLength, containerWidthForMarginAuto, zoomFactor); }); marginStart = computeOrTrimInlineMargin(containingBlock, Style::MarginTrimSide::InlineStart, [&] { return containerWidthForMarginAuto - childWidth - marginEnd; }); return true; } return false; }; if (handleMarginAuto()) return; // Case Four: Either no auto margins, or our width is >= the container width (css2.1, 10.3.3). In that case // auto margins will just turn into 0. marginStart = computeOrTrimInlineMargin(containingBlock, Style::MarginTrimSide::InlineStart, [&] { return Style::evaluateMinimum(marginStartLength, containerWidth, zoomFactor); }); marginEnd = computeOrTrimInlineMargin(containingBlock, Style::MarginTrimSide::InlineEnd, [&] { return Style::evaluateMinimum(marginEndLength, containerWidth, zoomFactor); }); } RenderBoxFragmentInfo* RenderBox::renderBoxFragmentInfo(RenderFragmentContainer* fragment, RenderBoxFragmentInfoFlags cacheFlag) const { // Make sure nobody is trying to call this with a null fragment. if (!fragment) return nullptr; // If we have computed our width in this fragment already, it will be cached, and we can // just return it. RenderBoxFragmentInfo* boxInfo = fragment->renderBoxFragmentInfo(*this); if (boxInfo && cacheFlag == RenderBoxFragmentInfoFlags::CacheRenderBoxFragmentInfo) return boxInfo; return nullptr; } static bool NODELETE shouldFlipBeforeAfterMargins(WritingMode containingBlockWritingMode, WritingMode childWritingMode) { ASSERT(containingBlockWritingMode.isOrthogonal(childWritingMode)); auto childBlockFlowDirection = childWritingMode.blockDirection(); bool shouldFlip = false; switch (containingBlockWritingMode.blockDirection()) { case FlowDirection::TopToBottom: shouldFlip = (childBlockFlowDirection == FlowDirection::RightToLeft); break; case FlowDirection::BottomToTop: shouldFlip = (childBlockFlowDirection == FlowDirection::RightToLeft); break; case FlowDirection::RightToLeft: shouldFlip = (childBlockFlowDirection == FlowDirection::BottomToTop); break; case FlowDirection::LeftToRight: shouldFlip = (childBlockFlowDirection == FlowDirection::BottomToTop); break; } if (containingBlockWritingMode.isInlineFlipped()) shouldFlip = !shouldFlip; return shouldFlip; } void RenderBox::overrideLogicalHeightForSizeContainment() { LayoutUnit intrinsicHeight; if (auto height = explicitIntrinsicInnerLogicalHeight()) intrinsicHeight = height.value(); else if (isRenderMenuList()) { // RenderMenuList has its own theme, if there isn't explicitIntrinsicInnerLogicalHeight, // as a size containment, it should be treated as if there is no content, and the height // should the original logical height for theme. return; } // We need the exact width of border and padding here, yet we can't use borderAndPadding* interfaces. // Because these interfaces evetually call borderAfter/Before, and RenderBlock::borderBefore // adds extra border to fieldset by adding intrinsicBorderForFieldset which is not needed here. auto borderAndPadding = RenderBox::borderBefore() + RenderBox::paddingBefore() + RenderBox::borderAfter() + RenderBox::paddingAfter(); setLogicalHeight(intrinsicHeight + borderAndPadding + scrollbarLogicalHeight()); } void RenderBox::updateLogicalHeight() { if (shouldApplySizeContainment() && !isRenderGrid()) overrideLogicalHeightForSizeContainment(); if (CheckedPtr flexContainer = dynamicDowncast(parent())) flexContainer->setFlexItemContentLogicalHeightIfNeeded(*this, contentBoxLogicalHeight()); auto computedValues = computeLogicalHeight(logicalHeight(), logicalTop()); setLogicalHeight(computedValues.extent); setLogicalTop(computedValues.position); setMarginBefore(computedValues.margins.before); setMarginAfter(computedValues.margins.after); } RenderBox::LogicalExtentComputedValues RenderBox::computeLogicalHeight(LayoutUnit logicalHeight, LayoutUnit logicalTop) const { LogicalExtentComputedValues computedValues; computedValues.extent = logicalHeight; computedValues.position = logicalTop; // Inline non-replaced elements do not support a height property. if (isInline() && !isBlockLevelReplacedOrAtomicInline()) return computedValues; // Let's allow the table cell to compute its preferred size. if (auto* tableCell = dynamicDowncast(*this); tableCell && !tableCell->isComputingPreferredSize()) { // Use the value set by table layout for orthogonal cells which in this case the logical width of the cell from the table's point of view. // see RenderTableCell::setCellLogicalWidth. if (tableCell->isOrthogonal()) computedValues.extent = overridingBorderBoxLogicalHeight().value_or(computedValues.extent); return computedValues; } if (isOutOfFlowPositioned()) { computeOutOfFlowPositionedLogicalHeight(computedValues); return computedValues; } auto& parent = *this->parent(); // Context here refers to the general term and not necessarily a formatting context // since, for example, we might compute the logical height from the aspect ratio. auto usedLogicalHeightFromContext = [&] -> std::optional { if (is(*this)) { // Tables use the passed-in height (not the style height) even when they are flex/grid items. auto heightValue = logicalHeight; if (shouldComputeLogicalHeightFromAspectRatio()) heightValue = blockSizeFromAspectRatio(horizontalBorderAndPaddingExtent(), verticalBorderAndPaddingExtent(), style().logicalAspectRatio(), style().boxSizingForAspectRatio(), logicalWidth(), style().aspectRatio(), is(*this)); return heightValue; } if (is(parent)) { if (auto overridingLogicalHeight = overridingLogicalHeightForFlexBasisComputation()) { ASSERT(!this->overridingBorderBoxLogicalHeight()); return { }; } if (auto overridingLogicalHeight = this->overridingBorderBoxLogicalHeight()) return *overridingLogicalHeight; if (CheckedPtr replaced = dynamicDowncast(*this)) return replaced->computeReplacedLogicalHeight() + borderAndPaddingLogicalHeight(); return { }; } if (CheckedPtr deprecatedFlexBox = dynamicDowncast(parent)) { if (auto overridingLogicalHeight = this->overridingBorderBoxLogicalHeight()) return *overridingLogicalHeight; auto& flexBoxStyle = deprecatedFlexBox->style(); auto treatAsReplaced = [&] { if (!is(*this)) return false; bool inHorizontalBox = flexBoxStyle.boxOrient() == BoxOrient::Horizontal; bool stretching = flexBoxStyle.boxAlign() == BoxAlignment::Stretch; return !inHorizontalBox || !stretching; }; if (treatAsReplaced()) return downcast(*this).computeReplacedLogicalHeight() + borderAndPaddingLogicalHeight(); // Block children of horizontal flexible boxes fill the height of the box. if (style().logicalHeight().isAuto() && flexBoxStyle.boxOrient() == BoxOrient::Horizontal && deprecatedFlexBox->isStretchingChildren()) return deprecatedFlexBox->contentBoxLogicalHeight() - marginBefore() - marginAfter(); return { }; } if (is(parent)) { if (auto overridingLogicalHeight = this->overridingBorderBoxLogicalHeight()) return *overridingLogicalHeight; if (CheckedPtr replaced = dynamicDowncast(*this)) return replaced->computeReplacedLogicalHeight() + borderAndPaddingLogicalHeight(); return { }; } if (CheckedPtr replaced = dynamicDowncast(*this)) return replaced->computeReplacedLogicalHeight() + borderAndPaddingLogicalHeight(); return { }; }; auto computedLogicalHeight = [&] -> Style::PreferredSize { if (is(parent)) { if (auto overridingLogicalHeight = overridingLogicalHeightForFlexBasisComputation()) { ASSERT(!this->overridingBorderBoxLogicalHeight()); return *overridingLogicalHeight; } } return style().logicalHeight(); }(); auto usedLogicalHeight = [&] { if (auto heightFromFormattingContext = usedLogicalHeightFromContext()) return *heightFromFormattingContext; // Callers passing LayoutUnit::max() for logicalHeight means an indefinite height, so // translate this to a nullopt intrinsic height for further logical height computations. auto intrinsicHeight = logicalHeight != LayoutUnit::max() ? std::make_optional(logicalHeight) : std::nullopt; if (shouldComputeLogicalHeightFromAspectRatio()) { if (intrinsicHeight && style().boxSizing() == BoxSizing::ContentBox) *intrinsicHeight -= RenderBox::borderBefore() + RenderBox::paddingBefore() + RenderBox::borderAfter() + RenderBox::paddingAfter(); auto heightFromAspectRatio = blockSizeFromAspectRatio(horizontalBorderAndPaddingExtent(), verticalBorderAndPaddingExtent(), style().logicalAspectRatio(), style().boxSizingForAspectRatio(), logicalWidth(), style().aspectRatio(), is(*this)); return constrainLogicalHeightByMinMax(heightFromAspectRatio, intrinsicHeight); } if (intrinsicHeight) *intrinsicHeight -= borderAndPaddingLogicalHeight(); auto mainOrPreferredHeight = computeLogicalHeightUsing(computedLogicalHeight, intrinsicHeight).value_or(computedValues.extent); return constrainLogicalHeightByMinMax(mainOrPreferredHeight, intrinsicHeight); }; computedValues.extent = usedLogicalHeight(); auto computeMargins = [&] { auto& containingBlock = *this->containingBlock(); bool hasPerpendicularContainingBlock = containingBlock.isHorizontalWritingMode() != isHorizontalWritingMode(); bool shouldFlipBeforeAfter = hasPerpendicularContainingBlock ? shouldFlipBeforeAfterMargins(containingBlock.writingMode(), writingMode()) : containingBlock.writingMode().isBlockOpposing(writingMode()); auto marginBefore = shouldFlipBeforeAfter ? computedValues.margins.after : computedValues.margins.before; auto marginAfter = shouldFlipBeforeAfter ? computedValues.margins.before : computedValues.margins.after; hasPerpendicularContainingBlock ? computeInlineDirectionMargins(containingBlock, containingBlockLogicalWidthForContent(), { }, computedValues.extent, marginBefore, marginAfter) : computeBlockDirectionMargins(containingBlock, marginBefore, marginAfter); computedValues.margins.before = shouldFlipBeforeAfter ? marginAfter : marginBefore; computedValues.margins.after = shouldFlipBeforeAfter ? marginBefore : marginAfter; }; computeMargins(); // WinIE quirk: The block always fills the entire canvas in quirks mode. The always fills the // block in quirks mode. Only apply this quirk if the block is normal flow and no height // is specified. When we're printing, we also need this quirk if the body or root has a percentage // height since we don't set a height in RenderView when we're printing. So without this quirk, the // height has nothing to be a percentage of, and it ends up being 0. That is bad. auto paginatedContentNeedsBaseHeight = [&] { if (!document().printing() || !computedLogicalHeight.isPercentOrCalculated() || isInline()) return false; if (isDocumentElementRenderer()) return true; auto* documentElementRenderer = document().documentElement()->renderer(); return isBody() && &parent == documentElementRenderer && documentElementRenderer->style().logicalHeight().isPercentOrCalculated(); }; if (stretchesToViewport() || paginatedContentNeedsBaseHeight()) { auto margins = collapsedMarginBefore() + collapsedMarginAfter(); auto visibleHeight = view().pageOrViewLogicalHeight(); if (isDocumentElementRenderer()) computedValues.extent = std::max(computedValues.extent, visibleHeight - margins); else if (CheckedPtr parentBox = dynamicDowncast(parent)) { auto marginsBordersPadding = margins + parentBox->marginBefore() + parentBox->marginAfter() + parentBox->borderAndPaddingLogicalHeight(); computedValues.extent = std::max(computedValues.extent, visibleHeight - marginsBordersPadding); } } return computedValues; } LayoutUnit RenderBox::computeLogicalHeightForIntrinsicWidthContribution() const { // FIXME:: We should probably return something other than just // border + padding, but for now we have no good way to do anything else // without layout, so we just use that. auto estimatedHeight = borderAndPaddingLogicalHeight(); if (shouldApplySizeContainment()) { if (auto height = explicitIntrinsicInnerLogicalHeight()) estimatedHeight += height.value() + scrollbarLogicalHeight(); } auto intrinsicHeight = std::make_optional(estimatedHeight - borderAndPaddingLogicalHeight()); auto logicalHeight = computeLogicalHeightUsing(style().logicalHeight(), intrinsicHeight).value_or(estimatedHeight); return constrainLogicalHeightByMinMax(logicalHeight, intrinsicHeight, IsComputingIntrinsicSize::Yes); } template std::optional RenderBox::computeLogicalHeightUsingGeneric(const SizeType& logicalHeight, std::optional intrinsicContentHeight) const { if (auto* replaced = dynamicDowncast(*this)) { if constexpr (std::same_as) { if (!replaced->replacedMinLogicalHeightComputesAsNone()) return replaced->computeReplacedLogicalHeightUsing(logicalHeight) + borderAndPaddingLogicalHeight(); } else if constexpr (std::same_as) { if (!replaced->replacedMaxLogicalHeightComputesAsNone()) return replaced->computeReplacedLogicalHeightUsing(logicalHeight) + borderAndPaddingLogicalHeight(); } return { }; } if (auto computedContentAndScrollbarLogicalHeight = computeContentAndScrollbarLogicalHeightUsing(logicalHeight, intrinsicContentHeight)) return adjustBorderBoxLogicalHeightForBoxSizing(*computedContentAndScrollbarLogicalHeight); return { }; } std::optional RenderBox::computeLogicalHeightUsing(const Style::PreferredSize& logicalHeight, std::optional intrinsicContentHeight) const { return computeLogicalHeightUsingGeneric(logicalHeight, intrinsicContentHeight); } std::optional RenderBox::computeLogicalHeightUsing(const Style::MinimumSize& logicalHeight, std::optional intrinsicContentHeight) const { return computeLogicalHeightUsingGeneric(logicalHeight, intrinsicContentHeight); } std::optional RenderBox::computeLogicalHeightUsing(const Style::MaximumSize& logicalHeight, std::optional intrinsicContentHeight) const { return computeLogicalHeightUsingGeneric(logicalHeight, intrinsicContentHeight); } template std::optional RenderBox::computeContentLogicalHeightGeneric(const SizeType& logicalHeight, std::optional intrinsicContentHeight) const { if (auto computedContentAndScrollbarLogicalHeight = computeContentAndScrollbarLogicalHeightUsing(logicalHeight, intrinsicContentHeight)) return std::max(0, adjustContentBoxLogicalHeightForBoxSizing(*computedContentAndScrollbarLogicalHeight) - scrollbarLogicalHeight()); return std::nullopt; } std::optional RenderBox::computeContentLogicalHeight(const Style::PreferredSize& logicalHeight, std::optional intrinsicContentHeight) const { return computeContentLogicalHeightGeneric(logicalHeight, intrinsicContentHeight); } std::optional RenderBox::computeContentLogicalHeight(const Style::MinimumSize& logicalHeight, std::optional intrinsicContentHeight) const { return computeContentLogicalHeightGeneric(logicalHeight, intrinsicContentHeight); } std::optional RenderBox::computeContentLogicalHeight(const Style::MaximumSize& logicalHeight, std::optional intrinsicContentHeight) const { return computeContentLogicalHeightGeneric(logicalHeight, intrinsicContentHeight); } std::optional RenderBox::computeContentLogicalHeight(const Style::FlexBasis& logicalHeight, std::optional intrinsicContentHeight) const { return computeContentLogicalHeightGeneric(logicalHeight, intrinsicContentHeight); } LayoutUnit RenderBox::blockAxisMarginForStretch() const { ASSERT(containingBlock()); CheckedRef containingBlock = *this->containingBlock(); auto availableSpace = containingBlockLogicalWidthForContent(); auto marginLogicalBefore = Style::evaluateMinimum( style().marginBefore(), availableSpace, style().usedZoomForLength()); auto marginLogicalAfter = Style::evaluateMinimum( style().marginAfter(), availableSpace, style().usedZoomForLength()); // When the child is non-orthogonal, its block axis maps to the parent's block axis. // Adjust margins per CSS Sizing 4 §6.1. if (!isOrthogonal(*this, containingBlock)) { bool blockDirectionsMatch = writingMode().blockDirection() == containingBlock->writingMode().blockDirection(); return marginForStretchInParentBlockAxis(*this, containingBlock, blockDirectionsMatch ? marginLogicalBefore : marginLogicalAfter, blockDirectionsMatch ? marginLogicalAfter : marginLogicalBefore); } return marginLogicalBefore + marginLogicalAfter; } template std::optional RenderBox::computeSizingKeywordLogicalContentHeightUsingGeneric(const SizeType& logicalHeight, std::optional intrinsicContentHeight, LayoutUnit borderAndPadding) const { auto intrinsic = [&] -> std::optional { if (intrinsicContentHeight) return adjustIntrinsicLogicalHeightForBoxSizing(*intrinsicContentHeight); return { }; }; auto minMaxContent = [&] -> std::optional { // FIXME: The CSS sizing spec is considering changing what min-content/max-content should resolve to. // If that happens, this code will have to change. if (CheckedPtr renderImage = dynamicDowncast(this)) { auto computedFixedLogicalWidth = style().logicalWidth().tryFixed(); auto preferredRatio = renderImage->preferredAspectRatioAsSize(); if (computedFixedLogicalWidth && !style().aspectRatio().hasRatio()) { return resolveHeightForRatio( borderAndPaddingLogicalWidth(), borderAndPaddingLogicalHeight(), LayoutUnit { computedFixedLogicalWidth->resolveZoom(style().usedZoomForLength()) }, preferredRatio.transposedSize().aspectRatio(), BoxSizing::ContentBox ); } auto heightFromCrossAxisOverrideAndAspectRatio = [&]() -> std::optional { // When the width comes from a flex cross-axis override (e.g. stretch in a // column flex container), use it to compute the min-content height through // the aspect ratio. if (!isFlexItem() || downcast(parent())->isHorizontalFlow()) return { }; if (auto overridingWidth = overridingBorderBoxLogicalWidth(); overridingWidth && !preferredRatio.isEmpty()) return resolveHeightForRatio(borderAndPaddingLogicalWidth(), borderAndPaddingLogicalHeight(), contentBoxLogicalWidth(*overridingWidth), preferredRatio.transposedSize().aspectRatio(), BoxSizing::ContentBox); return { }; }; if (auto height = heightFromCrossAxisOverrideAndAspectRatio()) return height; } return intrinsic(); }; return WTF::switchOn(logicalHeight, [&](const CSS::Keyword::MinContent&) -> std::optional { return minMaxContent(); }, [&](const CSS::Keyword::MaxContent&) -> std::optional { return minMaxContent(); }, [&](const CSS::Keyword::Intrinsic&) -> std::optional { return intrinsic(); }, [&](const CSS::Keyword::MinIntrinsic&) -> std::optional { return intrinsic(); }, [&](const CSS::Keyword::FitContent&) -> std::optional { return intrinsic(); }, [&](const CSS::Keyword::Stretch&) -> std::optional { ASSERT(containingBlock()); CheckedRef containingBlock = *this->containingBlock(); auto availableSpace = [&]() -> std::optional { if (isGridItem()) { if (isOrthogonal(*this, containingBlock)) return containingBlockLogicalWidthForContent(); // gridAreaContentLogicalHeight() is optional>: // - empty outer: not set at all (shouldn't happen for grid items) // - outer set, inner nullopt: grid area height not yet resolved (auto tracks during intrinsic sizing) // - outer set, inner has value: grid area height is definite // When the grid area is not yet resolved, stretch doesn't resolve. auto gridAreaSize = gridAreaContentLogicalHeight(); if (!gridAreaSize || !*gridAreaSize) return { }; return gridAreaSize->value(); } if (!isOrthogonal(*this, containingBlock)) view().addPercentHeightDescendant(const_cast(*this)); if (!isBlockSizeResolvableForStretch()) return { }; if (isOrthogonal(*this, containingBlock)) return containingBlockLogicalWidthForContent(); return containingBlock->availableLogicalHeight(AvailableLogicalHeightType::ExcludeMarginBorderPadding); }(); if (!availableSpace) return { }; return std::max(0_lu, *availableSpace - borderAndPadding - blockAxisMarginForStretch()); }, [&](const CSS::Keyword::WebkitFillAvailable&) -> std::optional { // Legacy -webkit-fill-available preserves pre-stretch-keyword behavior: // resolve through the containing block chain unconditionally, without the // stretch keyword's "indefinite containing block falls back to initial value" rule. return containingBlock()->availableLogicalHeight(AvailableLogicalHeightType::ExcludeMarginBorderPadding) - borderAndPadding; }, [&](const auto&) -> std::optional { ASSERT_NOT_REACHED(); return 0_lu; } ); } std::optional RenderBox::computeSizingKeywordLogicalContentHeightUsing(const Style::PreferredSize& logicalHeight, std::optional intrinsicContentHeight, LayoutUnit borderAndPadding) const { return computeSizingKeywordLogicalContentHeightUsingGeneric(logicalHeight, intrinsicContentHeight, borderAndPadding); } std::optional RenderBox::computeSizingKeywordLogicalContentHeightUsing(const Style::MinimumSize& logicalHeight, std::optional intrinsicContentHeight, LayoutUnit borderAndPadding) const { return computeSizingKeywordLogicalContentHeightUsingGeneric(logicalHeight, intrinsicContentHeight, borderAndPadding); } std::optional RenderBox::computeSizingKeywordLogicalContentHeightUsing(const Style::MaximumSize& logicalHeight, std::optional intrinsicContentHeight, LayoutUnit borderAndPadding) const { return computeSizingKeywordLogicalContentHeightUsingGeneric(logicalHeight, intrinsicContentHeight, borderAndPadding); } std::optional RenderBox::computeSizingKeywordLogicalContentHeightUsing(const Style::FlexBasis& logicalHeight, std::optional intrinsicContentHeight, LayoutUnit borderAndPadding) const { return computeSizingKeywordLogicalContentHeightUsingGeneric(logicalHeight, intrinsicContentHeight, borderAndPadding); } template std::optional RenderBox::computeContentAndScrollbarLogicalHeightUsing(const SizeType& logicalHeight, std::optional intrinsicContentHeight) const { auto keywordSize = [&] { // FIXME: The CSS sizing spec is considering changing what min-content/max-content should resolve to. // If that happens, this code will have to change. return computeSizingKeywordLogicalContentHeightUsing(logicalHeight, intrinsicContentHeight, borderAndPaddingLogicalHeight()); }; return WTF::switchOn(logicalHeight, [&](const typename SizeType::Fixed& fixedLogicalHeight) -> std::optional { return LayoutUnit { fixedLogicalHeight.resolveZoom(style().usedZoomForLength()) }; }, [&](const typename SizeType::Percentage&) -> std::optional { return computePercentageLogicalHeight(logicalHeight); }, [&](const typename SizeType::Calc&) -> std::optional { return computePercentageLogicalHeight(logicalHeight); }, [&](const CSS::Keyword::MinContent&) -> std::optional { return keywordSize(); }, [&](const CSS::Keyword::MaxContent&) -> std::optional { return keywordSize(); }, [&](const CSS::Keyword::FitContent&) -> std::optional { return keywordSize(); }, [&](const CSS::Keyword::Stretch&) -> std::optional { auto result = keywordSize(); if (result) return adjustIntrinsicLogicalHeightForBoxSizing(*result); return result; }, [&](const CSS::Keyword::WebkitFillAvailable&) -> std::optional { auto result = keywordSize(); if (result) return adjustIntrinsicLogicalHeightForBoxSizing(*result); return result; }, [&](const CSS::Keyword::Intrinsic&) -> std::optional { return keywordSize(); }, [&](const CSS::Keyword::MinIntrinsic&) -> std::optional { return keywordSize(); }, [&](const CSS::Keyword::Auto&) -> std::optional { if constexpr (std::same_as) { if (intrinsicContentHeight && isFlexItem() && downcast(parent())->useContentBasedMinimumBlockSize(*this)) return adjustIntrinsicLogicalHeightForBoxSizing(*intrinsicContentHeight); return LayoutUnit { 0 }; } else return { }; }, [&](const auto&) -> std::optional { return { }; } ); } bool RenderBox::skipContainingBlockForPercentHeightCalculation(const RenderBox& containingBlock, bool isPerpendicularWritingMode) const { // Flow threads for multicol or paged overflow should be skipped. They are invisible to the DOM, // and percent heights of children should be resolved against the multicol or paged container. if (containingBlock.isRenderFragmentedFlow() && !isPerpendicularWritingMode) return true; // If the writing mode of the containing block is orthogonal to ours, it means // that we shouldn't skip anything, since we're going to resolve the // percentage height against a containing block *width*. if (isPerpendicularWritingMode) return false; // Anonymous blocks should not impede percentage resolution on a child. // Examples of such anonymous blocks are blocks wrapped around inlines that // have block siblings (from the CSS spec) and multicol flow threads (an // implementation detail). Another implementation detail, ruby runs, create // anonymous inline-blocks, so skip those too. All other types of anonymous // objects, such as table-cells and flexboxes, will be treated as if they were // non-anonymous. if (containingBlock.shouldSkipForPercentageResolution()) return containingBlock.style().display() == Style::DisplayType::BlockFlow || containingBlock.style().display() == Style::DisplayType::InlineFlowRoot; // For quirks mode, we skip most auto-height containing blocks when computing percentages. auto shouldSkipContainingBlockInQuirksMode = [&] { ASSERT(document().inQuirksMode()); if (containingBlock.isFlexItem() && downcast(containingBlock.parent())->canUseFlexItemForPercentageResolution(containingBlock)) return false; if (containingBlock.isRenderTableCell()) return false; if (containingBlock.isOutOfFlowPositioned()) return false; if (containingBlock.isRenderGrid()) return false; if (containingBlock.isFlexibleBoxIncludingDeprecated()) return false; if (is(containingBlock)) return false; return containingBlock.style().logicalHeight().isAuto(); }; return document().inQuirksMode() && shouldSkipContainingBlockInQuirksMode(); } static bool NODELETE tableCellShouldHaveZeroInitialSize(const RenderTableCell& tableCell, const RenderBox& child, bool scrollsOverflowY) { // Normally we would let the cell size intrinsically, but scrolling overflow has to be // treated differently, since WinIE lets scrolled overflow fragments shrink as needed. // While we can't get all cases right, we can at least detect when the cell has a specified // height or when the table has a specified height. In these cases we want to initially have // no size and allow the flexing of the table or the cell to its specified height to cause us // to grow to fill the space. This could end up being wrong in some cases, but it is // preferable to the alternative (sizing intrinsically and making the row end up too big). if (!scrollsOverflowY) return false; if (tableCell.table()->style().logicalHeight().isAuto() && (tableCell.style().logicalHeight().isAuto() || tableCell.style().logicalHeight().isPercentOrCalculated())) return false; if (child.isBlockLevelReplacedOrAtomicInline()) return false; if (is(child.element()) && !is(child.element())) return false; return true; } template std::optional RenderBox::computePercentageLogicalHeightGeneric(const SizeType& logicalHeight, UpdatePercentageHeightDescendants updateDescendants) const { bool skippedAutoHeightContainingBlock = false; auto* containingBlock = this->containingBlock(); const RenderBox* containingBlockChild = this; LayoutUnit rootMarginBorderPaddingHeight; bool isHorizontal = isHorizontalWritingMode(); while (containingBlock && !is(*containingBlock) && skipContainingBlockForPercentHeightCalculation(*containingBlock, isHorizontal != containingBlock->isHorizontalWritingMode())) { if (containingBlock->isBody() || containingBlock->isDocumentElementRenderer()) rootMarginBorderPaddingHeight += containingBlock->marginBefore() + containingBlock->marginAfter() + containingBlock->borderAndPaddingLogicalHeight(); skippedAutoHeightContainingBlock = true; containingBlockChild = containingBlock; containingBlock = containingBlock->containingBlock(); } if (updateDescendants == UpdatePercentageHeightDescendants::Yes) containingBlock->addPercentHeightDescendant(const_cast(*this)); if (is(containingBlock) && view().frameView().isAutoSizeEnabled()) { // Dynamic height units like percentage don't play well with autosizing when we don't have a definite viewport size. Let's treat percentage as auto instead. return { }; } if (isFlexItem() && view().frameView().layoutContext().isPercentHeightResolveDisabledFor(*this)) return { }; auto isOrthogonal = isHorizontal != containingBlock->isHorizontalWritingMode(); auto overridingAvailableSize = std::optional { }; if (isGridItem()) { if (auto gridAreaSize = isOrthogonal ? gridAreaContentLogicalWidth() : gridAreaContentLogicalHeight()) { if (!*gridAreaSize) return { }; overridingAvailableSize = *gridAreaSize; } } if (CheckedPtr tableCell = dynamicDowncast(*containingBlock); tableCell && !isOrthogonal) { if (skippedAutoHeightContainingBlock) return { }; // Table cells violate what the CSS spec says to do with heights. Basically we // don't care if the cell specified a height or not. We just always make ourselves // be a percentage of the cell's current content height. // However, when the cell has an unresolvable percentage height (percentage // height on the cell with an auto-height table), we should not use the // overriding height from row layout to resolve children's percentage heights. bool cellHasUnresolvablePercentageHeight = tableCell->style().logicalHeight().isPercentOrCalculated() && tableCell->table()->style().logicalHeight().isAuto(); auto tableCellLogicalHeight = cellHasUnresolvablePercentageHeight ? std::nullopt : tableCell->overridingBorderBoxLogicalHeight(); if (!tableCellLogicalHeight) return tableCellShouldHaveZeroInitialSize(*tableCell, *this, scrollsOverflowY()) ? std::make_optional(0_lu) : std::nullopt; // Note: can't use contentBoxLogicalHeight here on table cells due to intrinsic padding. overridingAvailableSize = *tableCellLogicalHeight - tableCell->computedCSSPaddingBefore() - tableCell->computedCSSPaddingAfter() - tableCell->borderLogicalHeight() - tableCell->scrollbarLogicalHeight(); } auto availableHeight = !overridingAvailableSize ? (!isOrthogonal ? containingBlock->availableLogicalHeightForPercentageComputation() : containingBlockChild->containingBlockLogicalWidthForContent()) : overridingAvailableSize; auto availableHeightForQuirksPercentageResolution = [&]() -> std::optional { // Quirks spec §3.5 step 4: the walk stops at flex containers because they // are not block containers. The flex container may have auto height, making // availableLogicalHeightForPercentageComputation return nullopt, but during // cross-axis stretch layout its used content height is known. if (availableHeight || !skippedAutoHeightContainingBlock) return availableHeight; CheckedPtr flexContainer = dynamicDowncast(*containingBlock); if (!flexContainer || !flexContainer->isInCrossAxisStretchLayout()) return { }; return containingBlock->contentBoxLogicalHeight(); }; availableHeight = availableHeightForQuirksPercentageResolution(); if (!availableHeight) return { }; auto result = [&] { if constexpr (Style::IsPercentage) return Style::evaluate(logicalHeight, *availableHeight - rootMarginBorderPaddingHeight + (isRenderTable() && isOutOfFlowPositioned() ? containingBlock->paddingBefore() + containingBlock->paddingAfter() : 0_lu)); else return Style::evaluate(logicalHeight, *availableHeight - rootMarginBorderPaddingHeight + (isRenderTable() && isOutOfFlowPositioned() ? containingBlock->paddingBefore() + containingBlock->paddingAfter() : 0_lu), style().usedZoomForLength()); }(); // |overridingLogicalHeight| is the maximum height made available by the // cell to its percent height children when we decide they can determine the // height of the cell. If the percent height child is box-sizing:content-box // then we must subtract the border and padding from the cell's // |availableHeight| (given by |overridingLogicalHeight|) to arrive // at the child's computed height. bool subtractBorderAndPadding = isRenderTable() || (is(*containingBlock) && !skippedAutoHeightContainingBlock && containingBlock->overridingBorderBoxLogicalHeight() && style().boxSizing() == BoxSizing::ContentBox); if (subtractBorderAndPadding) { result -= borderAndPaddingLogicalHeight(); return std::max(0_lu, result); } return result; } std::optional RenderBox::computePercentageLogicalHeight(const Style::PreferredSize& logicalHeight, UpdatePercentageHeightDescendants updateDescendants) const { if (auto result = computePercentageLogicalHeightGeneric(logicalHeight, updateDescendants)) return result; if (style().hasUsedAppearance()) { // CSS Sizing 3: an unresolvable percentage behaves as auto. // CSS UI 4: widgets are laid out like replaced elements, so auto means intrinsic size. if (auto intrinsicHeight = intrinsicLogicalHeight()) return intrinsicHeight; } return { }; } std::optional RenderBox::computePercentageLogicalHeight(const Style::MinimumSize& logicalHeight, UpdatePercentageHeightDescendants updateDescendants) const { return computePercentageLogicalHeightGeneric(logicalHeight, updateDescendants); } std::optional RenderBox::computePercentageLogicalHeight(const Style::MaximumSize& logicalHeight, UpdatePercentageHeightDescendants updateDescendants) const { return computePercentageLogicalHeightGeneric(logicalHeight, updateDescendants); } std::optional RenderBox::computePercentageLogicalHeight(const Style::FlexBasis& logicalHeight, UpdatePercentageHeightDescendants updateDescendants) const { return computePercentageLogicalHeightGeneric(logicalHeight, updateDescendants); } std::optional RenderBox::computePercentageLogicalHeight(const Style::Percentage& logicalHeight, UpdatePercentageHeightDescendants updateDescendants) const { return computePercentageLogicalHeightGeneric(logicalHeight, updateDescendants); } std::optional RenderBox::computePercentageLogicalHeight(const Style::UnevaluatedCalculation>& logicalHeight, UpdatePercentageHeightDescendants updateDescendants) const { return computePercentageLogicalHeightGeneric(logicalHeight, updateDescendants); } void RenderBox::computeIntrinsicLogicalWidthContributions() { ASSERT(hasInvalidContentLogicalWidths()); constrainIntrinsicLogicalWidthsByMinMax(m_minContentLogicalWidthContribution, m_maxContentLogicalWidthContribution); clearContentLogicalWidthsInvalidation(); } void RenderBox::constrainIntrinsicLogicalWidthsByMinMax(LayoutUnit& minIntrinsicLogicalWidth, LayoutUnit& maxIntrinsicLogicalWidth) const { auto& minLogicalWidth = style().logicalMinWidth(); auto& maxLogicalWidth = style().logicalMaxWidth(); auto usedMaxLogicalWidth = [&] { // FIXME: We should be able to handle other values for the max logical width here. if (auto fixedMaxLogicalWidth = maxLogicalWidth.tryFixed()) return adjustContentBoxLogicalWidthForBoxSizing(*fixedMaxLogicalWidth); if (maxLogicalWidth.isMinContent()) { // max-width: min-content normally resolves to the content-based min-content size, // but a box with its own fixed inline width derives the size from that width instead. // (A zero-width deprecated flex item still flexes, so its 0 is not a usable fixed width.) auto fixedLogicalWidth = overridingLogicalWidthForFlexBasisComputation().value_or(style().logicalWidth()).tryFixed(); bool hasFixedLogicalWidth = fixedLogicalWidth && fixedLogicalWidth->isPositiveOrZero() && !(isDeprecatedFlexItem() && !static_cast(fixedLogicalWidth->resolveZoom(style().usedZoomForLength()))); if (!hasFixedLogicalWidth) return minIntrinsicLogicalWidth; return computeSizingKeywordLogicalWidthUsing(maxLogicalWidth, contentBoxLogicalWidth(), { }); } return LayoutUnit::max(); }(); auto usedMinLogicalWidth = [&]() -> LayoutUnit { // FIXME: We should be able to handle other values for the min logical width here. if (auto fixedMinLogicalWidth = minLogicalWidth.tryFixed(); fixedMinLogicalWidth && fixedMinLogicalWidth->isPositive()) return adjustContentBoxLogicalWidthForBoxSizing(*fixedMinLogicalWidth); if (minLogicalWidth.isMaxContent()) return maxIntrinsicLogicalWidth; // A min-content minimum floors the contribution at the box's own min-content size, // so a smaller max-width cannot clamp it below that (min wins over max). if (minLogicalWidth.isMinContent()) return minIntrinsicLogicalWidth; return { }; }(); if (!style().logicalWidth().isFixed() && shouldComputeLogicalHeightFromAspectRatio()) applyTransferredMinMaxSizesFromAspectRatio(minIntrinsicLogicalWidth, maxIntrinsicLogicalWidth); maxIntrinsicLogicalWidth = std::min(maxIntrinsicLogicalWidth, usedMaxLogicalWidth); minIntrinsicLogicalWidth = std::min(minIntrinsicLogicalWidth, usedMaxLogicalWidth); maxIntrinsicLogicalWidth = std::max(maxIntrinsicLogicalWidth, usedMinLogicalWidth); minIntrinsicLogicalWidth = std::max(minIntrinsicLogicalWidth, usedMinLogicalWidth); auto borderAndPadding = borderAndPaddingLogicalWidth(); minIntrinsicLogicalWidth += borderAndPadding; maxIntrinsicLogicalWidth += borderAndPadding; } LayoutUnit RenderBox::availableLogicalHeight(AvailableLogicalHeightType heightType) const { return constrainContentBoxLogicalHeightByMinMax(availableLogicalHeightUsing(style().logicalHeight(), heightType), std::nullopt); } LayoutUnit RenderBox::availableLogicalHeightUsing(const Style::PreferredSize& logicalHeight, AvailableLogicalHeightType heightType) const { // We need to stop here, since we don't want to increase the height of the table // artificially. We're going to rely on this cell getting expanded to some new // height, and then when we lay out again we'll use the calculation below. if (isRenderTableCell() && (logicalHeight.isAuto() || logicalHeight.isPercentOrCalculated())) { if (auto overridingLogicalHeight = this->overridingBorderBoxLogicalHeight()) return *overridingLogicalHeight - computedCSSPaddingBefore() - computedCSSPaddingAfter() - borderBefore() - borderAfter() - scrollbarLogicalHeight(); return this->logicalHeight() - borderAndPaddingLogicalHeight(); } if (auto usedFlexItemOverridingLogicalHeightForPercentageResolutionForFlex = (isFlexItem() ? downcast(*parent()).usedFlexItemOverridingLogicalHeightForPercentageResolution(*this) : std::nullopt)) return contentBoxLogicalHeight(*usedFlexItemOverridingLogicalHeightForPercentageResolutionForFlex); if (shouldComputeLogicalHeightFromAspectRatio()) { auto borderAndPaddingLogicalHeight = this->borderAndPaddingLogicalHeight(); auto borderBoxLogicalHeight = blockSizeFromAspectRatio(borderAndPaddingLogicalWidth(), borderAndPaddingLogicalHeight, style().logicalAspectRatio(), style().boxSizingForAspectRatio(), logicalWidth(), style().aspectRatio(), isRenderReplaced()); if (heightType == AvailableLogicalHeightType::ExcludeMarginBorderPadding) return borderBoxLogicalHeight - borderAndPaddingLogicalHeight; return borderBoxLogicalHeight; } if (logicalHeight.isPercentOrCalculated() && isOutOfFlowPositioned() && !isRenderFragmentedFlow()) { PositionedLayoutConstraints constraints(*this, LogicalBoxAxis::Block); return adjustContentBoxLogicalHeightForBoxSizing(Style::evaluate(logicalHeight, constraints.containingSize(), style().usedZoomForLength())); } if (auto computedContentAndScrollbarLogicalHeight = computeContentAndScrollbarLogicalHeightUsing(logicalHeight, std::nullopt)) return std::max(0, adjustContentBoxLogicalHeightForBoxSizing(computedContentAndScrollbarLogicalHeight) - scrollbarLogicalHeight()); // Height of absolutely positioned, non-replaced elements section 5.3 rule 5 // https://www.w3.org/TR/css-position-3/#abs-non-replaced-height if (CheckedPtr block = dynamicDowncast(*this); block && isOutOfFlowPositioned() && style().logicalHeight().isAuto() && !(style().logicalTop().isAuto() || style().logicalBottom().isAuto())) { auto computedValues = block->computeLogicalHeight(block->logicalHeight(), 0); return computedValues.extent - block->borderAndPaddingLogicalHeight() - block->scrollbarLogicalHeight(); } LayoutUnit availableHeight = isOrthogonal(*this, *containingBlock()) ? containingBlockLogicalWidthForContent() : containingBlockLogicalHeightForContent(heightType); if (heightType == AvailableLogicalHeightType::ExcludeMarginBorderPadding) { // FIXME: Margin collapsing hasn't happened yet, so this incorrectly removes collapsed margins. availableHeight -= marginBefore() + marginAfter() + borderAndPaddingLogicalHeight(); } return availableHeight; } void RenderBox::computeBlockDirectionMargins(const RenderBlock& containingBlock, LayoutUnit& marginBefore, LayoutUnit& marginAfter) const { // First assert that we're not calling this method on box types that don't support margins. ASSERT(!isRenderTableCell()); ASSERT(!isRenderTableRow()); ASSERT(!isRenderTableSection()); ASSERT(!isRenderTableCol()); // Margins are calculated with respect to the logical width of the containing block (8.3) auto constrainBlockMarginInAvailableSpaceOrTrim = [&](auto marginSideInBlockDirection) { ASSERT(marginSideInBlockDirection == Style::MarginTrimSide::BlockStart || marginSideInBlockDirection == Style::MarginTrimSide::BlockEnd); if (containingBlock.shouldTrimChildMargin(marginSideInBlockDirection, *this)) { // FIXME(255434): This should be set when the margin is being trimmed // within the context of its layout system (block, flex, grid) and should not // be done at this level within RenderBox. We should be able to leave the // trimming responsibility to each of those contexts and not need to // do any of it here (trimming the margin and setting the rare data bit) if (isGridItem()) const_cast(*this).markMarginAsTrimmed(marginSideInBlockDirection); return 0_lu; } auto availableSpace = containingBlockLogicalWidthForContent(); return marginSideInBlockDirection == Style::MarginTrimSide::BlockStart ? Style::evaluateMinimum(style().marginBefore(containingBlock.writingMode()), availableSpace, style().usedZoomForLength()) : Style::evaluateMinimum(style().marginAfter(containingBlock.writingMode()), availableSpace, style().usedZoomForLength()); }; marginBefore = constrainBlockMarginInAvailableSpaceOrTrim(Style::MarginTrimSide::BlockStart); marginAfter = constrainBlockMarginInAvailableSpaceOrTrim(Style::MarginTrimSide::BlockEnd); } void RenderBox::computeAndSetBlockDirectionMargins(const RenderBlock& containingBlock) { LayoutUnit marginBefore; LayoutUnit marginAfter; computeBlockDirectionMargins(containingBlock, marginBefore, marginAfter); containingBlock.setMarginBeforeForChild(*this, marginBefore); containingBlock.setMarginAfterForChild(*this, marginAfter); } // MARK: - Positioned Layout inline static LayoutRange getScrollableContainingBlockRange(const RenderBox& containingBlock, BoxAxis physicalAxis) { if (BoxAxis::Horizontal == physicalAxis) { // PositionedLayoutConstraints expects us to not have adjusted for left-side scrollbars yet. // FIXME: Get PositionedLayoutConstraints to work in pre-corrected coordinates instead of doing "fixup" afterwards. auto range = containingBlock.scrollablePaddingAreaOverflowRect().xRange(); if (containingBlock.isHorizontalWritingMode() && containingBlock.shouldPlaceVerticalScrollbarOnLeft()) range.moveBy(-containingBlock.verticalScrollbarWidth()); return range; } return containingBlock.scrollablePaddingAreaOverflowRect().yRange(); } LayoutRange RenderBox::containingBlockRangeForPositioned(const RenderBoxModelObject& container, BoxAxis physicalAxis) const { ASSERT(container.canContainAbsolutelyPositionedObjects() || container.canContainFixedPositionObjects()); bool isContainerInlineAxis = physicalAxis == container.writingMode().inlineAxis(); auto startEdge = isContainerInlineAxis ? container.borderLogicalLeft() : container.borderBefore(); if (isFixedPositioned()) { if (auto* renderView = dynamicDowncast(container)) { return isContainerInlineAxis ? LayoutRange(startEdge, renderView->clientLogicalWidthForFixedPosition()) : LayoutRange(startEdge, renderView->clientLogicalHeightForFixedPosition()); } } // Inline containing blocks are formed by relatively-positioned inline boxes. if (auto* inlineContainer = dynamicDowncast(container)) { return isContainerInlineAxis ? LayoutRange(startEdge, inlineContainer->innerPaddingBoxWidth()) : LayoutRange(startEdge, inlineContainer->innerPaddingBoxHeight()); } auto* containingBlock = dynamicDowncast(container) ? : container.containingBlock(); if (!containingBlock) { ASSERT_NOT_REACHED(); return { }; } if (auto* fragmentedFlow = dynamicDowncast(containingBlock)) { return isContainerInlineAxis ? LayoutRange(startEdge, fragmentedFlow->contentLogicalWidthOfFirstFragment()) : LayoutRange(startEdge, fragmentedFlow->contentLogicalHeightOfFirstFragment()); } if (enclosingFragmentedFlow() && enclosingFragmentedFlow()->writingMode().inlineAxis() == physicalAxis) { LayoutUnit pageOffset = containingBlock->offsetFromLogicalTopOfFirstPage(); if (RenderFragmentContainer* fragment = containingBlock->fragmentAtBlockOffset(pageOffset)) { if (auto boxInfo = containingBlock->renderBoxFragmentInfo(fragment)) { auto size = boxInfo->logicalWidth(); if (BoxAxis::Horizontal == physicalAxis) size -= containingBlock->width() - containingBlock->clientWidth(); else size -= containingBlock->height() - containingBlock->clientHeight(); return LayoutRange(startEdge, std::max(0, size)); } } } if (!style().positionArea().isNone() && containingBlock->hasRenderOverflow() && containingBlock->hasPotentiallyScrollableOverflow()) return getScrollableContainingBlockRange(*containingBlock, physicalAxis); return BoxAxis::Horizontal == physicalAxis ? LayoutRange(startEdge, containingBlock->clientWidth()) : LayoutRange(startEdge, containingBlock->clientHeight()); } void RenderBox::computeOutOfFlowPositionedLogicalWidth(LogicalExtentComputedValues& computedValues) const { ASSERT(isOutOfFlowPositioned()); if (CheckedPtr replaced = dynamicDowncast(this)) return replaced->computeReplacedOutOfFlowPositionedLogicalWidth(computedValues); // QUESTIONS // FIXME 1: Should we still deal with these the cases of 'left' or 'right' having // the type 'static' in determining whether to calculate the static distance? // NOTE: 'static' is not a legal value for 'left' or 'right' as of CSS 2.1. // FIXME 2: Can perhaps optimize out cases when max-width/min-width are greater // than or less than the computed width(). Be careful of box-sizing and // percentage issues. // The following is based off of the W3C Working Draft from April 11, 2006 of // CSS 2.1: Section 10.3.7 "Absolutely positioned, non-replaced elements" // // (block-style-comments in this function and in computeOutOfFlowPositionedLogicalWidthUsing() // correspond to text from the spec) PositionedLayoutConstraints inlineConstraints(*this, LogicalBoxAxis::Inline); inlineConstraints.computeInsets(); // Calculate the used width. See CSS2 § 10.3.7. auto& styleToUse = style(); auto usedWidth = computeOutOfFlowPositionedLogicalWidthUsing(styleToUse.logicalWidth(), inlineConstraints); LayoutUnit transferredMinSize = LayoutUnit::min(); LayoutUnit transferredMaxSize = LayoutUnit::max(); if (shouldComputeLogicalHeightFromAspectRatio()) std::tie(transferredMinSize, transferredMaxSize) = computeMinMaxLogicalWidthFromAspectRatio(); // Clamp by max-width. auto usedMaxWidth = LayoutUnit::max(); if (auto& logicalMaxWidth = styleToUse.logicalMaxWidth(); !logicalMaxWidth.isNone()) usedMaxWidth = computeOutOfFlowPositionedLogicalWidthUsing(logicalMaxWidth, inlineConstraints); if (transferredMaxSize < usedMaxWidth) usedMaxWidth = computeOutOfFlowPositionedLogicalWidthUsing(Style::MaximumSize { Style::MaximumSize::Fixed { transferredMaxSize } }, inlineConstraints); if (usedWidth > usedMaxWidth) usedWidth = usedMaxWidth; // Clamp by min-width. auto usedMinWidth = LayoutUnit::min(); if (auto& logicalMinWidth = styleToUse.logicalMinWidth(); !logicalMinWidth.isKnownZero()) usedMinWidth = computeOutOfFlowPositionedLogicalWidthUsing(logicalMinWidth, inlineConstraints); if (transferredMinSize > usedMinWidth) usedMinWidth = computeOutOfFlowPositionedLogicalWidthUsing(Style::MinimumSize { Style::MinimumSize::Fixed { transferredMinSize } }, inlineConstraints); if (is(*this)) { // The used width of a table is the greater of the resolved table width, and the used min-width of the table. usedMinWidth = std::max(usedMinWidth, minContentLogicalWidthContribution() - inlineConstraints.bordersPlusPadding()); } if (usedWidth < usedMinWidth) usedWidth = usedMinWidth; bool hasWidthBorderBoxAspectRatio = style().aspectRatio().hasRatio() && style().boxSizingForAspectRatio() == BoxSizing::BorderBox && style().logicalWidth().isAuto(); // Set the final width value. computedValues.extent = hasWidthBorderBoxAspectRatio ? usedWidth : usedWidth + inlineConstraints.bordersPlusPadding(); // Calculate the position. inlineConstraints.resolvePosition(computedValues); inlineConstraints.fixupLogicalLeftPosition(computedValues); // Adjust logicalLeft if we need to for the flipped version of our writing mode in fragments. // FIXME: Add support for other types of objects as containerBlock, not only RenderBlock. if (enclosingFragmentedFlow() && isWritingModeRoot() && inlineConstraints.isOrthogonal()) { if (CheckedPtr container = dynamicDowncast(inlineConstraints.container())) { ASSERT(inlineConstraints.container().canHaveBoxInfoInFragment()); LayoutUnit logicalLeftPos = computedValues.position; LayoutUnit cbPageOffset = container->offsetFromLogicalTopOfFirstPage(); RenderFragmentContainer* cbFragment = container->fragmentAtBlockOffset(cbPageOffset); if (cbFragment) { RenderBoxFragmentInfo* boxInfo = container->renderBoxFragmentInfo(cbFragment); if (boxInfo) { logicalLeftPos += boxInfo->logicalLeft(); computedValues.position = logicalLeftPos; } } } } } template LayoutUnit RenderBox::computeOutOfFlowPositionedLogicalWidthUsing(const SizeType& logicalWidth, const PositionedLayoutConstraints& inlineConstraints) const { auto fallback = [&] -> LayoutUnit { bool shrinkToFit = inlineConstraints.insetFitsContent() || !inlineConstraints.alignmentAppliesStretch(ItemPosition::Stretch); if (shrinkToFit) { auto preferredWidth = maxContentLogicalWidthContribution() - inlineConstraints.bordersPlusPadding(); auto preferredMinWidth = minContentLogicalWidthContribution() - inlineConstraints.bordersPlusPadding(); return std::min(std::max(preferredMinWidth, inlineConstraints.availableContentSpace()), preferredWidth); } return inlineConstraints.availableContentSpace(); }; auto intrinsic = [&](const auto& keyword) -> LayoutUnit { auto availableSpace = inlineConstraints.containingSize(); availableSpace -= inlineConstraints.insetBeforeValue(); availableSpace -= inlineConstraints.insetAfterValue(); return std::max(0_lu, computeSizingKeywordLogicalWidthUsing(keyword, availableSpace, inlineConstraints.bordersPlusPadding()) - inlineConstraints.bordersPlusPadding()); }; return WTF::switchOn(logicalWidth, [&](const typename SizeType::Fixed& fixedLogicalWidth) -> LayoutUnit { return adjustContentBoxLogicalWidthForBoxSizing(fixedLogicalWidth); }, [&](const typename SizeType::Percentage& percentageLogicalWidth) -> LayoutUnit { return adjustContentBoxLogicalWidthForBoxSizing(Style::evaluate(percentageLogicalWidth, inlineConstraints.containingSize())); }, [&](const typename SizeType::Calc& calculatedLogicalWidth) -> LayoutUnit { return adjustContentBoxLogicalWidthForBoxSizing(Style::evaluate(calculatedLogicalWidth, inlineConstraints.containingSize(), style().usedZoomForLength())); }, [&](const CSS::Keyword::FitContent& keyword) -> LayoutUnit { return intrinsic(keyword); }, [&](const CSS::Keyword::Stretch&) -> LayoutUnit { return inlineConstraints.availableContentSpace(); }, [&](const CSS::Keyword::WebkitFillAvailable&) -> LayoutUnit { return inlineConstraints.availableContentSpace(); }, [&](const CSS::Keyword::MinContent& keyword) -> LayoutUnit { return intrinsic(keyword); }, [&](const CSS::Keyword::MaxContent& keyword) -> LayoutUnit { return intrinsic(keyword); }, [&](const CSS::Keyword::Intrinsic&) -> LayoutUnit { if (shouldComputeLogicalWidthFromAspectRatio()) return inlineConstraints.containingSize(); return fallback(); }, [&](const CSS::Keyword::MinIntrinsic&) -> LayoutUnit { if (shouldComputeLogicalWidthFromAspectRatio()) return inlineConstraints.containingSize(); return fallback(); }, [&](const CSS::Keyword::Auto&) -> LayoutUnit { if constexpr (std::same_as) { if (shouldComputeLogicalWidthFromAspectRatio()) { auto [minLogicalWidth, maxLogicalWidth] = computeIntrinsicLogicalWidths(); return minLogicalWidth; } return 0_lu; } else { if (shouldComputeLogicalWidthFromAspectRatio()) { auto logicalWidth = constrainLogicalWidthByMinMax(computeLogicalWidthFromAspectRatio(), inlineConstraints.containingSize(), *containingBlock(), AllowIntrinsic::No); return style().boxSizingForAspectRatio() == BoxSizing::BorderBox ? logicalWidth : logicalWidth - inlineConstraints.bordersPlusPadding(); } return fallback(); } }, [&](const CSS::Keyword::None&) -> LayoutUnit { return inlineConstraints.containingSize(); } ); } void RenderBox::computeOutOfFlowPositionedLogicalHeight(LogicalExtentComputedValues& computedValues) const { ASSERT(isOutOfFlowPositioned()); if (CheckedPtr replaced = dynamicDowncast(this)) return replaced->computeReplacedOutOfFlowPositionedLogicalHeight(computedValues); PositionedLayoutConstraints blockConstraints(*this, LogicalBoxAxis::Block); blockConstraints.computeInsets(); // Calculate the used height. See CSS2 § 10.6.4. auto& styleToUse = style(); LayoutUnit computedHeight = computedValues.extent; LayoutUnit usedHeight = computeOutOfFlowPositionedLogicalHeightUsing(styleToUse.logicalHeight(), computedHeight, blockConstraints); // Clamp by max-height. if (auto logicalMaxHeight = styleToUse.logicalMaxHeight(); !logicalMaxHeight.isNone()) { auto usedMaxHeight = computeOutOfFlowPositionedLogicalHeightUsing(logicalMaxHeight, computedHeight, blockConstraints); if (usedHeight > usedMaxHeight) usedHeight = usedMaxHeight; } // Clamp by min-height. if (auto& logicalMinHeight = styleToUse.logicalMinHeight(); logicalMinHeight.isAuto() || !logicalMinHeight.isKnownZero()) { auto usedMinHeight = computeOutOfFlowPositionedLogicalHeightUsing(logicalMinHeight, computedHeight, blockConstraints); if (usedHeight < usedMinHeight) usedHeight = usedMinHeight; } // Set the final height value. if (is(*this)) { // Table content can expand the table beyond the specified height. The out-of-flow constraint // equation can produce a height smaller than content (e.g. height: 100% of a 0px // containing block). In-flow tables don't need this because their logicalHeight // is the accumulated section height and nothing overrides it. usedHeight = std::max(usedHeight, computedValues.extent - blockConstraints.bordersPlusPadding()); } computedValues.extent = usedHeight + blockConstraints.bordersPlusPadding(); // Calculate the position. blockConstraints.resolvePosition(computedValues); blockConstraints.adjustLogicalTopWithLogicalHeightIfNeeded(computedValues); // Adjust logicalTop if we need to for perpendicular writing modes in fragments. // FIXME: Add support for other types of objects as containerBlock, not only RenderBlock. if (enclosingFragmentedFlow() && blockConstraints.isOrthogonal()) { if (CheckedPtr container = dynamicDowncast(blockConstraints.container())) { ASSERT(blockConstraints.container().canHaveBoxInfoInFragment()); LayoutUnit logicalTopPos = computedValues.position; LayoutUnit cbPageOffset = container->offsetFromLogicalTopOfFirstPage() - logicalLeft(); RenderFragmentContainer* cbFragment = container->fragmentAtBlockOffset(cbPageOffset); if (cbFragment) { RenderBoxFragmentInfo* boxInfo = container->renderBoxFragmentInfo(cbFragment); if (boxInfo) { logicalTopPos += boxInfo->logicalLeft(); computedValues.position = logicalTopPos; } } } } } LayoutUnit RenderBox::computeOutOfFlowPositionedLogicalHeightUsing(const Style::PreferredSize& logicalHeight, LayoutUnit computedHeight, const PositionedLayoutConstraints& blockConstraints) const { auto contentLogicalHeight = computedHeight - blockConstraints.bordersPlusPadding(); // Table height is determined by content when height is auto (tables don't stretch to fill insets). if (isRenderTable() && logicalHeight.isAuto()) return contentLogicalHeight; bool fromAspectRatio = shouldComputeLogicalHeightFromAspectRatio(); bool logicalHeightIsAuto = logicalHeight.isAuto() && !fromAspectRatio; if (!logicalHeightIsAuto) { if (logicalHeight.isFillAvailable()) return adjustContentBoxLogicalHeightForBoxSizing(computeSizingKeywordLogicalContentHeightUsing(logicalHeight, contentLogicalHeight, blockConstraints.bordersPlusPadding()).value_or(0_lu)); if (logicalHeight.isStretch()) return blockConstraints.availableContentSpace(); if (logicalHeight.isIntrinsic()) return adjustContentBoxLogicalHeightForBoxSizing(computeSizingKeywordLogicalContentHeightUsing(logicalHeight, contentLogicalHeight, blockConstraints.bordersPlusPadding()).value_or(0_lu)); if (fromAspectRatio) { auto resolvedLogicalHeight = blockSizeFromAspectRatio(horizontalBorderAndPaddingExtent(), verticalBorderAndPaddingExtent(), style().logicalAspectRatio(), style().boxSizingForAspectRatio(), logicalWidth(), style().aspectRatio(), isRenderReplaced()); return std::max(LayoutUnit(), resolvedLogicalHeight - blockConstraints.bordersPlusPadding()); } return adjustContentBoxLogicalHeightForBoxSizing(Style::evaluate(logicalHeight, blockConstraints.containingSize(), style().usedZoomForLength())); } bool shrinkToFit = blockConstraints.insetFitsContent() || !blockConstraints.alignmentAppliesStretch(ItemPosition::Stretch); if (!shrinkToFit) return blockConstraints.availableContentSpace(); return contentLogicalHeight; } LayoutUnit RenderBox::computeOutOfFlowPositionedLogicalHeightUsing(const Style::MinimumSize& originalLogicalHeight, LayoutUnit computedHeight, const PositionedLayoutConstraints& blockConstraints) const { auto contentLogicalHeight = computedHeight - blockConstraints.bordersPlusPadding(); auto logicalHeight = originalLogicalHeight; if (logicalHeight.isAuto()) { if (shouldComputeLogicalHeightFromAspectRatio()) logicalHeight = Style::MinimumSize::Fixed { computedHeight }; else logicalHeight = 0_css_px; } if (logicalHeight.isFillAvailable()) return adjustContentBoxLogicalHeightForBoxSizing(computeSizingKeywordLogicalContentHeightUsing(logicalHeight, contentLogicalHeight, blockConstraints.bordersPlusPadding()).value_or(0_lu)); if (logicalHeight.isStretch()) return blockConstraints.availableContentSpace(); if (logicalHeight.isIntrinsic()) return adjustContentBoxLogicalHeightForBoxSizing(computeSizingKeywordLogicalContentHeightUsing(logicalHeight, contentLogicalHeight, blockConstraints.bordersPlusPadding()).value_or(0_lu)); return adjustContentBoxLogicalHeightForBoxSizing(Style::evaluate(logicalHeight, blockConstraints.containingSize(), style().usedZoomForLength())); } LayoutUnit RenderBox::computeOutOfFlowPositionedLogicalHeightUsing(const Style::MaximumSize& logicalHeight, LayoutUnit computedHeight, const PositionedLayoutConstraints& blockConstraints) const { auto contentLogicalHeight = computedHeight - blockConstraints.bordersPlusPadding(); if (logicalHeight.isFillAvailable()) return adjustContentBoxLogicalHeightForBoxSizing(computeSizingKeywordLogicalContentHeightUsing(logicalHeight, contentLogicalHeight, blockConstraints.bordersPlusPadding()).value_or(0_lu)); if (logicalHeight.isStretch()) return blockConstraints.availableContentSpace(); if (logicalHeight.isIntrinsic()) return adjustContentBoxLogicalHeightForBoxSizing(computeSizingKeywordLogicalContentHeightUsing(logicalHeight, contentLogicalHeight, blockConstraints.bordersPlusPadding()).value_or(0_lu)); return adjustContentBoxLogicalHeightForBoxSizing(Style::evaluate(logicalHeight, blockConstraints.containingSize(), style().usedZoomForLength())); } PositionWithAffinity RenderBox::positionForPoint(const LayoutPoint& point, HitTestSource source, const RenderFragmentContainer* fragment) { // no children...return this render object's element, if there is one, and offset 0 if (!firstChild()) return createPositionWithAffinity(nonPseudoElement() ? firstPositionInOrBeforeNode(nonPseudoElement()) : Position()); if (isRenderTable() && nonPseudoElement()) { LayoutUnit right = contentBoxWidth() + horizontalBorderAndPaddingExtent(); LayoutUnit bottom = contentBoxHeight() + verticalBorderAndPaddingExtent(); if (point.x() < 0 || point.x() > right || point.y() < 0 || point.y() > bottom) { if (point.x() <= right / 2) return createPositionWithAffinity(firstPositionInOrBeforeNode(nonPseudoElement())); return createPositionWithAffinity(lastPositionInOrAfterNode(nonPseudoElement())); } } // Pass off to the closest child. LayoutUnit minDist = LayoutUnit::max(); RenderBox* closestRenderer = nullptr; for (auto& renderer : childrenOfType(*this)) { if (CheckedPtr fragmentedFlow = dynamicDowncast(*this)) { ASSERT(fragment || fragmentedFlow->isSkippedContent()); if (!fragmentedFlow->objectShouldFragmentInFlowFragment(&renderer, fragment)) continue; } if ((!renderer.firstChild() && !renderer.isInline() && !is(renderer)) || (source == HitTestSource::Script ? renderer.style().visibility() : renderer.style().usedVisibility()) != Visibility::Visible) continue; LayoutUnit top = renderer.borderTop() + renderer.paddingTop() + renderer.y(); LayoutUnit bottom = top + renderer.contentBoxHeight(); LayoutUnit left = renderer.borderLeft() + renderer.paddingLeft() + renderer.x(); LayoutUnit right = left + renderer.contentBoxWidth(); if (point.x() <= right && point.x() >= left && point.y() >= top && point.y() <= bottom) { return renderer.positionForPoint(point - renderer.locationOffset(), source, fragment); } // Find the distance from (x, y) to the box. Split the space around the box into 8 pieces // and use a different compare depending on which piece (x, y) is in. LayoutPoint cmp; if (point.x() > right) { if (point.y() < top) cmp = LayoutPoint(right, top); else if (point.y() > bottom) cmp = LayoutPoint(right, bottom); else cmp = LayoutPoint(right, point.y()); } else if (point.x() < left) { if (point.y() < top) cmp = LayoutPoint(left, top); else if (point.y() > bottom) cmp = LayoutPoint(left, bottom); else cmp = LayoutPoint(left, point.y()); } else { if (point.y() < top) cmp = LayoutPoint(point.x(), top); else cmp = LayoutPoint(point.x(), bottom); } LayoutSize difference = cmp - point; LayoutUnit dist = difference.width() * difference.width() + difference.height() * difference.height(); if (dist < minDist) { closestRenderer = &renderer; minDist = dist; } } if (closestRenderer) return closestRenderer->positionForPoint(point - closestRenderer->locationOffset(), source, fragment); return createPositionWithAffinity(firstPositionInOrBeforeNode(nonPseudoElement())); } bool RenderBox::shrinkToAvoidFloats() const { // Floating objects don't shrink. Objects that don't avoid floats don't shrink. Non-inline box type of inline level elements don't shrink. if (isInline() || isFloating() || !avoidsFloats()) return false; // Only auto width objects can possibly shrink to avoid floats. return style().width().isAuto(); } bool RenderBox::avoidsFloats() const { if (is(*this) || isLegend() || (element() && element()->isFormControlElement())) return true; #if ENABLE(MATHML) if (is(*this)) return true; #endif if (CheckedPtr renderBlock = dynamicDowncast(*this)) return renderBlock->createsNewFormattingContext(); return false; } IntBoxExtent RenderBox::computeFilterOutsets() const { if (!hasFilter()) return { }; auto zoom = style().usedZoomForLength(); if (auto outsets = style().filter().calculateOutsets(zoom)) return *outsets; // FIXME: Need to compute outsets for reference filters: webkit.org/b/237538. return { }; } void RenderBox::addVisualEffectOverflow() { bool hasBoxShadow = !style().boxShadow().isNone(); bool hasBorderImageOutsets = style().hasBorderImageOutsets(); bool hasOutline = outlineStyleForRepaint().hasOutlineInVisualOverflow(); bool hasInflatingFilters = hasFilter() && !computeFilterOutsets().isZero(); if (!hasBoxShadow && !hasBorderImageOutsets && !hasOutline && !hasInflatingFilters) return; addVisualOverflow(applyVisualEffectOverflow(borderBoxRect())); if (CheckedPtr fragmentedFlow = enclosingFragmentedFlow()) fragmentedFlow->addFragmentsVisualEffectOverflow(*this); } template static void NODELETE convertOutsetsToOverflowCoordinates(RectEdges& outsets, WritingMode writingMode) { switch (writingMode.blockDirection()) { case FlowDirection::TopToBottom: case FlowDirection::LeftToRight: break; case FlowDirection::BottomToTop: std::swap(outsets.top(), outsets.bottom()); break; case FlowDirection::RightToLeft: std::swap(outsets.left(), outsets.right()); break; } } LayoutRect RenderBox::applyVisualEffectOverflow(const LayoutRect& borderBox, EnumSet options) const { LayoutUnit overflowMinX = borderBox.x(); LayoutUnit overflowMaxX = borderBox.maxX(); LayoutUnit overflowMinY = borderBox.y(); LayoutUnit overflowMaxY = borderBox.maxY(); // Compute box-shadow overflow first. if (!style().boxShadow().isNone()) { auto shadowOutsets = Style::shadowOutsetExtent(style().boxShadow(), style().usedZoomForLength()); // Box-shadow extent's left and top are negative when extends to left and top, respectively, so negate to convert to outsets. shadowOutsets.left() = -shadowOutsets.left(); shadowOutsets.top() = -shadowOutsets.top(); convertOutsetsToOverflowCoordinates(shadowOutsets, writingMode()); overflowMinX = borderBox.x() - shadowOutsets.left(); overflowMaxX = borderBox.maxX() + shadowOutsets.right(); overflowMinY = borderBox.y() - shadowOutsets.top(); overflowMaxY = borderBox.maxY() + shadowOutsets.bottom(); } // Now compute border-image-outset overflow. if (style().hasBorderImageOutsets()) { auto borderOutsets = style().borderImageOutsets(); convertOutsetsToOverflowCoordinates(borderOutsets, writingMode()); overflowMinX = std::min(overflowMinX, borderBox.x() - borderOutsets.left()); overflowMaxX = std::max(overflowMaxX, borderBox.maxX() + borderOutsets.right()); overflowMinY = std::min(overflowMinY, borderBox.y() - borderOutsets.top()); overflowMaxY = std::max(overflowMaxY, borderBox.maxY() + borderOutsets.bottom()); } if (outlineStyleForRepaint().hasOutlineInVisualOverflow()) { auto outlineSize = LayoutUnit { outlineStyleForRepaint().usedOutlineSize() }; overflowMinX = std::min(overflowMinX, borderBox.x() - outlineSize); overflowMaxX = std::max(overflowMaxX, borderBox.maxX() + outlineSize); overflowMinY = std::min(overflowMinY, borderBox.y() - outlineSize); overflowMaxY = std::max(overflowMaxY, borderBox.maxY() + outlineSize); } if (hasFilter() && !options.contains(VisualEffectOverflowOption::ExcludeFilterOutsets)) { auto outsets = computeFilterOutsets(); convertOutsetsToOverflowCoordinates(outsets, writingMode()); overflowMinX = std::min(overflowMinX, borderBox.x() - outsets.left()); overflowMaxX = std::max(overflowMaxX, borderBox.maxX() + outsets.right()); overflowMinY = std::min(overflowMinY, borderBox.y() - outsets.top()); overflowMaxY = std::max(overflowMaxY, borderBox.maxY() + outsets.bottom()); } // Add in the final overflow with shadows and outsets combined. return LayoutRect(overflowMinX, overflowMinY, overflowMaxX - overflowMinX, overflowMaxY - overflowMinY); } void RenderBox::addOverflowFromInFlowChildren(OptionSet options) { for (auto& child : childrenOfType(*this)) { if (!child.isFloatingOrOutOfFlowPositioned()) addOverflowFromContainedBox(child, options); } } void RenderBox::addOverflowFromContainedBox(const RenderBox& child, OptionSet options) { ASSERT(!child.isFloating()); addOverflowWithRendererOffset(child, child.locationOffset(), options); } void RenderBox::addOverflowFromFloatBox(const FloatingObject& floatBox) { if (!floatBox.renderer()) return; addOverflowWithRendererOffset(*floatBox.renderer(), floatBox.locationOffsetOfBorderBox()); } // 'offsetFromThis' is normally the renderer's position (RenderBox::location()). // However in case of a float box, it may belong to a different container // (but is intrusive to this container hence calling this function), // meaning that the renderer's position may not be the same as its actual offset from this container. void RenderBox::addOverflowWithRendererOffset(const RenderBox& renderer, LayoutSize offsetFromThis, OptionSet options) { UNUSED_PARAM(options); // Never allow flow threads to propagate overflow up to a parent. if (renderer.isRenderFragmentedFlow()) return; CheckedPtr fragmentedFlow = enclosingFragmentedFlow(); if (fragmentedFlow) fragmentedFlow->addFragmentsOverflowFromChild(*this, renderer, offsetFromThis); // Only propagate layout overflow from the child if the child isn't clipping its overflow. If it is, then // its overflow is internal to it, and we don't care about it. layoutOverflowRectForPropagation takes care of this // and just propagates the border box rect instead. auto childLayoutOverflowRect = renderer.layoutOverflowRectForPropagation(writingMode()); childLayoutOverflowRect.move(offsetFromThis); addLayoutOverflow(childLayoutOverflowRect); if ((hasPotentiallyScrollableOverflow() || isRenderView()) && options.containsAny({ ComputeOverflowOptions::MarginsExtendLayoutOverflow, ComputeOverflowOptions::MarginsExtendContentAreaX, ComputeOverflowOptions::MarginsExtendContentAreaY })) { addMarginBoxOverflow(renderer, offsetFromThis, options); } if (paintContainmentApplies()) return; // Add in visual overflow from the child. Even if the child clips its overflow, it may still // have visual overflow of its own set from box shadows or reflections. It is unnecessary to propagate this // overflow if we are clipping our own overflow. if (hasPotentiallyScrollableOverflow()) return; auto childVisualOverflowRect = std::optional { }; auto computeChildVisualOverflowRect = [&] () { childVisualOverflowRect = renderer.visualOverflowRectForPropagation(writingMode()); childVisualOverflowRect->move(offsetFromThis); }; // If this block is flowed inside a flow thread, make sure its overflow is propagated to the containing fragments. if (fragmentedFlow) { computeChildVisualOverflowRect(); fragmentedFlow->addFragmentsVisualOverflow(*this, *childVisualOverflowRect); } else { // Update our visual overflow in case the child spills out the block, but only if we were going to paint // the child block ourselves. if (renderer.hasSelfPaintingLayer() && !hasFilter()) return; } if (!childVisualOverflowRect) computeChildVisualOverflowRect(); addVisualOverflow(*childVisualOverflowRect); } bool RenderBox::hasLayoutOverflow() const { if (!m_overflow) return false; return !flippedClientBoxRect().contains(m_overflow->layoutOverflowRect()); } LayoutOptionalOutsets RenderBox::allowedLayoutOverflow() const { LayoutOptionalOutsets allowance; // Overflow is in the block's coordinate space and thus is flipped // for horizontal-bt and vertical-rl writing modes. This means we can // treat horizontal-tb/bt as the same and vertical-lr/rl as the same. if (writingMode().isHorizontal()) { allowance.top() = 0_lu; if (writingMode().isInlineLeftToRight()) allowance.left() = 0_lu; else allowance.right() = 0_lu; } else { allowance.left() = 0_lu; if (writingMode().isInlineTopToBottom()) allowance.top() = 0_lu; else allowance.bottom() = 0_lu; } return allowance; } LayoutRect RenderBox::clampToAllowedLayoutOverflow(const LayoutRect& rect, const LayoutRect& flippedClientBoxRect) { LayoutOptionalOutsets allowance = allowedLayoutOverflow(); LayoutRect clippedRect(rect); // Non-negative values indicate a limit, let's apply them. if (allowance.top()) clippedRect.shiftYEdgeTo(std::max(rect.y(), flippedClientBoxRect.y() - *allowance.top())); if (allowance.bottom()) clippedRect.shiftMaxYEdgeTo(std::min(rect.maxY(), flippedClientBoxRect.maxY() + *allowance.bottom())); if (allowance.left()) clippedRect.shiftXEdgeTo(std::max(rect.x(), flippedClientBoxRect.x() - *allowance.left())); if (allowance.right()) clippedRect.shiftMaxXEdgeTo(std::min(rect.maxX(), flippedClientBoxRect.maxX() + *allowance.right())); return clippedRect; } void RenderBox::addLayoutOverflow(const LayoutRect& rect) { auto clientBox = flippedClientBoxRect(); if (clientBox.contains(rect) || rect.isEmpty()) return; // For overflow clip objects, we don't want to propagate overflow into unreachable areas. if (hasPotentiallyScrollableOverflow() || isRenderView()) { LayoutRect clippedRect = clampToAllowedLayoutOverflow(rect, clientBox); // Now re-test with the adjusted rectangle and see if it has become unreachable or fully // contained. if (clientBox.contains(clippedRect) || clippedRect.isEmpty()) return; ensureOverflow().addLayoutOverflow(clippedRect); } else ensureOverflow().addLayoutOverflow(rect); } void RenderBox::addVisualOverflow(const LayoutRect& rect) { LayoutRect borderBox = borderBoxRect(); if (borderBox.contains(rect) || rect.isEmpty()) return; ensureOverflow().addVisualOverflow(rect); } void RenderBox::addMarginBoxOverflow(const RenderBox& renderer, LayoutSize offsetFromThis, OptionSet options) { auto childMarginRect = renderer.marginBoxRect(); childMarginRect.move(offsetFromThis); auto contentArea = scrollableContentAreaOverflowRect(); if (contentArea.contains(childMarginRect)) return; // Quick-check short circuit. // Some in-flow boxes (e.g. grid items) only extend the layout overflow edge. if (options.contains(ComputeOverflowOptions::MarginsExtendLayoutOverflow)) { auto clippedChildMarginRect = clampToAllowedLayoutOverflow(childMarginRect, flippedClientBoxRect()); if (!layoutOverflowRect().contains(clippedChildMarginRect)) ensureOverflow().addLayoutOverflow(clippedChildMarginRect); ASSERT(!options.containsAny({ ComputeOverflowOptions::MarginsExtendContentAreaX, ComputeOverflowOptions::MarginsExtendContentAreaY })); // If you need this to work, remove the return statement. return; } // Some in-flow boxes (e.g. flex items) extend the content overflow edge as well. if (options.contains(ComputeOverflowOptions::MarginsExtendContentAreaX) && !contentArea.containsX(childMarginRect)) ensureOverflow().addContentOverflowX(childMarginRect); if (options.contains(ComputeOverflowOptions::MarginsExtendContentAreaY) && !contentArea.containsY(childMarginRect)) ensureOverflow().addContentOverflowY(childMarginRect); } void RenderBox::clearOverflow() { m_overflow = { }; if (CheckedPtr fragmentedFlow = enclosingFragmentedFlow()) fragmentedFlow->clearFragmentsOverflow(*this); } RenderOverflow& RenderBox::ensureOverflow() { if (!m_overflow) m_overflow = makeUnique(flippedClientBoxRect(), borderBoxRect(), flippedContentBoxRect()); return *m_overflow; } bool RenderBox::percentageLogicalHeightIsResolvable() const { // Do this to avoid duplicating all the logic that already exists when computing an actual percentage height. return computePercentageLogicalHeight(Style::PreferredSize { 100_css_percentage }) != std::nullopt; } bool RenderBox::hasUnsplittableScrollingOverflow() const { // We will paginate as long as we don't scroll overflow in the pagination direction. bool isHorizontal = isHorizontalWritingMode(); if ((isHorizontal && !scrollsOverflowY()) || (!isHorizontal && !scrollsOverflowX())) return false; // Fragmenting scrollbars is only problematic in interactive media, e.g. multicol on a // screen. If we're printing, which is non-interactive media, we should allow objects with // non-visible overflow to be paginated as normally. if (document().printing()) return false; // We do have overflow. We'll still be willing to paginate as long as the block // has auto logical height, auto or undefined max-logical-height and a zero or auto min-logical-height. // Note this is just a heuristic, and it's still possible to have overflow under these // conditions, but it should work out to be good enough for common cases. Paginating overflow // with scrollbars present is not the end of the world and is what we used to do in the old model anyway. return style().logicalHeight().isSpecified() || (style().logicalMaxHeight().isSpecified() && (!style().logicalMaxHeight().isPercentOrCalculated() || percentageLogicalHeightIsResolvable())) || (style().logicalMinHeight().isSpecified() && style().logicalMinHeight().isPossiblyPositive() && (!style().logicalMinHeight().isPercentOrCalculated() || percentageLogicalHeightIsResolvable())); } bool RenderBox::isUnsplittableForPagination() const { return isBlockLevelReplacedOrAtomicInline() || (is(element()) && !is(element())) || hasUnsplittableScrollingOverflow() || (parent() && isWritingModeRoot()) || (isFloating() && style().pseudoElementType() == PseudoElementType::FirstLetter && style().initialLetter().drop() > 0) || shouldApplySizeContainment(); } LayoutUnit RenderBox::lineHeight() const { auto shouldUseLineHeightFromStyle = [&] { if (is(*this)) return true; if (CheckedPtr listMarkerRenderer = dynamicDowncast(*this)) return !listMarkerRenderer->isImage(); return false; }; if (shouldUseLineHeightFromStyle()) return LayoutUnit::fromFloatCeil(firstLineStyle().computedLineHeight()); if (isBlockLevelReplacedOrAtomicInline()) return marginBefore() + logicalHeight() + marginAfter(); return { }; } LayoutRect RenderBox::applyPaintGeometryTransformToRect(LayoutRect rect) const { // If we are relatively positioned or if we have a transform, then we have to convert // this rectangle into physical coordinates, apply relative positioning and transforms // to it, and then convert it back. // It ensures that the overflow rect tracks the paint geometry and not the inflow layout position. bool isTransformed = this->isTransformed(); // While a stickily positioned renderer is also inflow positioned, they stretch the overflow rect with their inflow geometry // (as opposed to the paint geometry) because they are not stationary. bool paintGeometryAffectsOverflow = isTransformed || (isInFlowPositioned() && !isStickilyPositioned()); if (!paintGeometryAffectsOverflow) return rect; flipForWritingMode(rect); LayoutSize containerOffset; if (isInFlowPositioned()) containerOffset = offsetForInFlowPosition(); auto container = this->container(); if (shouldUseTransformFromContainer(container)) { TransformationMatrix transform; getTransformFromContainer(containerOffset, transform); rect = transform.mapRect(rect); } else rect.move(offsetForInFlowPosition()); // Now we need to flip back. flipForWritingMode(rect); return rect; } LayoutRect RenderBox::logicalVisualOverflowRectForPropagation(const WritingMode parentWritingMode) const { LayoutRect rect = visualOverflowRectForPropagation(parentWritingMode); if (!parentWritingMode.isHorizontal()) return rect.transposedRect(); return rect; } LayoutRect RenderBox::convertRectToParentWritingMode(LayoutRect rect, const WritingMode parentWritingMode) const { // If the writing modes of the child and parent match, then we don't have to // do anything fancy. Just return the result. if (parentWritingMode.blockDirection() == writingMode().blockDirection()) return rect; // We are putting ourselves into our parent's coordinate space. If there is a flipped block mismatch // in a particular axis, then we have to flip the rect along that axis. if (writingMode().blockDirection() == FlowDirection::RightToLeft || parentWritingMode.blockDirection() == FlowDirection::RightToLeft) rect.setX(width() - rect.maxX()); else if (writingMode().blockDirection() == FlowDirection::BottomToTop || parentWritingMode.blockDirection() == FlowDirection::BottomToTop) rect.setY(height() - rect.maxY()); return rect; } LayoutRect RenderBox::visualOverflowRectForPropagation(const WritingMode parentWritingMode) const { LayoutRect rect = applyPaintGeometryTransformToRect(visualOverflowRect()); return convertRectToParentWritingMode(rect, parentWritingMode); } LayoutRect RenderBox::logicalLayoutOverflowRectForPropagation(const WritingMode parentWritingMode) const { LayoutRect rect = layoutOverflowRectForPropagation(parentWritingMode); if (!parentWritingMode.isHorizontal()) return rect.transposedRect(); return rect; } LayoutRect RenderBox::layoutOverflowRectForPropagation(const WritingMode parentWritingMode) const { // Only propagate interior layout overflow if we don't completely clip it. auto rect = borderBoxRect(); // As per https://drafts.csswg.org/css-overflow-3/#scrollable, both flex and grid items margins' should contribute to the scrollable overflow area. if (shouldMarginInlineEndContributeToScrollableOverflow(*this)) { auto marginEnd = std::max(0_lu, this->marginEnd(parentWritingMode)); parentWritingMode.isHorizontal() ? rect.setWidth(rect.width() + marginEnd) : rect.setHeight(rect.height() + marginEnd); } if (!shouldApplyLayoutContainment()) { if (hasNonVisibleOverflow()) { if (style().overflowX() == Overflow::Clip && style().overflowY() == Overflow::Visible) { LayoutRect clippedOverflowRect = layoutOverflowRect(); clippedOverflowRect.setX(rect.x()); clippedOverflowRect.setWidth(rect.width()); rect.unite(clippedOverflowRect); } else if (style().overflowY() == Overflow::Clip && style().overflowX() == Overflow::Visible) { LayoutRect clippedOverflowRect = layoutOverflowRect(); clippedOverflowRect.setY(rect.y()); clippedOverflowRect.setHeight(rect.height()); rect.unite(clippedOverflowRect); } } else rect.unite(layoutOverflowRect()); } rect = applyPaintGeometryTransformToRect(rect); return convertRectToParentWritingMode(rect, parentWritingMode); } LayoutRect RenderBox::flippedClientBoxRect() const { // Because of the special coordinate system used for overflow rectangles (not quite logical, not // quite physical), we need to flip the block progression coordinate in vertical-rl and // horizontal-bt writing modes. Apart from that, this method does the same as clientBoxRect(). auto rect = paddingBoxRectIncludingScrollbar(); // Flip block progression axis if writing mode is vertical-rl or horizontal-bt. flipForWritingMode(rect); if (hasNonVisibleOverflow()) { // Subtract space occupied by scrollbars. They are at their physical edge in this coordinate // system, so order is important here: first flip, then subtract scrollbars. if (isHorizontalWritingMode() && shouldPlaceVerticalScrollbarOnLeft()) rect.move(verticalScrollbarWidth(), 0); rect.contract(verticalScrollbarWidth(), horizontalScrollbarHeight()); } return rect; } LayoutUnit RenderBox::offsetLeft() const { return adjustedPositionRelativeToOffsetParent(topLeftLocation()).x(); } LayoutUnit RenderBox::offsetTop() const { return adjustedPositionRelativeToOffsetParent(topLeftLocation()).y(); } LayoutPoint RenderBox::flipForWritingModeForChild(const RenderBox& child, const LayoutPoint& point) const { if (!writingMode().isBlockFlipped()) return point; // The child is going to add in its x() and y(), so we have to make sure it ends up in // the right place. if (isHorizontalWritingMode()) return LayoutPoint(point.x(), point.y() + height() - child.height() - (2 * child.y())); return LayoutPoint(point.x() + width() - child.width() - (2 * child.x()), point.y()); } void RenderBox::flipForWritingMode(LayoutRect& rect) const { if (!writingMode().isBlockFlipped()) return; if (isHorizontalWritingMode()) rect.setY(height() - rect.maxY()); else rect.setX(width() - rect.maxX()); } LayoutUnit RenderBox::flipForWritingMode(LayoutUnit position) const { if (!writingMode().isBlockFlipped()) return position; return logicalHeight() - position; } LayoutPoint RenderBox::flipForWritingMode(const LayoutPoint& position) const { if (!writingMode().isBlockFlipped()) return position; return isHorizontalWritingMode() ? LayoutPoint(position.x(), height() - position.y()) : LayoutPoint(width() - position.x(), position.y()); } LayoutSize RenderBox::flipForWritingMode(const LayoutSize& offset) const { if (!writingMode().isBlockFlipped()) return offset; return isHorizontalWritingMode() ? LayoutSize(offset.width(), height() - offset.height()) : LayoutSize(width() - offset.width(), offset.height()); } FloatPoint RenderBox::flipForWritingMode(const FloatPoint& position) const { if (!writingMode().isBlockFlipped()) return position; return isHorizontalWritingMode() ? FloatPoint(position.x(), height() - position.y()) : FloatPoint(width() - position.x(), position.y()); } void RenderBox::flipForWritingMode(FloatRect& rect) const { if (!writingMode().isBlockFlipped()) return; if (isHorizontalWritingMode()) rect.setY(height() - rect.maxY()); else rect.setX(width() - rect.maxX()); } void RenderBox::flipForWritingMode(RepaintRects& rects) const { if (!writingMode().isBlockFlipped()) return; rects.flipForWritingMode(size(), isHorizontalWritingMode()); } LayoutPoint RenderBox::topLeftLocationWithFlipping() const { ASSERT(view().frameView().hasFlippedBlockRenderers()); auto* containerBlock = containingBlock(); if (!containerBlock || containerBlock == this) return location(); return containerBlock->flipForWritingModeForChild(*this, location()); } bool RenderBox::shouldIgnoreAspectRatio() const { return !style().aspectRatio().hasRatio() || isTablePart(); } static inline bool shouldComputeLogicalWidthFromAspectRatioAndInsets(const RenderBox& renderer) { if (!renderer.isOutOfFlowPositioned()) return false; auto& style = renderer.style(); if (!style.logicalWidth().isAuto()) { // Not applicable for aspect ratio computation. return false; } // When both left and right are set, the out-of-flow positioned box is horizontally constrained and aspect ratio for the logical width is not applicable. auto hasConstrainedWidth = (!style.logicalLeft().isAuto() && !style.logicalRight().isAuto()) || renderer.intrinsicLogicalWidth(); if (hasConstrainedWidth) return false; // When both top and bottom are set, the out-of-flow positioned box is vertically constrained and it can be used as if it had a non-auto height value. auto hasConstrainedHeight = !style.logicalTop().isAuto() && !style.logicalBottom().isAuto(); if (!hasConstrainedHeight) return false; // FIXME: This could probably be omitted and let the callers handle the height check (as they seem to be doing anyway). return style.logicalHeight().isAuto(); } static RenderBlock* containingBlockForStretchResolution(const RenderBox& box) { auto isIgnoredAsContainingBlock = [](auto& candidate) { // Flow threads (multicol/paged) are invisible to the DOM; their children should // resolve against the multicol or paged container. if (candidate.isRenderFragmentedFlow()) return true; // Only anonymous boxes are subject to the CSS 2 Section 9.2.1.1 "ignored when // resolving values" rule. RenderView and view-transition pseudos are excluded // because they are not anonymous boxes in the spec sense. if (!candidate.shouldSkipForPercentageResolution()) return false; // Among anonymous boxes, only the implementation-detail kinds are spec-ignored: // block-flow wrappers around inlines that have block siblings, multicol flow // threads, and inline-flow-root wrappers for ruby runs. Anonymous flex items, // grid items, table cells, and ruby bases are CSS-mandated structures that // remain as legitimate containing blocks. auto display = candidate.style().display(); return display == Style::DisplayType::BlockFlow || display == Style::DisplayType::InlineFlowRoot; }; // Walks past anonymous boxes that the CSS 2 Section 9.2.1.1 rule says to ignore // when resolving values that would refer to them, returning the first ancestor // that is a legitimate containing block for value resolution. auto* ancestor = box.containingBlock(); while (ancestor && isIgnoredAsContainingBlock(*ancestor)) ancestor = ancestor->containingBlock(); return ancestor; } bool RenderBox::isBlockSizeResolvableForStretch() const { // css-sizing-4 Section 6.7 defines stretch as behaving like 100%, so stretch // resolution shares the percent rule from CSS 2 Section 9.2.1.1. CheckedPtr containingBlock = containingBlockForStretchResolution(*this); if (!containingBlock) return false; // Orthogonal children's block axis maps to the parent's inline axis, which is always definite. if (isOrthogonal(*this, *containingBlock)) return true; // The containing block has a directly resolvable height (fixed, percentage, etc.) if (containingBlock->hasDefiniteLogicalHeight() || containingBlock->stretchesToViewport()) return true; // hasDefiniteLogicalHeight() is timing-dependent for flex items: it returns false during // "after main axis item sizing" because the overriding cross size hasn't been set yet. // hasDefiniteCrossSizeForFlexItem() answers the same question based purely on style // (container cross size is definite + item will be stretched). // This only applies when the flex item's block axis is the cross axis (row flex). // For column flex, the block axis is the main axis and hasDefiniteLogicalHeight() // already gives the correct answer during that phase. if (containingBlock->isFlexItem()) { if (auto* flexContainer = dynamicDowncast(containingBlock->parent())) return flexContainer->mainAxisIsFlexItemInlineAxis(*containingBlock) && flexContainer->hasDefiniteCrossSizeForFlexItem(*containingBlock); } return false; } bool RenderBox::logicalHeightBehavesAsAuto() const { auto height = style().logicalHeight(); if (height.isAuto() || height.isIntrinsic() || isUnresolveableStretchSize(height)) return true; // A percentage/calc that does not resolve against the containing block behaves as auto. Resolvability // is checked from the containing block's style only (hasDefiniteLogicalHeightForPercentageResolutionFromStyle), so this stays // side-effect-free (no percent-height descendant registration) and independent of transient layout // state - unlike percentageLogicalHeightIsResolvable(). if (!isOutOfFlowPositioned() && height.isPercentOrCalculated()) { CheckedPtr containingBlock = this->containingBlock(); return containingBlock && !containingBlock->hasDefiniteLogicalHeightForPercentageResolutionFromStyle(); } return false; } bool RenderBox::shouldComputeLogicalHeightFromAspectRatio() const { if (shouldIgnoreAspectRatio()) return false; if (shouldComputeLogicalWidthFromAspectRatioAndInsets(*this)) return false; auto h = style().logicalHeight(); return h.isAuto() || h.isIntrinsic() || isUnresolveableStretchSize(h) || (!isOutOfFlowPositioned() && h.isPercentOrCalculated() && !percentageLogicalHeightIsResolvable()); } bool RenderBox::hasFullyConstrainedLogicalHeight() const { // CSS Sizing 3 section 3.2.1: "If the size is being computed in a context where // it is fully constrained (e.g., because both inset properties are specified), the resulting size is definite." return isOutOfFlowPositioned() && style().logicalHeight().isAuto() && !style().logicalTop().isAuto() && !style().logicalBottom().isAuto(); } bool RenderBox::shouldComputeLogicalWidthFromAspectRatio() const { if (shouldIgnoreAspectRatio()) return false; if (isGridItem()) { if (is(*this)) { if (hasStretchedLogicalWidth() && hasStretchedLogicalHeight()) return false; } else if (hasStretchedLogicalWidth(StretchingMode::Explicit)) return false; if (style().logicalWidth().isPercentOrCalculated() && parent()->style().logicalWidth().isFixed()) return false; } auto isResolvablePercentageHeight = [&] { return style().logicalHeight().isPercentOrCalculated() && (isOutOfFlowPositioned() || percentageLogicalHeightIsResolvable()); }; return overridingBorderBoxLogicalHeight() || shouldComputeLogicalWidthFromAspectRatioAndInsets(*this) || style().logicalHeight().isFixed() || isResolvablePercentageHeight() || isResolveableStretchSize(style().logicalHeight()); } LayoutUnit RenderBox::computeLogicalWidthFromAspectRatio() const { ASSERT(preferredAspectRatio()); auto computedValues = computeLogicalHeight(logicalHeight(), logicalTop()); auto logicalHeightforAspectRatio = computedValues.extent; return inlineSizeFromAspectRatio(horizontalBorderAndPaddingExtent(), verticalBorderAndPaddingExtent(), preferredAspectRatio().value_or(0.f), style().boxSizingForAspectRatio(), logicalHeightforAspectRatio, style().aspectRatio(), isRenderReplaced()); } void RenderBox::applyAutomaticContentBasedMinimumSize(LayoutUnit& minLogicalWidth, LayoutUnit& maxLogicalWidth) const { if (!firstChild() || !style().logicalMinWidth().isAuto()) return; // "The automatic minimum size in the ratio-dependent axis of a box with a preferred // aspect ratio that is neither a replaced element nor a scroll container is its // min-content size capped by its maximum size." https://www.w3.org/TR/css-sizing-4/#aspect-ratio-minimum auto [minContentLogicalWidth, maxContentLogicalWidth] = computeIntrinsicKeywordLogicalWidths(); minLogicalWidth = std::max(minLogicalWidth, minContentLogicalWidth); maxLogicalWidth = std::max(maxLogicalWidth, maxContentLogicalWidth); } std::optional RenderBox::preferredAspectRatio() const { if (style().aspectRatio().hasRatio()) return style().logicalAspectRatio(); ASSERT_NOT_REACHED(); return std::nullopt; } FloatSize RenderBox::preferredAspectRatioAsSize() const { if (style().aspectRatio().hasRatio()) return FloatSize::narrowPrecision(style().aspectRatioLogicalWidth().value, style().aspectRatioLogicalHeight().value); return { }; } void RenderBox::applyTransferredMinMaxSizesFromAspectRatio(LayoutUnit& minContentLogicalWidth, LayoutUnit& maxContentLogicalWidth) const { ASSERT(preferredAspectRatio()); auto [transferredMin, transferredMax] = computeMinMaxLogicalWidthFromAspectRatio(); transferredMin = std::max(transferredMin - borderAndPaddingLogicalWidth(), 0_lu); transferredMax = std::max(transferredMax - borderAndPaddingLogicalWidth(), 0_lu); minContentLogicalWidth = std::clamp(minContentLogicalWidth, transferredMin, transferredMax); maxContentLogicalWidth = std::clamp(maxContentLogicalWidth, transferredMin, transferredMax); } std::pair RenderBox::computeMinMaxLogicalWidthFromAspectRatio() const { auto transferredMinSize = LayoutUnit { }; auto transferredMaxSize = LayoutUnit::max(); auto aspectRatio = preferredAspectRatio(); if (!aspectRatio) return { transferredMinSize, transferredMaxSize }; if (style().logicalMinHeight().isSpecified() || style().logicalMinHeight().isStretch()) { if (LayoutUnit blockMinSize = constrainLogicalHeightByMinMax(LayoutUnit(), std::nullopt); blockMinSize > LayoutUnit()) transferredMinSize = inlineSizeFromAspectRatio(borderAndPaddingLogicalWidth(), borderAndPaddingLogicalHeight(), *aspectRatio, style().boxSizingForAspectRatio(), blockMinSize, style().aspectRatio(), isRenderReplaced()); } if (style().logicalMaxHeight().isSpecified() || style().logicalMaxHeight().isStretch()) { if (LayoutUnit blockMaxSize = constrainLogicalHeightByMinMax(LayoutUnit::max(), std::nullopt); blockMaxSize != LayoutUnit::max()) transferredMaxSize = inlineSizeFromAspectRatio(borderAndPaddingLogicalWidth(), borderAndPaddingLogicalHeight(), *aspectRatio, style().boxSizingForAspectRatio(), blockMaxSize, style().aspectRatio(), isRenderReplaced()); } // Spec says the transferred max size should be floored by the transferred min size transferredMaxSize = std::max(transferredMinSize, transferredMaxSize); return { transferredMinSize, transferredMaxSize }; } std::pair RenderBox::computeMinMaxLogicalHeightFromAspectRatio() const { auto transferredMinSize = LayoutUnit { }; auto transferredMaxSize = LayoutUnit::max(); auto aspectRatio = preferredAspectRatio(); if (!aspectRatio) return { transferredMinSize, transferredMaxSize }; if (style().logicalMinWidth().isSpecified() || style().logicalMinWidth().isStretch()) { if (LayoutUnit inlineMinSize = computeLogicalWidthUsing(style().logicalMinWidth(), containingBlockLogicalWidthForContent(), *containingBlock()); inlineMinSize > LayoutUnit()) transferredMinSize = blockSizeFromAspectRatio(borderAndPaddingLogicalWidth(), borderAndPaddingLogicalHeight(), *aspectRatio, style().boxSizingForAspectRatio(), inlineMinSize, style().aspectRatio(), isRenderReplaced()); } if (style().logicalMaxWidth().isSpecified() || style().logicalMaxWidth().isStretch()) { if (LayoutUnit inlineMaxSize = computeLogicalWidthUsing(style().logicalMaxWidth(), containingBlockLogicalWidthForContent(), *containingBlock()); inlineMaxSize != LayoutUnit::max()) transferredMaxSize = blockSizeFromAspectRatio(borderAndPaddingLogicalWidth(), borderAndPaddingLogicalHeight(), *aspectRatio, style().boxSizingForAspectRatio(), inlineMaxSize, style().aspectRatio(), isRenderReplaced()); } // Spec says the transferred max size should be floored by the transferred min size transferredMaxSize = std::max(transferredMinSize, transferredMaxSize); return { transferredMinSize, transferredMaxSize }; } bool RenderBox::hasRelativeDimensions() const { auto& style = this->style(); return style.height().isPercentOrCalculated() || style.width().isPercentOrCalculated() || style.maxHeight().isPercentOrCalculated() || style.maxWidth().isPercentOrCalculated() || style.minHeight().isPercentOrCalculated() || style.minWidth().isPercentOrCalculated() || style.height().isStretch() || style.width().isStretch() || style.maxHeight().isStretch() || style.maxWidth().isStretch() || style.minHeight().isStretch() || style.minWidth().isStretch(); } bool RenderBox::hasRelativeLogicalHeight() const { return style().logicalHeight().isPercentOrCalculated() || style().logicalMinHeight().isPercentOrCalculated() || style().logicalMaxHeight().isPercentOrCalculated() || style().logicalHeight().isStretch() || style().logicalMinHeight().isStretch() || style().logicalMaxHeight().isStretch(); } bool RenderBox::hasRelativeLogicalWidth() const { return style().logicalWidth().isPercentOrCalculated() || style().logicalMinWidth().isPercentOrCalculated() || style().logicalMaxWidth().isPercentOrCalculated() || style().logicalWidth().isStretch() || style().logicalMinWidth().isStretch() || style().logicalMaxWidth().isStretch(); } LayoutUnit RenderBox::offsetFromLogicalTopOfFirstPage() const { auto* layoutState = view().frameView().layoutContext().layoutState(); if ((layoutState && !layoutState->isPaginated()) || (!layoutState && !enclosingFragmentedFlow())) return 0; RenderBlock* containerBlock = containingBlock(); return containerBlock->offsetFromLogicalTopOfFirstPage() + logicalTop(); } LayoutBoxExtent RenderBox::scrollPaddingForViewportRect(const LayoutRect& viewportRect) { return Style::extentForRect(style().scrollPaddingBox(), viewportRect, style().usedZoomForLength()); } LayoutUnit RenderBox::intrinsicLogicalWidth() const { return writingMode().isHorizontal() ? intrinsicSize().width() : intrinsicSize().height(); } bool RenderBox::shouldIgnoreLogicalMinMaxWidthSizes() const { if (!isFlexItem()) return false; if (auto* flexBox = dynamicDowncast(parent())) return flexBox->isComputingFlexBaseSizes() && writingMode().isHorizontal() == flexBox->isHorizontalFlow(); ASSERT_NOT_REACHED(); return false; } bool RenderBox::shouldIgnoreLogicalMinMaxHeightSizes() const { if (!isFlexItem()) return false; if (auto* flexBox = dynamicDowncast(parent())) return flexBox->isComputingFlexBaseSizes() && writingMode().isHorizontal() != flexBox->isHorizontalFlow(); ASSERT_NOT_REACHED(); return false; } std::optional RenderBox::explicitIntrinsicInnerWidth() const { ASSERT(isHorizontalWritingMode() ? shouldApplySizeOrInlineSizeContainment() : shouldApplySizeContainment()); auto& style = this->style(); auto& containIntrinsicWidth = style.containIntrinsicWidth(); if (containIntrinsicWidth.isNone()) return { }; if (containIntrinsicWidth.hasAuto() && isSkippedContentRoot(*this)) { // If auto is specified and the element has a last remembered size and is currently skipping its contents, // its explicit intrinsic inner size in the corresponding axis is the last remembered size in that axis. // https://drafts.csswg.org/css-sizing-4/#intrinsic-size-override if (auto width = isHorizontalWritingMode() ? element()->lastRememberedLogicalWidth() : element()->lastRememberedLogicalHeight()) return width; } if (auto length = containIntrinsicWidth.tryLength()) return LayoutUnit { length->resolveZoom(style.usedZoomForLength()) }; ASSERT(containIntrinsicWidth.isAutoAndNone()); return { }; } std::optional RenderBox::explicitIntrinsicInnerHeight() const { ASSERT(isHorizontalWritingMode() ? shouldApplySizeContainment() : shouldApplySizeOrInlineSizeContainment()); auto& style = this->style(); auto& containIntrinsicHeight = style.containIntrinsicHeight(); if (containIntrinsicHeight.isNone()) return { }; if (containIntrinsicHeight.hasAuto() && isSkippedContentRoot(*this)) { // If auto is specified and the element has a last remembered size and is currently skipping its contents, // its explicit intrinsic inner size in the corresponding axis is the last remembered size in that axis. // https://drafts.csswg.org/css-sizing-4/#intrinsic-size-override if (auto height = isHorizontalWritingMode() ? element()->lastRememberedLogicalHeight() : element()->lastRememberedLogicalWidth()) return height; } if (auto length = containIntrinsicHeight.tryLength()) return LayoutUnit { length->resolveZoom(style.usedZoomForLength()) }; ASSERT(containIntrinsicHeight.isAutoAndNone()); return { }; } // 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. bool RenderBox::requiresLayer() const { return RenderBoxModelObject::requiresLayer() || hasNonVisibleOverflow() || style().specifiesColumns() || style().usedContain().contains(Style::ContainValue::Layout) || !style().usedZIndex().isAuto() || hasRunningAcceleratedAnimations(); } void RenderBox::updateFloatPainterAfterSelfPaintingLayerChange() { ASSERT(isFloating()); ASSERT(!hasLayer() || !layer()->isSelfPaintingLayer()); // Find the ancestor renderer that is supposed to paint this float now that it is not self painting anymore. auto floatingObjectForFloatPainting = [&]() -> FloatingObject* { auto& layoutContext = view().frameView().layoutContext(); if (!layoutContext.isInLayout() || layoutContext.subtreeLayoutRoot() != this) return nullptr; FloatingObject* floatPainter = nullptr; for (auto* ancestor = containingBlock(); ancestor; ancestor = ancestor->containingBlock()) { auto* blockFlow = dynamicDowncast(*ancestor); if (!blockFlow) { ASSERT_NOT_REACHED(); break; } auto* floatingObjects = blockFlow->floatingObjectSet(); if (!floatingObjects) break; auto blockFlowContainsThisFloat = false; for (auto& floatingObject : *floatingObjects) { if (!floatingObject->renderer()) continue; blockFlowContainsThisFloat = floatingObject->renderer() == this; if (blockFlowContainsThisFloat) { floatPainter = floatingObject.get(); if (blockFlow->hasLayer() && blockFlow->layer()->isSelfPaintingLayer()) return floatPainter; break; } } if (!blockFlowContainsThisFloat) break; } // There has to be an ancestor with a floating object assigned to this renderer. ASSERT(floatPainter); return floatPainter; }; if (auto* floatingObject = floatingObjectForFloatPainting()) floatingObject->setPaintsFloat(true); } using ShapeOutsideInfoMap = SingleThreadWeakHashMap>; static ShapeOutsideInfoMap& NODELETE shapeOutsideInfoMap() { static NeverDestroyed staticInfoMap; return staticInfoMap; } ShapeOutsideInfo* RenderBox::shapeOutsideInfo() const { if (!renderBoxHasShapeOutsideInfo()) return nullptr; if (!ShapeOutsideInfo::isEnabledFor(*this)) return nullptr; return shapeOutsideInfoMap().get(*this); } ShapeOutsideInfo& RenderBox::ensureShapeOutsideInfo() { setRenderBoxHasShapeOutsideInfo(true); return *shapeOutsideInfoMap().ensure(*this, [&] { return makeUnique(*this); }).iterator->value; } void RenderBox::removeShapeOutsideInfo() { if (!renderBoxHasShapeOutsideInfo()) return; setRenderBoxHasShapeOutsideInfo(false); shapeOutsideInfoMap().remove(*this); } bool RenderBox::hasAutoHeightOrContainingBlockWithAutoHeight(UpdatePercentageHeightDescendants updatePercentageDescendants) const { auto& logicalHeight = style().logicalHeight(); auto* containingBlock = containingBlockForAutoHeightDetection(logicalHeight); if (updatePercentageDescendants == UpdatePercentageHeightDescendants::Yes && logicalHeight.isPercentOrCalculated() && containingBlock) containingBlock->addPercentHeightDescendant(const_cast(*this)); if (isFlexItem() && downcast(*parent()).canUseFlexItemForPercentageResolution(*this)) return false; if (isGridItem()) { if (auto containingBlockContentLogicalHeight = gridAreaContentLogicalHeight()) return !*containingBlockContentLogicalHeight; } auto isOutOfFlowPositionedWithImplicitHeight = isOutOfFlowPositioned() && !style().logicalTop().isAuto() && !style().logicalBottom().isAuto(); if (logicalHeight.isAuto() && !isOutOfFlowPositionedWithImplicitHeight) return true; // We need the containing block to have a definite block-size in order to resolve the block-size of the descendant, // except when in quirks mode. Flexboxes follow strict behavior even in quirks mode, though. if (!containingBlock || (document().inQuirksMode() && !containingBlock->isFlexibleBoxIncludingDeprecated())) return false; return !containingBlock->hasDefiniteLogicalHeight(); } bool RenderBox::overflowChangesMayAffectLayout() const { if (style().overflowY() != Overflow::Auto && style().overflowX() != Overflow::Auto) return false; if (style().usesLegacyScrollbarStyle()) return true; // FIXME: Bug 273167 #if PLATFORM(IOS_FAMILY) if (!ScrollbarTheme::theme().isMockTheme()) return false; #endif return !ScrollbarTheme::theme().usesOverlayScrollbars(); } } // namespace WebCore