| /* |
| * Copyright (C) 1999 Lars Knoll ([email protected]) |
| * (C) 1999 Antti Koivisto ([email protected]) |
| * (C) 2005 Allan Sandfeld Jensen ([email protected]) |
| * Copyright (C) 2005-2025 Samuel Weinig ([email protected]) |
| * 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 "CSSFontSelector.h" |
| #include "Document.h" |
| #include "DocumentInlines.h" |
| #include "EditingInlines.h" |
| #include "EventHandler.h" |
| #include "FloatQuad.h" |
| #include "FloatRoundedRect.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 "RenderBlockInlines.h" |
| #include "RenderBoxFragmentInfo.h" |
| #include "RenderBoxInlines.h" |
| #include "RenderChildIterator.h" |
| #include "RenderDeprecatedFlexibleBox.h" |
| #include "RenderElementInlines.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 "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 "StylePrimitiveNumericTypes+Evaluation.h" |
| #include "StyleReflection.h" |
| #include "StyleScrollSnapPoints.h" |
| #include "TransformOperationData.h" |
| #include "TransformState.h" |
| #include <algorithm> |
| #include <math.h> |
| #include <wtf/Assertions.h> |
| #include <wtf/RuntimeApplicationChecks.h> |
| #include <wtf/StackStats.h> |
| #include <wtf/TZoneMallocInlines.h> |
| |
| namespace WebCore { |
| |
| WTF_MAKE_TZONE_OR_ISO_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<const RenderBox, LayoutUnit>; |
| static OverrideSizeMap* gOverridingLogicalHeightMap = nullptr; |
| static OverrideSizeMap* gOverridingLogicalWidthMap = nullptr; |
| |
| using OverridingPreferredSizeMap = SingleThreadWeakHashMap<const RenderBox, Style::PreferredSize>; |
| static OverridingPreferredSizeMap* gOverridingLogicalHeightMapForFlexBasisComputation = nullptr; |
| static OverridingPreferredSizeMap* gOverridingLogicalWidthMapForFlexBasisComputation = nullptr; |
| |
| // FIXME: We should store these based on physical direction. |
| using OverrideOptionalSizeMap = SingleThreadWeakHashMap<const RenderBox, RenderBox::GridAreaSize>; |
| 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, RenderStyle&& style, OptionSet<TypeFlag> flags, TypeSpecificFlags typeSpecificFlags) |
| : RenderBoxModelObject(type, element, WTFMove(style), flags | TypeFlag::IsBox, typeSpecificFlags) |
| { |
| ASSERT(isRenderBox()); |
| } |
| |
| RenderBox::RenderBox(Type type, Document& document, RenderStyle&& style, OptionSet<TypeFlag> flags, TypeSpecificFlags typeSpecificFlags) |
| : RenderBoxModelObject(type, document, WTFMove(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().hasSnapPosition()) |
| view().unregisterBoxWithScrollSnapPositions(*this); |
| if (style().containerType() != ContainerType::Normal) |
| view().unregisterContainerQueryBox(*this); |
| if (!style().anchorNames().isNone()) |
| view().unregisterAnchor(*this); |
| if (!style().positionTryFallbacks().isEmpty()) |
| view().unregisterPositionTryBox(*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* outermostBlockContainingFloatingObject(RenderBox& box) |
| { |
| ASSERT(box.isFloating()); |
| RenderBlockFlow* parentBlock = nullptr; |
| for (auto& ancestor : ancestorsOfType<RenderBlockFlow>(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(StyleDifference diff, const RenderStyle& newStyle) |
| { |
| s_hadNonVisibleOverflow = hasNonVisibleOverflow(); |
| |
| const RenderStyle* 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 >= StyleDifference::Repaint && (isDocumentElementRenderer() || isBody())) { |
| view().repaintRootContents(); |
| if (oldStyle->hasEntirelyFixedBackground() != newStyle.hasEntirelyFixedBackground()) |
| 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 == StyleDifference::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. |
| setNeedsLayoutAndPreferredWidthsUpdate(); |
| if (CheckedPtr flexContainer = dynamicDowncast<RenderFlexibleBox>(parent())) { |
| flexContainer->clearCachedFlexItemIntrinsicContentLogicalHeight(*this); |
| flexContainer->clearCachedMainSizeForFlexItem(*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.hasSnapPosition(); |
| if (boxContributesSnapPositions || (oldStyle && oldStyle->hasSnapPosition())) { |
| if (boxContributesSnapPositions) |
| view().registerBoxWithScrollSnapPositions(*this); |
| else |
| view().unregisterBoxWithScrollSnapPositions(*this); |
| } |
| |
| if (newStyle.containerType() != ContainerType::Normal) |
| view().registerContainerQueryBox(*this); |
| else if (oldStyle && oldStyle->containerType() != ContainerType::Normal) |
| view().unregisterContainerQueryBox(*this); |
| |
| if (!newStyle.positionTryFallbacks().isEmpty() && newStyle.hasOutOfFlowPosition()) |
| view().registerPositionTryBox(*this); |
| else if (oldStyle && !oldStyle->positionTryFallbacks().isEmpty() && 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(StyleDifference diff, const RenderStyle* 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 RenderStyle& 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 (auto* 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 (auto* 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(newStyle.scrollbarWidth()); |
| else if (auto* scrollableArea = layer()->scrollableArea()) |
| scrollableArea->scrollbarWidthChanged(newStyle.scrollbarWidth()); |
| } |
| |
| #if ENABLE(DARK_MODE_CSS) |
| if (layer() && oldStyle && oldStyle->colorScheme() != newStyle.colorScheme()) { |
| if (auto* scrollableArea = layer()->scrollableArea()) |
| scrollableArea->invalidateScrollbars(); |
| } |
| #endif |
| |
| // Our opaqueness might have changed without triggering layout. |
| if (diff >= StyleDifference::Repaint && diff <= StyleDifference::RepaintLayer) |
| invalidateAncestorBackgroundObscurationStatus(); |
| |
| bool isBodyRenderer = isBody(); |
| |
| if (isDocElementRenderer || isBodyRenderer) { |
| view().frameView().recalculateScrollbarOverlayStyle(); |
| |
| if (diff != StyleDifference::Equal) |
| view().compositor().rootOrBodyStyleChanged(*this, oldStyle); |
| } |
| |
| if ((oldStyle && !oldStyle->shapeOutside().isNone()) || !style().shapeOutside().isNone()) |
| updateShapeOutsideInfoAfterStyleChange(style(), oldStyle); |
| 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().isDisplayFlexibleBoxIncludingDeprecatedOrGridBox()) |
| 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->setNeedsLayoutAndPreferredWidthsUpdate(); |
| } |
| } |
| } |
| |
| static bool hasEquivalentGridPositioningStyle(const RenderStyle& style, const RenderStyle& 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 RenderStyle& style, const RenderStyle* oldStyle) |
| { |
| if (!oldStyle) |
| return; |
| CheckedPtr parentGrid = dynamicDowncast<RenderGrid>(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 RenderStyle& style, const RenderStyle* oldStyle) |
| { |
| Style::ShapeOutside shapeOutside = style.shapeOutside(); |
| Style::ShapeOutside oldShapeOutside = oldStyle ? oldStyle->shapeOutside() : RenderStyle::initialShapeOutside(); |
| |
| Style::ShapeMargin shapeMargin = style.shapeMargin(); |
| Style::ShapeMargin oldShapeMargin = oldStyle ? oldStyle->shapeMargin() : RenderStyle::initialShapeMargin(); |
| |
| Style::ShapeImageThreshold shapeImageThreshold = style.shapeImageThreshold(); |
| Style::ShapeImageThreshold oldShapeImageThreshold = oldStyle ? oldStyle->shapeImageThreshold() : RenderStyle::initialShapeImageThreshold(); |
| |
| // FIXME: A future optimization would do a deep comparison for equality. (bug 100811) |
| if (shapeOutside == oldShapeOutside && shapeMargin == oldShapeMargin && shapeImageThreshold == oldShapeImageThreshold) |
| return; |
| |
| if (shapeOutside.isNone()) |
| removeShapeOutsideInfo(); |
| else |
| ensureShapeOutsideInfo().markShapeAsDirty(); |
| |
| if (!shapeOutside.isNone() || shapeOutside != oldShapeOutside) |
| markShapeOutsideDependentsForLayout(); |
| } |
| |
| void RenderBox::updateFromStyle() |
| { |
| RenderBoxModelObject::updateFromStyle(); |
| |
| const RenderStyle& 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.isFloating()); |
| |
| // We also handle <body> and <html>, 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 <html>. |
| // (2) We are the primary <body> (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. |
| auto& documentElement = *document().documentElement(); |
| auto& documentElementRenderer = *documentElement.renderer(); |
| if (is<HTMLHtmlElement>(documentElement) |
| && document().body() == element() |
| && documentElementRenderer.effectiveOverflowX() == Overflow::Visible |
| && !styleToUse.usedContain() |
| && !documentElementRenderer.style().usedContain()) { |
| 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()); |
| } |
| |
| bool RenderBox::computeHasTransformRelatedProperty(const RenderStyle& 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().preserves3D(); |
| } |
| |
| 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<RenderElement>(*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<LayoutUnit>(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 |
| { |
| auto* scrollableArea = layer() ? layer()->scrollableArea() : nullptr; |
| return (hasNonVisibleOverflow() && scrollableArea) ? scrollableArea->scrollPosition().x() : 0; |
| } |
| |
| int RenderBox::scrollTop() const |
| { |
| auto* scrollableArea = layer() ? layer()->scrollableArea() : nullptr; |
| return (hasNonVisibleOverflow() && scrollableArea) ? scrollableArea->scrollPosition().y() : 0; |
| } |
| |
| bool RenderBox::shouldResetLogicalHeightBeforeLayout() const |
| { |
| auto* flexBoxParent = dynamicDowncast<RenderFlexibleBox>(parent()); |
| return flexBoxParent && flexBoxParent->shouldResetFlexItemLogicalHeightBeforeLayout(); |
| } |
| |
| void RenderBox::resetLogicalHeightBeforeLayoutIfNeeded() |
| { |
| if (shouldResetLogicalHeightBeforeLayout()) |
| setLogicalHeight(0_lu); |
| } |
| |
| static void setupWheelEventMonitor(RenderLayerScrollableArea& scrollableArea) |
| { |
| Page& 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; |
| auto* scrollableArea = layer()->scrollableArea(); |
| ASSERT(scrollableArea); |
| setupWheelEventMonitor(*scrollableArea); |
| scrollableArea->scrollToXPosition(newLeft, options); |
| } |
| |
| void RenderBox::setScrollTop(int newTop, const ScrollPositionChangeOptions& options) |
| { |
| if (!hasPotentiallyScrollableOverflow() || !layer()) |
| return; |
| auto* scrollableArea = layer()->scrollableArea(); |
| ASSERT(scrollableArea); |
| setupWheelEventMonitor(*scrollableArea); |
| scrollableArea->scrollToYPosition(newTop, options); |
| } |
| |
| void RenderBox::setScrollPosition(const ScrollPosition& position, const ScrollPositionChangeOptions& options) |
| { |
| if (!hasPotentiallyScrollableOverflow() || !layer()) |
| return; |
| auto* scrollableArea = layer()->scrollableArea(); |
| ASSERT(scrollableArea); |
| setupWheelEventMonitor(*scrollableArea); |
| scrollableArea->setScrollPosition(position, options); |
| } |
| |
| void RenderBox::boundingRects(Vector<LayoutRect>& rects, const LayoutPoint& accumulatedOffset) const |
| { |
| rects.append({ accumulatedOffset, size() }); |
| } |
| |
| void RenderBox::absoluteQuads(Vector<FloatQuad>& 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, UseTransforms, wasFixed)); |
| } |
| |
| void RenderBox::applyTransform(TransformationMatrix& t, const RenderStyle& style, const FloatRect& boundingBox, OptionSet<RenderStyle::TransformOperationOption> options) const |
| { |
| style.applyTransform(t, 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 RenderStyle& styleToUse = style(); |
| ASSERT(styleToUse.hasAspectRatio() || isRenderReplacedWithIntrinsicRatio()); |
| 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 (minimumSizeType == MinimumSizeIsAutomaticContentBased::Yes) |
| computedMinSize = std::min(computedMinSize, computedMaxSize); |
| } |
| |
| 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; |
| LayoutUnit computedMinWidth; |
| if (logicalMinWidth.isAuto() && shouldComputeLogicalWidthFromAspectRatio() && (styleToUse.logicalWidth().isAuto() || styleToUse.logicalWidth().isMinContent() || styleToUse.logicalWidth().isMaxContent()) && !is<RenderReplaced>(*this) && effectiveOverflowInlineDirection() == Overflow::Visible) { |
| // The automatic minimum size in the ratio-dependent axis is its min-content size. See https://www.w3.org/TR/css-sizing-4/#aspect-ratio-minimum |
| logicalMinWidth = CSS::Keyword::MinContent { }; |
| minimumSizeType = MinimumSizeIsAutomaticContentBased::Yes; |
| } |
| computedMinWidth = computeLogicalWidthUsing(logicalMinWidth, availableWidth, cb); |
| |
| if (styleToUse.hasAspectRatio()) |
| constrainLogicalMinMaxSizesByAspectRatio(computedMinWidth, computedMaxWidth, logicalWidth, minimumSizeType, ConstrainDimension::Width); |
| |
| logicalWidth = std::min(logicalWidth, computedMaxWidth); |
| return std::max(logicalWidth, computedMinWidth); |
| } |
| |
| LayoutUnit RenderBox::constrainLogicalHeightByMinMax(LayoutUnit logicalHeight, std::optional<LayoutUnit> intrinsicContentHeight) const |
| { |
| auto& styleToUse = style(); |
| std::optional<LayoutUnit> computedLogicalMaxHeight; |
| if (!styleToUse.logicalMaxHeight().isNone()) |
| computedLogicalMaxHeight = computeLogicalHeightUsing(styleToUse.logicalMaxHeight(), intrinsicContentHeight); |
| |
| auto logicalMinHeight = styleToUse.logicalMinHeight(); |
| auto minimumSizeType = MinimumSizeIsAutomaticContentBased::No; |
| if (logicalMinHeight.isAuto() && shouldComputeLogicalHeightFromAspectRatio() && intrinsicContentHeight && !is<RenderReplaced>(*this) && effectiveOverflowBlockDirection() == Overflow::Visible) { |
| 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 }; |
| minimumSizeType = MinimumSizeIsAutomaticContentBased::Yes; |
| } |
| if (logicalMinHeight.isMinContent() || logicalMinHeight.isMaxContent()) |
| logicalMinHeight = CSS::Keyword::Auto { }; |
| auto computedLogicalMinHeight = computeLogicalHeightUsing(logicalMinHeight, intrinsicContentHeight); |
| auto maxHeight = computedLogicalMaxHeight.value_or(LayoutUnit::max()); |
| auto minHeight = computedLogicalMinHeight.value_or(LayoutUnit()); |
| if (styleToUse.hasAspectRatio()) |
| constrainLogicalMinMaxSizesByAspectRatio(minHeight, maxHeight, logicalHeight, minimumSizeType, ConstrainDimension::Height); |
| logicalHeight = std::min(logicalHeight, maxHeight); |
| return std::max(logicalHeight, minHeight); |
| } |
| |
| LayoutUnit RenderBox::constrainContentBoxLogicalHeightByMinMax(LayoutUnit logicalHeight, std::optional<LayoutUnit> intrinsicContentHeight) const |
| { |
| // If the min/max height and logical height are both percentages we take advantage of already knowing the current resolved percentage height |
| // to avoid recursing up through our containing blocks again to determine it. |
| 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())); |
| } |
| |
| void RenderBox::addFocusRingRects(Vector<LayoutRect>& rects, const LayoutPoint& additionalOffset, const RenderLayerModelObject*) const |
| { |
| if (!size().isEmpty()) |
| rects.append(LayoutRect(additionalOffset, size())); |
| } |
| |
| int RenderBox::reflectionOffset() const |
| { |
| if (!style().boxReflect()) |
| return 0; |
| if (style().boxReflect()->direction() == ReflectionDirection::Left || style().boxReflect()->direction() == ReflectionDirection::Right) |
| return valueForLength(style().boxReflect()->offset(), borderBoxRect().width()); |
| return valueForLength(style().boxReflect()->offset(), borderBoxRect().height()); |
| } |
| |
| LayoutRect RenderBox::reflectedRect(const LayoutRect& r) const |
| { |
| if (!style().boxReflect()) |
| return LayoutRect(); |
| |
| LayoutRect box = borderBoxRect(); |
| LayoutRect result = r; |
| switch (style().boxReflect()->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 |
| { |
| auto* scrollableArea = layer() ? layer()->scrollableArea() : nullptr; |
| if (!scrollableArea) |
| return 0; |
| return includeVerticalScrollbarSize() ? scrollableArea->verticalScrollbarWidth(OverlayScrollbarSizeRelevancy::IgnoreOverlayScrollbarSize, isHorizontalWritingMode()) : 0; |
| } |
| |
| int RenderBox::horizontalScrollbarHeight() const |
| { |
| auto* 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) |
| { |
| auto* 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 (auto* 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() != 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<RenderBox>(*renderer); box && box->canAutoscroll()) |
| break; |
| if (is<RenderView>(*renderer) && renderer->document().ownerElement()) |
| renderer = renderer->document().ownerElement()->renderer(); |
| else |
| renderer = renderer->parent(); |
| } |
| |
| return dynamicDowncast<RenderBox>(renderer); |
| } |
| |
| void RenderBox::panScroll(const IntPoint& source) |
| { |
| if (auto* 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::shouldInvalidatePreferredWidths() const |
| { |
| return style().paddingStart().isPercentOrCalculated() || style().paddingEnd().isPercentOrCalculated() || (style().hasAspectRatio() && (hasRelativeLogicalHeight() || (isFlexItem() && hasStretchedLogicalHeight()))); |
| } |
| |
| ScrollPosition RenderBox::scrollPosition() const |
| { |
| if (!hasPotentiallyScrollableOverflow()) |
| return { 0, 0 }; |
| |
| ASSERT(hasLayer()); |
| auto* scrollableArea = layer()->scrollableArea(); |
| if (!scrollableArea) |
| return { 0, 0 }; |
| |
| return 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(VisibleRectContextOption::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(VisibleRectContextOption::ApplyCompositedClips) && usesCompositedScrolling()) |
| || (!context.options.contains(VisibleRectContextOption::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 scrollMargin = context.scrollMargin.value(); |
| |
| auto scrollMarginEdges = LayoutBoxExtent { |
| valueForLength(scrollMargin.top(), clipRect.height()), |
| valueForLength(scrollMargin.right(), clipRect.width()), |
| valueForLength(scrollMargin.bottom(), clipRect.height()), |
| valueForLength(scrollMargin.left(), clipRect.width()) |
| }; |
| |
| clipRect.expand(scrollMarginEdges); |
| } |
| |
| bool intersects; |
| if (context.options.contains(VisibleRectContextOption::UseEdgeInclusiveIntersection)) |
| intersects = rects.edgeInclusiveIntersect(clipRect); |
| else |
| intersects = rects.intersect(clipRect); |
| |
| flipForWritingMode(rects); |
| return intersects; |
| } |
| |
| LayoutUnit RenderBox::minPreferredLogicalWidth() const |
| { |
| if (needsPreferredLogicalWidthsUpdate()) { |
| SetLayoutNeededForbiddenScope layoutForbiddenScope(*this); |
| const_cast<RenderBox&>(*this).computePreferredLogicalWidths(); |
| } |
| return m_minPreferredLogicalWidth; |
| } |
| |
| LayoutUnit RenderBox::maxPreferredLogicalWidth() const |
| { |
| if (needsPreferredLogicalWidthsUpdate()) { |
| SetLayoutNeededForbiddenScope layoutForbiddenScope(*this); |
| const_cast<RenderBox&>(*this).computePreferredLogicalWidths(); |
| } |
| return m_maxPreferredLogicalWidth; |
| } |
| |
| 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<LayoutUnit> RenderBox::overridingBorderBoxLogicalWidth() const |
| { |
| if (!gOverridingLogicalWidthMap) |
| return { }; |
| if (auto result = gOverridingLogicalWidthMap->find(*this); result != gOverridingLogicalWidthMap->end()) |
| return result->value; |
| return { }; |
| } |
| |
| std::optional<LayoutUnit> RenderBox::overridingBorderBoxLogicalHeight() const |
| { |
| if (!gOverridingLogicalHeightMap) |
| return { }; |
| if (auto result = gOverridingLogicalHeightMap->find(*this); result != gOverridingLogicalHeightMap->end()) |
| return result->value; |
| return { }; |
| } |
| |
| std::optional<RenderBox::GridAreaSize> RenderBox::gridAreaContentWidth(WritingMode writingMode) const |
| { |
| if (writingMode.isHorizontal()) |
| return gridAreaContentLogicalWidth(); |
| return gridAreaContentLogicalHeight(); |
| } |
| |
| std::optional<RenderBox::GridAreaSize> RenderBox::gridAreaContentHeight(WritingMode writingMode) const |
| { |
| if (writingMode.isHorizontal()) |
| return gridAreaContentLogicalHeight(); |
| return gridAreaContentLogicalWidth(); |
| } |
| |
| std::optional<RenderBox::GridAreaSize> RenderBox::gridAreaContentLogicalWidth() const |
| { |
| if (!gGridAreaContentLogicalWidthMap) |
| return { }; |
| if (auto result = gGridAreaContentLogicalWidthMap->find(*this); result != gGridAreaContentLogicalWidthMap->end()) |
| return result->value; |
| return { }; |
| } |
| |
| std::optional<RenderBox::GridAreaSize> 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<Style::PreferredSize> 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<Style::PreferredSize> 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(MarginTrimType newTrimmedMargin) |
| { |
| auto& rareData = ensureRareData(); |
| rareData.trimmedMargins = rareData.trimmedMargins | newTrimmedMargin; |
| } |
| |
| void RenderBox::clearTrimmedMarginsMarkings() |
| { |
| ASSERT(hasRareData()); |
| ensureRareData().trimmedMargins = { }; |
| } |
| |
| bool RenderBox::hasTrimmedMargin(std::optional<MarginTrimType> 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<CSS::Nonnegative, float>& logicalWidth) const |
| { |
| auto width = LayoutUnit { logicalWidth.value }; |
| 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<CSS::Nonnegative, float>& logicalWidth) const |
| { |
| auto width = LayoutUnit { logicalWidth.value }; |
| 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<LayoutUnit> 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<RenderSVGResourceClipper>(element.renderer())) |
| return clipper->hitTestClipContent( FloatRect { borderBoxRect() }, hitTestLocationInLocalCoordinates); |
| CheckedRef clipper = downcast<LegacyRenderSVGResourceClipper>(*element.renderer()); |
| return clipper->hitTestClipContent( FloatRect { borderBoxRect() }, FloatPoint { hitTestLocationInLocalCoordinates }); |
| }; |
| |
| return WTF::switchOn(style().clipPath(), |
| [&](const Style::BasicShapePath& clipPath) { |
| auto& shape = clipPath.shape(); |
| auto path = Style::path(shape, referenceBoxRect(clipPath.referenceBox())); |
| return path.contains(hitTestLocationInLocalCoordinates, Style::windRule(shape)); |
| }, |
| [&](const Style::ReferencePath& clipPath) { |
| RefPtr element = document().getElementById(clipPath.fragment()); |
| return !is<SVGClipPathElement>(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().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 == HitTestForeground && 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(protectedNodeForHitTest().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 RenderStyle& style = this->style(); |
| |
| if (!style.hasBackground() || !style.hasBorder() || !style.hasBorderRadius() || borderImageIsLoadedAndCanBeRendered()) |
| return BleedAvoidance::None; |
| |
| if (!theme().mayNeedBleedAvoidance(style)) |
| return BleedAvoidance::None; |
| |
| AffineTransform ctm = context.getCTM(); |
| FloatSize contextScaling(static_cast<float>(ctm.xScale()), static_cast<float>(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 (auto* 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 (auto* 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 (auto* control = ensureControlPartForBorderOnly()) |
| paintCSSBorder = theme().paint(*this, *control, paintInfo, paintRect); |
| else |
| paintCSSBorder = theme().paintBorderOnly(*this, paintInfo); |
| } |
| |
| if (paintCSSBorder && style().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().visitedDependentColorWithColorFilter(CSSPropertyBackgroundColor); |
| if (backgroundColor.isVisible()) { |
| paintedExtent = backgroundRect; |
| return true; |
| } |
| |
| auto& layers = style().backgroundLayers(); |
| if (!layers.image() || layers.next()) { |
| paintedExtent = backgroundRect; |
| return true; |
| } |
| |
| auto geometry = BackgroundPainter::calculateBackgroundImageGeometry(*this, nullptr, layers, paintOffset, backgroundRect); |
| paintedExtent = geometry.destinationRect; |
| return !geometry.hasNonLocalGeometry; |
| } |
| |
| bool RenderBox::backgroundIsKnownToBeOpaqueInRect(const LayoutRect& localRect) const |
| { |
| if (!BackgroundPainter::paintsOwnBackground(*this)) |
| return false; |
| |
| Color backgroundColor = style().visitedDependentColorWithColorFilter(CSSPropertyBackgroundColor); |
| 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().hasBorderRadius()) |
| return false; |
| |
| // FIXME: The background color clip is defined by the last layer. |
| if (style().backgroundLayers().next()) |
| return false; |
| LayoutRect backgroundRect; |
| switch (style().backgroundClip()) { |
| 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 RenderStyle& 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 (RenderLayer* childLayer = childBox.layer()) { |
| if (childLayer->isComposited()) |
| return false; |
| // FIXME: Deal with z-index. |
| if (!childStyle.hasAutoUsedZIndex()) |
| 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<RenderBox>(*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 (childBox.foregroundIsKnownToBeOpaqueInRect(childLocalRect, maxDepthToTest - 1)) |
| return true; |
| } |
| return false; |
| } |
| |
| bool RenderBox::computeBackgroundIsKnownToBeObscured(const LayoutPoint& paintOffset) |
| { |
| // 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 (auto* scrollableArea = layer() ? layer()->scrollableArea() : nullptr) { |
| if (scrollableArea->scrollingMayRevealBackground()) |
| return false; |
| } |
| return foregroundIsKnownToBeOpaqueInRect(backgroundRect, backgroundObscurationTestMaxDepth); |
| } |
| |
| bool RenderBox::backgroundHasOpaqueTopLayer() const |
| { |
| auto& fillLayer = style().backgroundLayers(); |
| if (fillLayer.clip() != FillBox::BorderBox) |
| return false; |
| |
| // Clipped with local scrolling |
| if (hasNonVisibleOverflow() && fillLayer.attachment() == FillAttachment::LocalBackground) |
| return false; |
| |
| if (fillLayer.hasOpaqueImage(*this) && fillLayer.hasRepeatXY() && fillLayer.image()->canRender(this, style().usedZoom())) |
| return true; |
| |
| // If there is only one layer and no image, check whether the background color is opaque. |
| if (!fillLayer.next() && !fillLayer.hasImage()) { |
| Color bgColor = style().visitedDependentColorWithColorFilter(CSSPropertyBackgroundColor); |
| 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::ReferencePath>(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 (auto* maskBorder = style().maskBorder().image()) |
| allMaskImagesLoaded &= maskBorder->isLoaded(this); |
| |
| allMaskImagesLoaded &= style().maskLayers().imagesAreLoaded(this); |
| |
| paintInfo.context().setCompositeOperation(CompositeOperator::DestinationIn); |
| paintInfo.context().beginTransparencyLayer(1); |
| compositeOp = CompositeOperator::SourceOver; |
| } |
| |
| if (allMaskImagesLoaded) { |
| BackgroundPainter { *this, paintInfo }.paintFillLayers(Color(), style().maskLayers(), paintRect, BleedAvoidance::None, compositeOp); |
| BorderPainter { *this, paintInfo }.paintNinePieceImage(paintRect, style(), style().maskBorder(), compositeOp); |
| } |
| |
| if (pushTransparencyLayer) |
| paintInfo.context().endTransparencyLayer(); |
| } |
| |
| LayoutRect RenderBox::maskClipRect(const LayoutPoint& paintOffset) |
| { |
| const NinePieceImage& maskBorder = style().maskBorder(); |
| if (maskBorder.image()) { |
| LayoutRect borderImageRect = borderBoxRect(); |
| |
| // Apply outsets to the border box. |
| borderImageRect.expand(style().maskBorderOutsets()); |
| return borderImageRect; |
| } |
| |
| LayoutRect result; |
| LayoutRect borderBox = borderBoxRect(); |
| for (auto* maskLayer = &style().maskLayers(); maskLayer; maskLayer = maskLayer->next()) { |
| if (maskLayer->image()) { |
| // Masks should never have fixed attachment, so it's OK for paintContainer to be null. |
| result.unite(BackgroundPainter::calculateBackgroundImageGeometry(*this, nullptr, *maskLayer, paintOffset, borderBox).destinationRect); |
| } |
| } |
| return result; |
| } |
| |
| static StyleImage* findLayerUsedImage(WrappedImagePtr image, const FillLayer& layers) |
| { |
| for (auto* layer = &layers; layer; layer = layer->next()) { |
| if (layer->image() && image == layer->image()->data()) |
| return layer->image(); |
| } |
| return nullptr; |
| } |
| |
| void RenderBox::imageChanged(WrappedImagePtr image, const IntRect*) |
| { |
| if ((style().borderImage().image() && style().borderImage().image()->data() == image) || |
| (style().maskBorder().image() && style().maskBorder().image()->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(), true); |
| if (!didFullRepaint) |
| didFullRepaint = repaintLayerRectsForImage(image, style.maskLayers(), false); |
| }; |
| |
| repaintForBackgroundAndMask(style()); |
| |
| if (auto* firstLineStyle = style().getCachedPseudoStyle({ PseudoId::FirstLine })) |
| repaintForBackgroundAndMask(*firstLineStyle); |
| |
| if (!isComposited()) |
| return; |
| |
| if (layer()->hasCompositedMask() && findLayerUsedImage(image, style().maskLayers())) |
| layer()->contentChanged(ContentChangeType::MaskImage); |
| |
| if (auto* styleImage = findLayerUsedImage(image, style().backgroundLayers())) { |
| layer()->contentChanged(ContentChangeType::BackgroundIImage); |
| incrementVisuallyNonEmptyPixelCountIfNeeded(flooredIntSize(styleImage->imageSize(this, style().usedZoom()))); |
| } |
| } |
| |
| void RenderBox::incrementVisuallyNonEmptyPixelCountIfNeeded(const IntSize& size) |
| { |
| if (didContibuteToVisuallyNonEmptyPixelCount()) |
| return; |
| |
| view().frameView().incrementVisuallyNonEmptyPixelCount(size); |
| setDidContibuteToVisuallyNonEmptyPixelCount(); |
| } |
| |
| bool RenderBox::repaintLayerRectsForImage(WrappedImagePtr image, const FillLayer& layers, bool drawingBackground) |
| { |
| LayoutRect rendererRect; |
| RenderBox* layerRenderer = nullptr; |
| |
| for (auto* layer = &layers; layer; layer = layer->next()) { |
| if (layer->image() && image == layer->image()->data() && (layer->image()->isLoaded(this) || layer->image()->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<RenderView>(*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<RenderBox>(document().documentElement()->renderer()); |
| auto rendererLayer = documentElementRenderer->layer(); |
| if (rendererLayer && rendererLayer->isComposited()) |
| layerRenderer = documentElementRenderer; |
| } else { |
| layerRenderer = this; |
| rendererRect = borderBoxRect(); |
| } |
| } |
| // FIXME: Figure out how to pass absolute position to calculateBackgroundImageGeometry (for pixel snapping) |
| auto geometry = BackgroundPainter::calculateBackgroundImageGeometry(*layerRenderer, nullptr, *layer, 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().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 (auto* 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->value }; |
| 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->value }, 0_lu); |
| |
| if (auto clipTop = rect.value->top().tryLength()) { |
| auto c = LayoutUnit { clipTop->value }; |
| clipRect.move(0_lu, c); |
| clipRect.contract(0_lu, c); |
| } |
| |
| if (auto clipBottom = rect.value->bottom().tryLength()) |
| clipRect.contract(0_lu, height() - LayoutUnit { clipBottom->value }); |
| |
| return clipRect; |
| } |
| ); |
| } |
| |
| LayoutUnit RenderBox::shrinkLogicalWidthToAvoidFloats(LayoutUnit childMarginStart, LayoutUnit childMarginEnd, const RenderBlock& containingBlock) const |
| { |
| LayoutUnit logicalTopPosition = logicalTop(); |
| LayoutUnit logicalHeight = containingBlock.logicalHeightForChild(*this); |
| LayoutUnit result = containingBlock.availableLogicalWidthForLine(logicalTopPosition, logicalHeight) - 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, logicalHeight); |
| 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, logicalHeight); |
| if (endOffset > endContentSideWithMargin) |
| result += childMarginEnd; |
| else |
| result += endOffset - endContentSide; |
| } |
| |
| return result; |
| } |
| |
| LayoutUnit RenderBox::containingBlockLogicalWidthForContent() const |
| { |
| if (isOutOfFlowPositioned()) { |
| PositionedLayoutConstraints constraints(*this, LogicalBoxAxis::Inline); |
| return constraints.containingInlineSize(); |
| } |
| |
| if (isGridItem()) { |
| if (auto gridAreaContentLogicalWidth = this->gridAreaContentLogicalWidth()) { |
| ASSERT(is<RenderGrid>(containingBlock())); |
| return gridAreaContentLogicalWidth->value_or(0_lu); |
| } |
| } |
| |
| if (auto* containingBlock = this->containingBlock()) |
| return containingBlock->contentBoxLogicalWidth(); |
| |
| ASSERT_NOT_REACHED(); |
| return 0_lu; |
| } |
| |
| 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->value }); |
| } |
| |
| LayoutUnit fillFallbackExtent = containingBlockStyle.writingMode().isHorizontal() |
| ? view().frameView().layoutSize().height() |
| : view().frameView().layoutSize().width(); |
| LayoutUnit fillAvailableExtent = containingBlock->availableLogicalHeight(AvailableLogicalHeightType::ExcludeMarginBorderPadding); |
| view().addPercentHeightDescendant(const_cast<RenderBox&>(*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<MapCoordinatesMode> 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(IsFixed); |
| else if (mode.contains(IsFixed) && canContainFixedPositionObjects()) |
| mode.remove(IsFixed); |
| |
| if (wasFixed) |
| *wasFixed = mode.contains(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(IgnoreStickyOffsets) && isStickilyPositioned()) |
| containerOffset -= stickyPositionOffset(); |
| |
| pushOntoTransformState(transformState, mode, ancestorContainer, container, containerOffset, containerSkipped); |
| if (containerSkipped) |
| return; |
| |
| mode.remove(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<MapCoordinatesMode> mode, TransformState& transformState) const |
| { |
| bool isFixedPos = isFixedPositioned(); |
| if (isFixedPos) |
| mode.add(IsFixed); |
| else if (mode.contains(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(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<RenderFragmentContainer>(container)); |
| |
| LayoutSize offset; |
| if (isInFlowPositioned()) |
| offset += offsetForInFlowPosition(); |
| |
| if (!isInline() || isBlockLevelReplacedOrAtomicInline()) |
| offset += topLeftLocationOffset(); |
| |
| if (auto* boxContainer = dynamicDowncast<RenderBox>(container)) |
| offset -= toLayoutSize(boxContainer->scrollPosition()); |
| |
| if (isAbsolutelyPositioned() && container.isInFlowPositioned()) { |
| if (auto* inlineContainer = dynamicDowncast<RenderInline>(container)) |
| offset += inlineContainer->offsetForInFlowPositionedInline(this); |
| } |
| |
| if (offsetDependsOnPoint) |
| *offsetDependsOnPoint |= is<RenderFragmentedFlow>(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<RepaintRects> |
| { |
| // 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 RenderStyle& 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(VisibleRectContextOption::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<RenderReplaced>() is a fast bit check, is<RenderWidget>() is a virtual function call. |
| if (is<RenderReplaced>(this) && is<RenderWidget>(this)) { |
| LayoutSize flooredLocationOffset = flooredIntSize(locationOffset); |
| adjustedRects.expand(locationOffset - flooredLocationOffset); |
| locationOffset = flooredLocationOffset; |
| context.descendantNeedsEnclosingIntRect = true; |
| } else if (auto* columnFlow = dynamicDowncast<RenderMultiColumnFlow>(*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<RenderInline>(*localContainer)) { |
| auto offsetForInFlowPosition = downcast<RenderInline>(*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<RenderLayerModelObject>(*localContainer).applyCachedClipAndScrollPosition(adjustedRects, container, context); |
| if (isEmpty) { |
| if (context.options.contains(VisibleRectContextOption::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.m_extent); |
| setLogicalLeft(computedValues.m_position); |
| setMarginStart(computedValues.m_margins.m_start); |
| setMarginEnd(computedValues.m_margins.m_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 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.m_extent = logicalWidth(); |
| computedValues.m_position = logicalLeft(); |
| computedValues.m_margins.m_start = marginStart(); |
| computedValues.m_margins.m_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 |
| computePositionedLogicalWidth(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.m_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<RenderReplaced>(*this) && (!inVerticalBox || !stretching); |
| if (!isReplaced) |
| return false; |
| return !isGridItem() || !hasStretchedLogicalWidth(); |
| }(); |
| |
| auto usedLogicalWidthLength = [&] -> Style::PreferredSize { |
| if (auto overridingLogicalWidthLength = overridingLogicalWidthForFlexBasisComputation()) |
| return *overridingLogicalWidthLength; |
| if (treatAsReplaced) |
| return Style::PreferredSize::Fixed { computeReplacedLogicalWidth() }; |
| return style().logicalWidth(); |
| }(); |
| |
| auto containerLogicalWidth = std::max(0_lu, containingBlockLogicalWidthForContent()); |
| auto& styleToUse = style(); |
| if (isInline() && is<RenderReplaced>(*this)) { |
| // just calculate margins |
| computedValues.m_margins.m_start = Style::evaluateMinimum(styleToUse.marginStart(), containerLogicalWidth); |
| computedValues.m_margins.m_end = Style::evaluateMinimum(styleToUse.marginEnd(), containerLogicalWidth); |
| if (treatAsReplaced) |
| computedValues.m_extent = std::max(LayoutUnit(Style::evaluate(usedLogicalWidthLength, 0.0f) + borderAndPaddingLogicalWidth()), minPreferredLogicalWidth()); |
| return; |
| } |
| |
| auto& containingBlock = *this->containingBlock(); |
| bool hasPerpendicularContainingBlock = containingBlock.isHorizontalWritingMode() != isHorizontalWritingMode(); |
| |
| // Width calculations |
| auto logicalWidth = [&] { |
| if (auto overridingLogicalWidth = this->overridingBorderBoxLogicalWidth()) |
| return *overridingLogicalWidth; |
| if (treatAsReplaced) |
| return LayoutUnit { Style::evaluate(usedLogicalWidthLength, 0.0f) } + borderAndPaddingLogicalWidth(); |
| if (shouldComputeLogicalWidthFromAspectRatio() && style().logicalWidth().isAuto()) |
| return computeLogicalWidthFromAspectRatio(); |
| |
| auto containerWidthInInlineDirection = !hasPerpendicularContainingBlock ? containerLogicalWidth : perpendicularContainingBlockLogicalHeight(); |
| auto preferredWidth = computeLogicalWidthUsing(usedLogicalWidthLength, containerWidthInInlineDirection, containingBlock); |
| return constrainLogicalWidthByMinMax(preferredWidth, containerWidthInInlineDirection, containingBlock); |
| }; |
| computedValues.m_extent = logicalWidth(); |
| |
| // Margin calculations. |
| if (hasPerpendicularContainingBlock || isFloating() || isInline()) { |
| computedValues.m_margins.m_start = computeOrTrimInlineMargin(containingBlock, MarginTrimType::BlockStart, [&] { |
| return Style::evaluateMinimum(styleToUse.marginStart(), containerLogicalWidth); |
| }); |
| computedValues.m_margins.m_end = computeOrTrimInlineMargin(containingBlock, MarginTrimType::BlockEnd, [&] { |
| return Style::evaluateMinimum(styleToUse.marginEnd(), containerLogicalWidth); |
| }); |
| } else { |
| auto containerLogicalWidthForAutoMargins = containerLogicalWidth; |
| if (avoidsFloats() && containingBlock.containsFloats()) |
| containerLogicalWidthForAutoMargins = containingBlockAvailableLineWidth(); |
| bool hasInvertedDirection = containingBlock.writingMode().isInlineOpposing(writingMode()); |
| computeInlineDirectionMargins(containingBlock, containerLogicalWidth, containerLogicalWidthForAutoMargins, computedValues.m_extent, |
| hasInvertedDirection ? computedValues.m_margins.m_end : computedValues.m_margins.m_start, |
| hasInvertedDirection ? computedValues.m_margins.m_start : computedValues.m_margins.m_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.m_extent + computedValues.m_margins.m_start + computedValues.m_margins.m_end); |
| }; |
| if (!shouldIgnoreOverconstrainedMargin()) { |
| auto availableSpaceForMargin = containerLogicalWidth - computedValues.m_extent; |
| bool hasInvertedDirection = containingBlock.writingMode().isInlineOpposing(writingMode()); |
| if (hasInvertedDirection) |
| computedValues.m_margins.m_start = availableSpaceForMargin - computedValues.m_margins.m_end; |
| else |
| computedValues.m_margins.m_end = availableSpaceForMargin - computedValues.m_margins.m_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, MarginTrimType::InlineStart, [&] { |
| return Style::evaluateMinimum(marginStartLength, availableSizeForResolvingMargin); |
| }); |
| marginEnd = computeOrTrimInlineMargin(*container, MarginTrimType::InlineEnd, [&] { |
| return Style::evaluateMinimum(marginEndLength, availableSizeForResolvingMargin); |
| }); |
| return availableLogicalWidth - marginStart - marginEnd; |
| } |
| |
| |
| template<typename Keyword> void RenderBox::computeIntrinsicKeywordLogicalWidths(Keyword, LayoutUnit borderAndPadding, LayoutUnit& minLogicalWidth, LayoutUnit& maxLogicalWidth) const |
| { |
| if constexpr (std::same_as<Keyword, CSS::Keyword::MinIntrinsic>) |
| computeIntrinsicKeywordLogicalWidths(minLogicalWidth, maxLogicalWidth); |
| else { |
| if (shouldComputeLogicalWidthFromAspectRatio()) { |
| minLogicalWidth = maxLogicalWidth = computeLogicalWidthFromAspectRatioInternal() - borderAndPadding; |
| if (firstChild()) { |
| LayoutUnit minChildrenLogicalWidth; |
| LayoutUnit maxChildrenLogicalWidth; |
| computeIntrinsicKeywordLogicalWidths(minChildrenLogicalWidth, maxChildrenLogicalWidth); |
| minLogicalWidth = std::max(minLogicalWidth, minChildrenLogicalWidth); |
| maxLogicalWidth = std::max(maxLogicalWidth, maxChildrenLogicalWidth); |
| } |
| } else |
| computeIntrinsicKeywordLogicalWidths(minLogicalWidth, maxLogicalWidth); |
| } |
| } |
| |
| LayoutUnit RenderBox::computeIntrinsicLogicalWidthUsing(CSS::Keyword::WebkitFillAvailable, LayoutUnit availableLogicalWidth, LayoutUnit borderAndPadding) const |
| { |
| return std::max(borderAndPadding, fillAvailableMeasure(availableLogicalWidth)); |
| } |
| |
| LayoutUnit RenderBox::computeIntrinsicLogicalWidthUsing(CSS::Keyword::MaxContent keyword, LayoutUnit /*availableLogicalWidth*/, LayoutUnit borderAndPadding) const |
| { |
| LayoutUnit minLogicalWidth; |
| LayoutUnit maxLogicalWidth; |
| computeIntrinsicKeywordLogicalWidths(keyword, borderAndPadding, minLogicalWidth, maxLogicalWidth); |
| |
| return maxLogicalWidth + borderAndPadding; |
| } |
| |
| LayoutUnit RenderBox::computeIntrinsicLogicalWidthUsing(CSS::Keyword::MinContent keyword, LayoutUnit /*availableLogicalWidth*/, LayoutUnit borderAndPadding) const |
| { |
| LayoutUnit minLogicalWidth; |
| LayoutUnit maxLogicalWidth; |
| computeIntrinsicKeywordLogicalWidths(keyword, borderAndPadding, minLogicalWidth, maxLogicalWidth); |
| |
| return minLogicalWidth + borderAndPadding; |
| } |
| |
| LayoutUnit RenderBox::computeIntrinsicLogicalWidthUsing(CSS::Keyword::MinIntrinsic keyword, LayoutUnit /*availableLogicalWidth*/, LayoutUnit borderAndPadding) const |
| { |
| LayoutUnit minLogicalWidth; |
| LayoutUnit maxLogicalWidth; |
| computeIntrinsicKeywordLogicalWidths(keyword, borderAndPadding, minLogicalWidth, maxLogicalWidth); |
| |
| return minLogicalWidth + borderAndPadding; |
| } |
| |
| LayoutUnit RenderBox::computeIntrinsicLogicalWidthUsing(CSS::Keyword::FitContent keyword, LayoutUnit availableLogicalWidth, LayoutUnit borderAndPadding) const |
| { |
| LayoutUnit minLogicalWidth; |
| LayoutUnit maxLogicalWidth; |
| computeIntrinsicKeywordLogicalWidths(keyword, borderAndPadding, minLogicalWidth, maxLogicalWidth); |
| |
| return std::max(minLogicalWidth + borderAndPadding, std::min(maxLogicalWidth + borderAndPadding, fillAvailableMeasure(availableLogicalWidth))); |
| } |
| |
| template<typename SizeType> LayoutUnit RenderBox::computeIntrinsicLogicalWidthUsingGeneric(const SizeType& logicalWidth, LayoutUnit availableLogicalWidth, LayoutUnit borderAndPadding) const |
| { |
| if (logicalWidth.isFillAvailable()) |
| return computeIntrinsicLogicalWidthUsing(CSS::Keyword::WebkitFillAvailable { }, availableLogicalWidth, borderAndPadding); |
| if (logicalWidth.isMinIntrinsic()) |
| return computeIntrinsicLogicalWidthUsing(CSS::Keyword::MinContent { }, availableLogicalWidth, borderAndPadding); |
| if (logicalWidth.isMaxContent()) |
| return computeIntrinsicLogicalWidthUsing(CSS::Keyword::MaxContent { }, availableLogicalWidth, borderAndPadding); |
| if (logicalWidth.isMinContent()) |
| return computeIntrinsicLogicalWidthUsing(CSS::Keyword::MinContent { }, availableLogicalWidth, borderAndPadding); |
| if (logicalWidth.isFitContent()) |
| return computeIntrinsicLogicalWidthUsing(CSS::Keyword::FitContent { }, availableLogicalWidth, borderAndPadding); |
| |
| ASSERT_NOT_REACHED(); |
| return 0; |
| } |
| |
| LayoutUnit RenderBox::computeIntrinsicLogicalWidthUsing(const Style::PreferredSize& logicalWidth, LayoutUnit availableLogicalWidth, LayoutUnit borderAndPadding) const |
| { |
| return computeIntrinsicLogicalWidthUsingGeneric(logicalWidth, availableLogicalWidth, borderAndPadding); |
| } |
| |
| LayoutUnit RenderBox::computeIntrinsicLogicalWidthUsing(const Style::MinimumSize& logicalWidth, LayoutUnit availableLogicalWidth, LayoutUnit borderAndPadding) const |
| { |
| return computeIntrinsicLogicalWidthUsingGeneric(logicalWidth, availableLogicalWidth, borderAndPadding); |
| } |
| |
| LayoutUnit RenderBox::computeIntrinsicLogicalWidthUsing(const Style::MaximumSize& logicalWidth, LayoutUnit availableLogicalWidth, LayoutUnit borderAndPadding) const |
| { |
| return computeIntrinsicLogicalWidthUsingGeneric(logicalWidth, availableLogicalWidth, borderAndPadding); |
| } |
| |
| LayoutUnit RenderBox::computeIntrinsicLogicalWidthUsing(const Style::FlexBasis& logicalWidth, LayoutUnit availableLogicalWidth, LayoutUnit borderAndPadding) const |
| { |
| return computeIntrinsicLogicalWidthUsingGeneric(logicalWidth, availableLogicalWidth, borderAndPadding); |
| } |
| |
| template<typename SizeType> LayoutUnit RenderBox::computeLogicalWidthUsingGeneric(const SizeType& logicalWidth, LayoutUnit availableLogicalWidth, const RenderBlock& containingBlock) const |
| { |
| if constexpr (std::same_as<SizeType, Style::MinimumSize>) { |
| 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)); |
| } |
| |
| if (logicalWidth.isIntrinsic() || logicalWidth.isMinIntrinsic()) |
| return computeIntrinsicLogicalWidthUsing(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<SizeType, Style::PreferredSize> || std::same_as<SizeType, Style::FlexBasis>) { |
| if (sizesPreferredLogicalWidthToFitContent()) |
| return std::max(minPreferredLogicalWidth(), std::min(maxPreferredLogicalWidth(), 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::columnFlexItemHasStretchAlignment() const |
| { |
| // auto margins mean we don't stretch. Note that this function will only be |
| // used for widths, so we don't have to check marginBefore/marginAfter. |
| const auto& parentStyle = parent()->style(); |
| ASSERT(parentStyle.isColumnFlexDirection()); |
| if (style().marginStart().isAuto() || style().marginEnd().isAuto()) |
| return false; |
| return style().resolvedAlignSelf(&parentStyle, containingBlock()->selfAlignmentNormalBehavior()).position() == ItemPosition::Stretch; |
| } |
| |
| 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<RenderFlexibleBox>(*parent()) && parent()->style().flexWrap() == FlexWrap::NoWrap && parent()->style().isColumnFlexDirection() && columnFlexItemHasStretchAlignment()) |
| return true; |
| return false; |
| } |
| |
| // FIXME: Can/Should we move this inside specific layout classes (flex. grid)? Can we refactor columnFlexItemHasStretchAlignment logic? |
| bool RenderBox::hasStretchedLogicalHeight() const |
| { |
| auto& style = this->style(); |
| if (!style.logicalHeight().isAuto() || style.marginBefore().isAuto() || style.marginAfter().isAuto()) |
| return false; |
| RenderBlock* containingBlock = this->containingBlock(); |
| if (!containingBlock) { |
| // We are evaluating align-self/justify-self, which default to 'normal' for the root element. |
| // The 'normal' value behaves like 'start' except for Flexbox Items, which obviously should have a container. |
| return false; |
| } |
| if (containingBlock->isHorizontalWritingMode() != isHorizontalWritingMode()) { |
| if (auto* grid = dynamicDowncast<RenderGrid>(*this); grid && grid->isSubgridInParentDirection(Style::GridTrackSizingDirection::Columns)) |
| return true; |
| return style.resolvedJustifySelf(&containingBlock->style(), containingBlock->selfAlignmentNormalBehavior(this)).position() == ItemPosition::Stretch; |
| } |
| if (auto* grid = dynamicDowncast<RenderGrid>(*this); grid && grid->isSubgridInParentDirection(Style::GridTrackSizingDirection::Rows)) |
| return true; |
| return style.resolvedAlignSelf(&containingBlock->style(), containingBlock->selfAlignmentNormalBehavior(this)).position() == ItemPosition::Stretch; |
| } |
| |
| // FIXME: Can/Should we move this inside specific layout classes (flex. grid)? Can we refactor columnFlexItemHasStretchAlignment logic? |
| bool RenderBox::hasStretchedLogicalWidth(StretchingMode stretchingMode) const |
| { |
| auto& style = this->style(); |
| if (!style.logicalWidth().isAuto() || style.marginStart().isAuto() || style.marginEnd().isAuto()) |
| return false; |
| RenderBlock* containingBlock = this->containingBlock(); |
| if (!containingBlock) { |
| // We are evaluating align-self/justify-self, which default to 'normal' for the root element. |
| // The 'normal' value behaves like 'start' except for Flexbox Items, which obviously should have a container. |
| return false; |
| } |
| auto normalItemPosition = stretchingMode == StretchingMode::Any ? containingBlock->selfAlignmentNormalBehavior(this) : ItemPosition::Normal; |
| if (containingBlock->isHorizontalWritingMode() != isHorizontalWritingMode()) { |
| if (auto* grid = dynamicDowncast<RenderGrid>(*this); grid && grid->isSubgridInParentDirection(Style::GridTrackSizingDirection::Rows)) |
| return true; |
| return style.resolvedAlignSelf(&containingBlock->style(), normalItemPosition).position() == ItemPosition::Stretch; |
| } |
| if (auto* grid = dynamicDowncast<RenderGrid>(*this); grid && grid->isSubgridInParentDirection(Style::GridTrackSizingDirection::Columns)) |
| return true; |
| return style.resolvedJustifySelf(&containingBlock->style(), normalItemPosition).position() == ItemPosition::Stretch; |
| } |
| |
| bool RenderBox::sizesPreferredLogicalWidthToFitContent() 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()) { |
| // FIXME: The masonry logic should not be living in RenderBox; it should ideally live in RenderGrid. |
| // This is a temporary solution to prevent regressions. |
| auto* renderGrid = downcast<RenderGrid>(parent()); |
| return (renderGrid->areMasonryColumns() && !GridLayoutFunctions::isOrthogonalGridItem(*renderGrid, *this)) || !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& 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<RenderFlexibleBox>(*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 (!columnFlexItemHasStretchAlignment()) |
| 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() && element() && (is<HTMLInputElement>(*element()) || is<HTMLSelectElement>(*element()) || is<HTMLButtonElement>(*element()) || is<HTMLTextAreaElement>(*element()) || is<HTMLLegendElement>(*element()))) |
| return true; |
| |
| if (isHorizontalWritingMode() != containingBlock()->isHorizontalWritingMode()) |
| return true; |
| |
| return false; |
| } |
| |
| template<typename Function> |
| LayoutUnit RenderBox::computeOrTrimInlineMargin(const RenderBlock& containingBlock, MarginTrimType 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 == MarginTrimType::InlineStart || marginSide == MarginTrimType::InlineEnd)) |
| const_cast<RenderBox&>(*this).markMarginAsTrimmed(marginSide); |
| return 0_lu; |
| } |
| return computeInlineMargin(); |
| } |
| |
| void RenderBox::computeInlineDirectionMargins(const RenderBlock& containingBlock, LayoutUnit containerWidth, std::optional<LayoutUnit> availableSpaceAdjustedWithFloats, LayoutUnit childWidth, LayoutUnit& marginStart, LayoutUnit& marginEnd) const |
| { |
| auto& containingBlockStyle = containingBlock.style(); |
| auto marginStartLength = style().marginStart(containingBlockStyle.writingMode()); |
| auto marginEndLength = style().marginEnd(containingBlockStyle.writingMode()); |
| |
| if (isFloating()) { |
| marginStart = Style::evaluateMinimum(marginStartLength, containerWidth); |
| marginEnd = Style::evaluateMinimum(marginEndLength, containerWidth); |
| return; |
| } |
| |
| if (isInline()) { |
| // Inline blocks/tables don't have their margins increased. |
| marginStart = computeOrTrimInlineMargin(containingBlock, MarginTrimType::InlineStart, [&] { |
| return Style::evaluateMinimum(marginStartLength, containerWidth); |
| }); |
| marginEnd = computeOrTrimInlineMargin(containingBlock, MarginTrimType::InlineStart, [&] { |
| return Style::evaluateMinimum(marginEndLength, containerWidth); |
| }); |
| return; |
| } |
| |
| if (is<RenderFlexibleBox>(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 (isGridItem() && downcast<RenderGrid>(containingBlock).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() == TextAlignMode::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, MarginTrimType::InlineStart, [&] { |
| LayoutUnit marginStartWidth = Style::evaluateMinimum(marginStartLength, containerWidthForMarginAuto); |
| LayoutUnit marginEndWidth = Style::evaluateMinimum(marginEndLength, containerWidthForMarginAuto); |
| LayoutUnit centeredMarginBoxStart = std::max<LayoutUnit>(0, (containerWidthForMarginAuto - childWidth - marginStartWidth - marginEndWidth) / 2); |
| return centeredMarginBoxStart + marginStartWidth; |
| }); |
| marginEnd = computeOrTrimInlineMargin(containingBlock, MarginTrimType::InlineEnd, [&] { |
| LayoutUnit marginEndWidth = Style::evaluateMinimum(marginEndLength, containerWidthForMarginAuto); |
| 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); |
| 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() == TextAlignMode::WebKitLeft) |
| || (containingBlockStyle.writingMode().isBidiLTR() && containingBlockStyle.textAlign() == TextAlignMode::WebKitRight)); |
| if ((marginStartLength.isAuto() || pushToEndFromTextAlign) && childWidth < containerWidthForMarginAuto) { |
| marginEnd = computeOrTrimInlineMargin(containingBlock, MarginTrimType::InlineEnd, [&] { |
| return Style::evaluate(marginEndLength, containerWidthForMarginAuto); |
| }); |
| marginStart = computeOrTrimInlineMargin(containingBlock, MarginTrimType::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, MarginTrimType::InlineStart, [&] { |
| return Style::evaluateMinimum(marginStartLength, containerWidth); |
| }); |
| marginEnd = computeOrTrimInlineMargin(containingBlock, MarginTrimType::InlineEnd, [&] { |
| return Style::evaluateMinimum(marginEndLength, containerWidth); |
| }); |
| } |
| |
| 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 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::cacheIntrinsicContentLogicalHeightForFlexItem(LayoutUnit height) const |
| { |
| // FIXME: it should be enough with checking hasOverridingLogicalHeight() as this logic could be shared |
| // by any layout system using overrides like grid or flex. However this causes a never ending sequence of calls |
| // between layoutBlock() <-> relayoutToAvoidWidows(). |
| if (isFloatingOrOutOfFlowPositioned()) |
| return; |
| CheckedPtr flexibleBox = dynamicDowncast<RenderFlexibleBox>(parent()); |
| if (!flexibleBox) |
| return; |
| if (overridingBorderBoxLogicalHeight() || shouldComputeLogicalHeightFromAspectRatio()) |
| return; |
| flexibleBox->setCachedFlexItemIntrinsicContentLogicalHeight(*this, height); |
| } |
| |
| 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(); |
| |
| cacheIntrinsicContentLogicalHeightForFlexItem(contentBoxLogicalHeight()); |
| auto computedValues = computeLogicalHeight(logicalHeight(), logicalTop()); |
| setLogicalHeight(computedValues.m_extent); |
| setLogicalTop(computedValues.m_position); |
| setMarginBefore(computedValues.m_margins.m_before); |
| setMarginAfter(computedValues.m_margins.m_after); |
| } |
| |
| RenderBox::LogicalExtentComputedValues RenderBox::computeLogicalHeight(LayoutUnit logicalHeight, LayoutUnit logicalTop) const |
| { |
| LogicalExtentComputedValues computedValues; |
| computedValues.m_extent = logicalHeight; |
| computedValues.m_position = logicalTop; |
| |
| // Cell height is managed by the table and inline non-replaced elements do not support a height property. |
| if (isRenderTableCell() || (isInline() && !isBlockLevelReplacedOrAtomicInline())) |
| return computedValues; |
| |
| if (isOutOfFlowPositioned()) { |
| computePositionedLogicalHeight(computedValues); |
| return computedValues; |
| } |
| |
| bool checkMinMaxHeight = false; |
| auto computedHeightValue = [&]() -> Style::PreferredSize { |
| auto& parent = *this->parent(); |
| |
| if (is<RenderTable>(*this)) { |
| // Table as flex and grid item is special and needs table like handling. |
| auto heightValue = logicalHeight; |
| if (shouldComputeLogicalHeightFromAspectRatio()) |
| heightValue = blockSizeFromAspectRatio(horizontalBorderAndPaddingExtent(), verticalBorderAndPaddingExtent(), style().logicalAspectRatio(), style().boxSizingForAspectRatio(), logicalWidth(), style().aspectRatio(), is<RenderReplaced>(*this)); |
| return Style::PreferredSize::Fixed { heightValue }; |
| } |
| |
| if (is<RenderFlexibleBox>(parent)) { |
| if (auto overridingLogicalHeight = overridingLogicalHeightForFlexBasisComputation()) { |
| ASSERT(!this->overridingBorderBoxLogicalHeight()); |
| checkMinMaxHeight = true; |
| return *overridingLogicalHeight; |
| } |
| |
| if (auto overridingLogicalHeight = this->overridingBorderBoxLogicalHeight()) |
| return Style::PreferredSize::Fixed { *overridingLogicalHeight }; |
| |
| if (is<RenderReplaced>(*this)) |
| return Style::PreferredSize::Fixed { computeReplacedLogicalHeight() + borderAndPaddingLogicalHeight() }; |
| |
| checkMinMaxHeight = true; |
| return style().logicalHeight(); |
| } |
| |
| if (CheckedPtr deprecatedFlexBox = dynamicDowncast<RenderDeprecatedFlexibleBox>(parent)) { |
| if (auto overridingLogicalHeight = this->overridingBorderBoxLogicalHeight()) |
| return Style::PreferredSize::Fixed { *overridingLogicalHeight }; |
| |
| auto& flexBoxStyle = deprecatedFlexBox->style(); |
| auto treatAsReplaced = [&] { |
| if (!is<RenderReplaced>(*this)) |
| return false; |
| bool inHorizontalBox = flexBoxStyle.boxOrient() == BoxOrient::Horizontal; |
| bool stretching = flexBoxStyle.boxAlign() == BoxAlignment::Stretch; |
| return !inHorizontalBox || !stretching; |
| }; |
| if (treatAsReplaced()) |
| return Style::PreferredSize::Fixed { 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 Style::PreferredSize::Fixed { deprecatedFlexBox->contentBoxLogicalHeight() - marginBefore() - marginAfter() }; |
| |
| checkMinMaxHeight = true; |
| return style().logicalHeight(); |
| } |
| |
| if (is<RenderGrid>(parent)) { |
| if (auto overridingLogicalHeight = this->overridingBorderBoxLogicalHeight()) |
| return Style::PreferredSize::Fixed { *overridingLogicalHeight }; |
| |
| if (is<RenderReplaced>(*this)) |
| return Style::PreferredSize::Fixed{ computeReplacedLogicalHeight() + borderAndPaddingLogicalHeight() }; |
| |
| checkMinMaxHeight = true; |
| return style().logicalHeight(); |
| } |
| |
| if (is<RenderReplaced>(*this)) |
| return Style::PreferredSize::Fixed { computeReplacedLogicalHeight() + borderAndPaddingLogicalHeight() }; |
| |
| checkMinMaxHeight = true; |
| return style().logicalHeight(); |
| }(); |
| |
| auto computedLogicalHeight = [&] { |
| if (!checkMinMaxHeight) { |
| ASSERT(computedHeightValue.isFixed()); |
| return LayoutUnit { computedHeightValue.tryFixed()->value }; |
| } |
| |
| // 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<RenderReplaced>(*this)); |
| return constrainLogicalHeightByMinMax(heightFromAspectRatio, intrinsicHeight); |
| } |
| |
| if (intrinsicHeight) |
| *intrinsicHeight -= borderAndPaddingLogicalHeight(); |
| auto mainOrPreferredHeight = computeLogicalHeightUsing(computedHeightValue, intrinsicHeight).value_or(computedValues.m_extent); |
| return constrainLogicalHeightByMinMax(mainOrPreferredHeight, intrinsicHeight); |
| }; |
| computedValues.m_extent = computedLogicalHeight(); |
| |
| 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.m_margins.m_after : computedValues.m_margins.m_before; |
| auto marginAfter = shouldFlipBeforeAfter ? computedValues.m_margins.m_before : computedValues.m_margins.m_after; |
| |
| hasPerpendicularContainingBlock ? computeInlineDirectionMargins(containingBlock, containingBlockLogicalWidthForContent(), { }, computedValues.m_extent, marginBefore, marginAfter) : computeBlockDirectionMargins(containingBlock, marginBefore, marginAfter); |
| computedValues.m_margins.m_before = shouldFlipBeforeAfter ? marginAfter : marginBefore; |
| computedValues.m_margins.m_after = shouldFlipBeforeAfter ? marginBefore : marginAfter; |
| }; |
| computeMargins(); |
| |
| // WinIE quirk: The <html> block always fills the entire canvas in quirks mode. The <body> always fills the |
| // <html> 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() || !computedHeightValue.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.m_extent = std::max(computedValues.m_extent, visibleHeight - margins); |
| else { |
| auto marginsBordersPadding = margins + parentBox()->marginBefore() + parentBox()->marginAfter() + parentBox()->borderAndPaddingLogicalHeight(); |
| computedValues.m_extent = std::max(computedValues.m_extent, visibleHeight - marginsBordersPadding); |
| } |
| } |
| return computedValues; |
| } |
| |
| LayoutUnit RenderBox::computeLogicalHeightWithoutLayout() 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(); |
| } |
| LogicalExtentComputedValues computedValues = computeLogicalHeight(estimatedHeight, 0_lu); |
| return computedValues.m_extent; |
| } |
| |
| template<typename SizeType> std::optional<LayoutUnit> RenderBox::computeLogicalHeightUsingGeneric(const SizeType& logicalHeight, std::optional<LayoutUnit> intrinsicContentHeight) const |
| { |
| if (is<RenderReplaced>(this)) { |
| if constexpr (std::same_as<SizeType, Style::MinimumSize>) { |
| if (!replacedMinLogicalHeightComputesAsNone()) |
| return computeReplacedLogicalHeightUsing(logicalHeight) + borderAndPaddingLogicalHeight(); |
| } else if constexpr (std::same_as<SizeType, Style::MaximumSize>) { |
| if (!replacedMaxLogicalHeightComputesAsNone()) |
| return computeReplacedLogicalHeightUsing(logicalHeight) + borderAndPaddingLogicalHeight(); |
| } |
| return std::nullopt; |
| } |
| if (auto computedContentAndScrollbarLogicalHeight = computeContentAndScrollbarLogicalHeightUsing(logicalHeight, intrinsicContentHeight)) |
| return adjustBorderBoxLogicalHeightForBoxSizing(*computedContentAndScrollbarLogicalHeight); |
| return std::nullopt; |
| } |
| |
| std::optional<LayoutUnit> RenderBox::computeLogicalHeightUsing(const Style::PreferredSize& logicalHeight, std::optional<LayoutUnit> intrinsicContentHeight) const |
| { |
| return computeLogicalHeightUsingGeneric(logicalHeight, intrinsicContentHeight); |
| } |
| |
| std::optional<LayoutUnit> RenderBox::computeLogicalHeightUsing(const Style::MinimumSize& logicalHeight, std::optional<LayoutUnit> intrinsicContentHeight) const |
| { |
| return computeLogicalHeightUsingGeneric(logicalHeight, intrinsicContentHeight); |
| } |
| |
| std::optional<LayoutUnit> RenderBox::computeLogicalHeightUsing(const Style::MaximumSize& logicalHeight, std::optional<LayoutUnit> intrinsicContentHeight) const |
| { |
| return computeLogicalHeightUsingGeneric(logicalHeight, intrinsicContentHeight); |
| } |
| |
| template<typename SizeType> std::optional<LayoutUnit> RenderBox::computeContentLogicalHeightGeneric(const SizeType& logicalHeight, std::optional<LayoutUnit> intrinsicContentHeight) const |
| { |
| if (auto computedContentAndScrollbarLogicalHeight = computeContentAndScrollbarLogicalHeightUsing(logicalHeight, intrinsicContentHeight)) |
| return std::max<LayoutUnit>(0, adjustContentBoxLogicalHeightForBoxSizing(*computedContentAndScrollbarLogicalHeight) - scrollbarLogicalHeight()); |
| return std::nullopt; |
| } |
| |
| std::optional<LayoutUnit> RenderBox::computeContentLogicalHeight(const Style::PreferredSize& logicalHeight, std::optional<LayoutUnit> intrinsicContentHeight) const |
| { |
| return computeContentLogicalHeightGeneric(logicalHeight, intrinsicContentHeight); |
| } |
| |
| std::optional<LayoutUnit> RenderBox::computeContentLogicalHeight(const Style::MinimumSize& logicalHeight, std::optional<LayoutUnit> intrinsicContentHeight) const |
| { |
| return computeContentLogicalHeightGeneric(logicalHeight, intrinsicContentHeight); |
| } |
| |
| std::optional<LayoutUnit> RenderBox::computeContentLogicalHeight(const Style::MaximumSize& logicalHeight, std::optional<LayoutUnit> intrinsicContentHeight) const |
| { |
| return computeContentLogicalHeightGeneric(logicalHeight, intrinsicContentHeight); |
| } |
| |
| std::optional<LayoutUnit> RenderBox::computeContentLogicalHeight(const Style::FlexBasis& logicalHeight, std::optional<LayoutUnit> intrinsicContentHeight) const |
| { |
| return computeContentLogicalHeightGeneric(logicalHeight, intrinsicContentHeight); |
| } |
| |
| static inline bool isOrthogonal(const RenderBox& renderer, const RenderElement& ancestor) |
| { |
| return renderer.isHorizontalWritingMode() != ancestor.isHorizontalWritingMode(); |
| } |
| |
| template<typename SizeType> std::optional<LayoutUnit> RenderBox::computeIntrinsicLogicalContentHeightUsingGeneric(const SizeType& logicalHeight, std::optional<LayoutUnit> intrinsicContentHeight, LayoutUnit borderAndPadding) const |
| { |
| auto intrinsic = [&] -> std::optional<LayoutUnit> { |
| if (intrinsicContentHeight) |
| return adjustIntrinsicLogicalHeightForBoxSizing(*intrinsicContentHeight); |
| return { }; |
| }; |
| |
| auto minMaxContent = [&] -> std::optional<LayoutUnit> { |
| // 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 (auto* renderImage = dynamicDowncast<RenderImage>(this)) { |
| auto computedFixedLogicalWidth = style().logicalWidth().tryFixed(); |
| if (computedFixedLogicalWidth && !style().hasAspectRatio()) { |
| auto intrinsicRatio = renderImage->intrinsicRatio(); |
| return resolveHeightForRatio( |
| borderAndPaddingLogicalWidth(), |
| borderAndPaddingLogicalHeight(), |
| LayoutUnit { computedFixedLogicalWidth->value }, |
| intrinsicRatio.transposedSize().aspectRatio(), |
| BoxSizing::ContentBox |
| ); |
| } |
| } |
| return intrinsic(); |
| }; |
| |
| auto fillAvailable = [&] -> std::optional<LayoutUnit> { |
| return containingBlock()->availableLogicalHeight(AvailableLogicalHeightType::ExcludeMarginBorderPadding) - borderAndPadding; |
| }; |
| |
| return WTF::switchOn(logicalHeight, |
| [&](const CSS::Keyword::MinContent&) -> std::optional<LayoutUnit> { |
| return minMaxContent(); |
| }, |
| [&](const CSS::Keyword::MaxContent&) -> std::optional<LayoutUnit> { |
| return minMaxContent(); |
| }, |
| [&](const CSS::Keyword::Intrinsic&) -> std::optional<LayoutUnit> { |
| return intrinsic(); |
| }, |
| [&](const CSS::Keyword::MinIntrinsic&) -> std::optional<LayoutUnit> { |
| return intrinsic(); |
| }, |
| [&](const CSS::Keyword::FitContent&) -> std::optional<LayoutUnit> { |
| return intrinsic(); |
| }, |
| [&](const CSS::Keyword::WebkitFillAvailable&) -> std::optional<LayoutUnit> { |
| return fillAvailable(); |
| }, |
| [&](const auto&) -> std::optional<LayoutUnit> { |
| ASSERT_NOT_REACHED(); |
| return 0_lu; |
| } |
| ); |
| } |
| |
| std::optional<LayoutUnit> RenderBox::computeIntrinsicLogicalContentHeightUsing(const Style::PreferredSize& logicalHeight, std::optional<LayoutUnit> intrinsicContentHeight, LayoutUnit borderAndPadding) const |
| { |
| return computeIntrinsicLogicalContentHeightUsingGeneric(logicalHeight, intrinsicContentHeight, borderAndPadding); |
| } |
| |
| std::optional<LayoutUnit> RenderBox::computeIntrinsicLogicalContentHeightUsing(const Style::MinimumSize& logicalHeight, std::optional<LayoutUnit> intrinsicContentHeight, LayoutUnit borderAndPadding) const |
| { |
| return computeIntrinsicLogicalContentHeightUsingGeneric(logicalHeight, intrinsicContentHeight, borderAndPadding); |
| } |
| |
| std::optional<LayoutUnit> RenderBox::computeIntrinsicLogicalContentHeightUsing(const Style::MaximumSize& logicalHeight, std::optional<LayoutUnit> intrinsicContentHeight, LayoutUnit borderAndPadding) const |
| { |
| return computeIntrinsicLogicalContentHeightUsingGeneric(logicalHeight, intrinsicContentHeight, borderAndPadding); |
| } |
| |
| std::optional<LayoutUnit> RenderBox::computeIntrinsicLogicalContentHeightUsing(const Style::FlexBasis& logicalHeight, std::optional<LayoutUnit> intrinsicContentHeight, LayoutUnit borderAndPadding) const |
| { |
| return computeIntrinsicLogicalContentHeightUsingGeneric(logicalHeight, intrinsicContentHeight, borderAndPadding); |
| } |
| |
| template<typename SizeType> std::optional<LayoutUnit> RenderBox::computeContentAndScrollbarLogicalHeightUsing(const SizeType& logicalHeight, std::optional<LayoutUnit> intrinsicContentHeight) const |
| { |
| auto intrinsic = [&] { |
| // 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 computeIntrinsicLogicalContentHeightUsing(logicalHeight, intrinsicContentHeight, borderAndPaddingLogicalHeight()); |
| }; |
| |
| return WTF::switchOn(logicalHeight, |
| [&](const typename SizeType::Fixed& fixedLogicalHeight) -> std::optional<LayoutUnit> { |
| return LayoutUnit { fixedLogicalHeight.value }; |
| }, |
| [&](const typename SizeType::Percentage&) -> std::optional<LayoutUnit> { |
| return computePercentageLogicalHeight(logicalHeight); |
| }, |
| [&](const typename SizeType::Calc&) -> std::optional<LayoutUnit> { |
| return computePercentageLogicalHeight(logicalHeight); |
| }, |
| [&](const CSS::Keyword::MinContent&) -> std::optional<LayoutUnit> { |
| return intrinsic(); |
| }, |
| [&](const CSS::Keyword::MaxContent&) -> std::optional<LayoutUnit> { |
| return intrinsic(); |
| }, |
| [&](const CSS::Keyword::FitContent&) -> std::optional<LayoutUnit> { |
| return intrinsic(); |
| }, |
| [&](const CSS::Keyword::WebkitFillAvailable&) -> std::optional<LayoutUnit> { |
| return intrinsic(); |
| }, |
| [&](const CSS::Keyword::Intrinsic&) -> std::optional<LayoutUnit> { |
| return intrinsic(); |
| }, |
| [&](const CSS::Keyword::MinIntrinsic&) -> std::optional<LayoutUnit> { |
| return intrinsic(); |
| }, |
| [&](const CSS::Keyword::Auto&) -> std::optional<LayoutUnit> { |
| if constexpr (std::same_as<SizeType, Style::MinimumSize>) { |
| if (intrinsicContentHeight && isFlexItem() && downcast<RenderFlexibleBox>(parent())->shouldApplyMinBlockSizeAutoForFlexItem(*this)) |
| return adjustIntrinsicLogicalHeightForBoxSizing(*intrinsicContentHeight); |
| return LayoutUnit { 0 }; |
| } else |
| return { }; |
| }, |
| [&](const auto&) -> std::optional<LayoutUnit> { |
| 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; |
| |
| // Render view is not considered auto height. |
| if (is<RenderView>(containingBlock)) |
| return false; |
| |
| // 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.isAnonymousForPercentageResolution()) |
| return containingBlock.style().display() == DisplayType::Block || containingBlock.style().display() == DisplayType::InlineBlock; |
| |
| // For quirks mode, we skip most auto-height containing blocks when computing |
| // percentages. |
| return document().inQuirksMode() && !containingBlock.isRenderTableCell() && !containingBlock.isOutOfFlowPositioned() && !containingBlock.isRenderGrid() && !containingBlock.isFlexibleBoxIncludingDeprecated() && containingBlock.style().logicalHeight().isAuto(); |
| } |
| |
| static bool 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.style().logicalHeight().isAuto() && tableCell.table()->style().logicalHeight().isAuto()) |
| return false; |
| if (child.isBlockLevelReplacedOrAtomicInline()) |
| return false; |
| if (is<HTMLFormControlElement>(child.element()) && !is<HTMLFieldSetElement>(child.element())) |
| return false; |
| return true; |
| } |
| |
| template<typename SizeType> std::optional<LayoutUnit> 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<RenderView>(*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<RenderBox&>(*this)); |
| |
| if (is<RenderView>(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<LayoutUnit> { }; |
| if (isGridItem()) { |
| if (auto gridAreaSize = isOrthogonal ? gridAreaContentLogicalWidth() : gridAreaContentLogicalHeight()) { |
| if (!*gridAreaSize) |
| return { }; |
| overridingAvailableSize = *gridAreaSize; |
| } |
| } |
| if (CheckedPtr tableCell = dynamicDowncast<RenderTableCell>(*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. |
| auto tableCellLogicalHeight = 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; |
| |
| if (!availableHeight) |
| return { }; |
| |
| auto result = Style::evaluate(logicalHeight, *availableHeight - rootMarginBorderPaddingHeight + (isRenderTable() && isOutOfFlowPositioned() ? containingBlock->paddingBefore() + containingBlock->paddingAfter() : 0_lu)); |
| |
| // |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<RenderTableCell>(*containingBlock) && !skippedAutoHeightContainingBlock && containingBlock->overridingBorderBoxLogicalHeight() && style().boxSizing() == BoxSizing::ContentBox); |
| if (subtractBorderAndPadding) { |
| result -= borderAndPaddingLogicalHeight(); |
| return std::max(0_lu, result); |
| } |
| return result; |
| } |
| |
| std::optional<LayoutUnit> RenderBox::computePercentageLogicalHeight(const Style::PreferredSize& logicalHeight, UpdatePercentageHeightDescendants updateDescendants) const |
| { |
| return computePercentageLogicalHeightGeneric(logicalHeight, updateDescendants); |
| } |
| |
| std::optional<LayoutUnit> RenderBox::computePercentageLogicalHeight(const Style::MinimumSize& logicalHeight, UpdatePercentageHeightDescendants updateDescendants) const |
| { |
| return computePercentageLogicalHeightGeneric(logicalHeight, updateDescendants); |
| } |
| |
| std::optional<LayoutUnit> RenderBox::computePercentageLogicalHeight(const Style::MaximumSize& logicalHeight, UpdatePercentageHeightDescendants updateDescendants) const |
| { |
| return computePercentageLogicalHeightGeneric(logicalHeight, updateDescendants); |
| } |
| |
| std::optional<LayoutUnit> RenderBox::computePercentageLogicalHeight(const Style::FlexBasis& logicalHeight, UpdatePercentageHeightDescendants updateDescendants) const |
| { |
| return computePercentageLogicalHeightGeneric(logicalHeight, updateDescendants); |
| } |
| |
| std::optional<LayoutUnit> RenderBox::computePercentageLogicalHeight(const Style::Percentage<CSS::Nonnegative, float>& logicalHeight, UpdatePercentageHeightDescendants updateDescendants) const |
| { |
| return computePercentageLogicalHeightGeneric(logicalHeight, updateDescendants); |
| } |
| |
| std::optional<LayoutUnit> RenderBox::computePercentageLogicalHeight(const Style::UnevaluatedCalculation<CSS::LengthPercentage<CSS::Nonnegative, float>>& logicalHeight, UpdatePercentageHeightDescendants updateDescendants) const |
| { |
| return computePercentageLogicalHeightGeneric(logicalHeight, updateDescendants); |
| } |
| |
| LayoutUnit RenderBox::computeReplacedLogicalWidth(ShouldComputePreferred shouldComputePreferred) const |
| { |
| return computeReplacedLogicalWidthRespectingMinMaxWidth(computeReplacedLogicalWidthUsing(style().logicalWidth()), shouldComputePreferred); |
| } |
| |
| LayoutUnit RenderBox::computeReplacedLogicalWidthRespectingMinMaxWidth(LayoutUnit logicalWidth, ShouldComputePreferred shouldComputePreferred) const |
| { |
| if (shouldIgnoreLogicalMinMaxWidthSizes()) |
| return logicalWidth; |
| |
| auto& logicalMinWidth = style().logicalMinWidth(); |
| auto& logicalMaxWidth = style().logicalMaxWidth(); |
| bool useLogicalWidthForMinWidth = (shouldComputePreferred == ShouldComputePreferred::ComputePreferred && logicalMinWidth.isPercentOrCalculated()); |
| bool useLogicalWidthForMaxWidth = (shouldComputePreferred == ShouldComputePreferred::ComputePreferred && logicalMaxWidth.isPercentOrCalculated()) || logicalMaxWidth.isNone(); |
| auto minLogicalWidth = useLogicalWidthForMinWidth ? logicalWidth : computeReplacedLogicalWidthUsing(logicalMinWidth); |
| auto maxLogicalWidth = useLogicalWidthForMaxWidth ? logicalWidth : computeReplacedLogicalWidthUsing(logicalMaxWidth); |
| return std::max(minLogicalWidth, std::min(logicalWidth, maxLogicalWidth)); |
| } |
| |
| template<typename SizeType> LayoutUnit RenderBox::computeReplacedLogicalWidthUsingGeneric(const SizeType& logicalWidth) const |
| { |
| auto calculateContainerWidth = [&] { |
| if (isOutOfFlowPositioned()) { |
| PositionedLayoutConstraints constraints(*this, LogicalBoxAxis::Inline); |
| return constraints.containingSize(); |
| } |
| if (isHorizontalWritingMode() == containingBlock()->isHorizontalWritingMode()) |
| return containingBlockLogicalWidthForContent(); |
| return perpendicularContainingBlockLogicalHeight(); |
| }; |
| |
| auto percentageOrCalc = [&](const auto& logicalWidth) { |
| // FIXME: Handle cases when containing block width is calculated or viewport percent. |
| // https://bugs.webkit.org/show_bug.cgi?id=91071 |
| if (auto containerWidth = calculateContainerWidth(); containerWidth > 0 || (!containerWidth && (containingBlock()->style().logicalWidth().isSpecified()))) |
| return adjustContentBoxLogicalWidthForBoxSizing(Style::evaluate(logicalWidth, containerWidth)); |
| return 0_lu; |
| }; |
| |
| auto content = [&](const auto& keyword, const auto& availableLogicalWidth) { |
| // FIXME: Handle cases when containing block width is calculated or viewport percent. |
| // https://bugs.webkit.org/show_bug.cgi?id=91071 |
| return computeIntrinsicLogicalWidthUsing(keyword, availableLogicalWidth, borderAndPaddingLogicalWidth()) - borderAndPaddingLogicalWidth(); |
| }; |
| |
| return WTF::switchOn(logicalWidth, |
| [&](const typename SizeType::Fixed& fixedLogicalWidth) -> LayoutUnit { |
| return adjustContentBoxLogicalWidthForBoxSizing(fixedLogicalWidth); |
| }, |
| [&](const typename SizeType::Percentage& percentageLogicalWidth) -> LayoutUnit { |
| return percentageOrCalc(percentageLogicalWidth); |
| }, |
| [&](const typename SizeType::Calc& calculatedLogicalWidth) -> LayoutUnit { |
| return percentageOrCalc(calculatedLogicalWidth); |
| }, |
| [&](const CSS::Keyword::FitContent& keyword) -> LayoutUnit { |
| return content(keyword, calculateContainerWidth()); |
| }, |
| [&](const CSS::Keyword::WebkitFillAvailable& keyword) -> LayoutUnit { |
| return content(keyword, calculateContainerWidth()); |
| }, |
| [&](const CSS::Keyword::MinContent& keyword) -> LayoutUnit { |
| // min-content/max-content don't need the availableLogicalWidth argument. |
| return content(keyword, 0_lu); |
| }, |
| [&](const CSS::Keyword::MaxContent& keyword) -> LayoutUnit { |
| // min-content/max-content don't need the availableLogicalWidth argument. |
| return content(keyword, 0_lu); |
| }, |
| [&](const CSS::Keyword::Intrinsic&) -> LayoutUnit { |
| return intrinsicLogicalWidth(); |
| }, |
| [&](const CSS::Keyword::MinIntrinsic&) -> LayoutUnit { |
| return intrinsicLogicalWidth(); |
| }, |
| [&](const CSS::Keyword::Auto&) -> LayoutUnit { |
| if constexpr (std::same_as<SizeType, Style::MinimumSize>) |
| return 0_lu; |
| else |
| return intrinsicLogicalWidth(); |
| }, |
| [&](const CSS::Keyword::None&) -> LayoutUnit { |
| return intrinsicLogicalWidth(); |
| } |
| ); |
| } |
| |
| LayoutUnit RenderBox::computeReplacedLogicalWidthUsing(const Style::PreferredSize& logicalWidth) const |
| { |
| return computeReplacedLogicalWidthUsingGeneric(logicalWidth); |
| } |
| |
| LayoutUnit RenderBox::computeReplacedLogicalWidthUsing(const Style::MinimumSize& logicalWidth) const |
| { |
| return computeReplacedLogicalWidthUsingGeneric(logicalWidth); |
| } |
| |
| LayoutUnit RenderBox::computeReplacedLogicalWidthUsing(const Style::MaximumSize& logicalWidth) const |
| { |
| return computeReplacedLogicalWidthUsingGeneric(logicalWidth); |
| } |
| |
| LayoutUnit RenderBox::computeReplacedLogicalHeight(std::optional<LayoutUnit>) const |
| { |
| return computeReplacedLogicalHeightRespectingMinMaxHeight(computeReplacedLogicalHeightUsing(style().logicalHeight())); |
| } |
| |
| static bool allowMinMaxPercentagesInAutoHeightBlocksQuirk() |
| { |
| #if PLATFORM(COCOA) |
| return WTF::CocoaApplication::isAppleBooks(); |
| #else |
| return false; |
| #endif |
| } |
| |
| bool RenderBox::shouldComputePreferredLogicalWidthsFromStyle() const |
| { |
| auto fixedLogicalWidth = overridingLogicalWidthForFlexBasisComputation().value_or(style().logicalWidth()).tryFixed(); |
| return fixedLogicalWidth && fixedLogicalWidth->value >= 0 && !(isDeprecatedFlexItem() && !static_cast<int>(fixedLogicalWidth->value)); |
| } |
| |
| void RenderBox::computePreferredLogicalWidths() |
| { |
| ASSERT(needsPreferredLogicalWidthsUpdate()); |
| |
| computePreferredLogicalWidths(style().logicalMinWidth(), style().logicalMaxWidth(), borderAndPaddingLogicalWidth()); |
| clearNeedsPreferredWidthsUpdate(); |
| } |
| |
| void RenderBox::computePreferredLogicalWidths(const Style::MinimumSize& minLogicalWidth, const Style::MaximumSize& maxLogicalWidth, LayoutUnit borderAndPaddingLogicalWidth) |
| { |
| 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()) { |
| if (!shouldComputePreferredLogicalWidthsFromStyle()) |
| return m_minPreferredLogicalWidth; |
| |
| return computeIntrinsicLogicalWidthUsing(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->value > 0) |
| return adjustContentBoxLogicalWidthForBoxSizing(*fixedMinLogicalWidth); |
| |
| if (minLogicalWidth.isMaxContent()) |
| return m_maxPreferredLogicalWidth; |
| |
| return { }; |
| }(); |
| |
| if (!style().logicalWidth().isFixed() && shouldComputeLogicalHeightFromAspectRatio()) { |
| auto [transferredMinLogicalWidth, transferredMaxLogicalWidth] = computeMinMaxLogicalWidthFromAspectRatio(); |
| transferredMinLogicalWidth = std::max(transferredMinLogicalWidth - borderAndPaddingLogicalWidth, 0_lu); |
| transferredMaxLogicalWidth = std::max(transferredMaxLogicalWidth - borderAndPaddingLogicalWidth, 0_lu); |
| m_minPreferredLogicalWidth = std::clamp(m_minPreferredLogicalWidth, transferredMinLogicalWidth, transferredMaxLogicalWidth); |
| m_maxPreferredLogicalWidth = std::clamp(m_maxPreferredLogicalWidth, transferredMinLogicalWidth, transferredMaxLogicalWidth); |
| } |
| |
| m_maxPreferredLogicalWidth = std::min(m_maxPreferredLogicalWidth, usedMaxLogicalWidth); |
| m_minPreferredLogicalWidth = std::min(m_minPreferredLogicalWidth, usedMaxLogicalWidth); |
| |
| m_maxPreferredLogicalWidth = std::max(m_maxPreferredLogicalWidth, usedMinLogicalWidth); |
| m_minPreferredLogicalWidth = std::max(m_minPreferredLogicalWidth, usedMinLogicalWidth); |
| |
| m_minPreferredLogicalWidth += borderAndPaddingLogicalWidth; |
| m_maxPreferredLogicalWidth += borderAndPaddingLogicalWidth; |
| } |
| |
| bool RenderBox::replacedMinMaxLogicalHeightComputesAsNone(const auto& logicalHeight, const auto& initialLogicalHeight) const |
| { |
| if (logicalHeight == initialLogicalHeight) |
| return true; |
| |
| if (isGridItem() && logicalHeight.isPercentOrCalculated()) { |
| if (auto gridAreaContentLogicalHeight = this->gridAreaContentLogicalHeight()) |
| return !*gridAreaContentLogicalHeight; |
| } |
| |
| // Make sure % min-height and % max-height resolve to none if the containing block has auto height. |
| // Note that the "height" case for replaced elements was handled by hasReplacedLogicalHeight, which is why |
| // min and max-height are the only ones handled here. |
| // FIXME: For now we put in a quirk for Apple Books until we can move them to viewport units. |
| if (auto* cb = containingBlockForAutoHeightDetection(logicalHeight)) |
| return allowMinMaxPercentagesInAutoHeightBlocksQuirk() ? false : cb->hasAutoHeightOrContainingBlockWithAutoHeight(); |
| |
| return false; |
| } |
| |
| bool RenderBox::replacedMinLogicalHeightComputesAsNone() const |
| { |
| return replacedMinMaxLogicalHeightComputesAsNone(style().logicalMinHeight(), RenderStyle::initialMinSize()); |
| } |
| |
| bool RenderBox::replacedMaxLogicalHeightComputesAsNone() const |
| { |
| return replacedMinMaxLogicalHeightComputesAsNone(style().logicalMaxHeight(), RenderStyle::initialMaxSize()); |
| } |
| |
| LayoutUnit RenderBox::computeReplacedLogicalHeightRespectingMinMaxHeight(LayoutUnit logicalHeight) const |
| { |
| LayoutUnit minLogicalHeight; |
| if (!replacedMinLogicalHeightComputesAsNone()) |
| minLogicalHeight = computeReplacedLogicalHeightUsing(style().logicalMinHeight()); |
| LayoutUnit maxLogicalHeight = logicalHeight; |
| if (!replacedMaxLogicalHeightComputesAsNone()) |
| maxLogicalHeight = computeReplacedLogicalHeightUsing(style().logicalMaxHeight()); |
| return std::max(minLogicalHeight, std::min(logicalHeight, maxLogicalHeight)); |
| } |
| |
| template<typename SizeType> LayoutUnit RenderBox::computeReplacedLogicalHeightUsingGeneric(const SizeType& logicalHeight) const |
| { |
| #if ASSERT_ENABLED |
| // This function should get called with Style::MinimumSize/Style::MaximumSize only if replaced[Min|Max]LogicalHeightComputesAsNone |
| // returns false, otherwise we should not try to compute those values as they may be incorrect. The caller should make sure this |
| // condition holds before calling this function |
| if constexpr (std::same_as<SizeType, Style::MinimumSize>) |
| ASSERT(!replacedMinLogicalHeightComputesAsNone()); |
| else if constexpr (std::same_as<SizeType, Style::MaximumSize>) |
| ASSERT(!replacedMaxLogicalHeightComputesAsNone()); |
| #endif |
| |
| auto percentageOrCalculated = [&](const auto& logicalHeight) { |
| auto* container = isOutOfFlowPositioned() ? this->container() : containingBlock(); |
| while (container && container->isAnonymousForPercentageResolution()) { |
| // Stop at rendering context root. |
| if (is<RenderView>(*container)) |
| break; |
| container = container->containingBlock(); |
| } |
| bool hasPerpendicularContainingBlock = container->isHorizontalWritingMode() != isHorizontalWritingMode(); |
| std::optional<LayoutUnit> stretchedHeight; |
| if (auto* block = dynamicDowncast<RenderBlock>(container)) { |
| block->addPercentHeightDescendant(*const_cast<RenderBox*>(this)); |
| if (auto usedFlexItemOverridingLogicalHeightForPercentageResolutionForFlex = (block->isFlexItem() ? downcast<RenderFlexibleBox>(block->parent())->usedFlexItemOverridingLogicalHeightForPercentageResolution(*block) : std::nullopt)) |
| stretchedHeight = block->contentBoxLogicalHeight(*usedFlexItemOverridingLogicalHeightForPercentageResolutionForFlex); |
| else if (auto usedChildOverridingLogicalHeightForGrid = (block->isGridItem() && !hasPerpendicularContainingBlock ? block->overridingBorderBoxLogicalHeight() : std::nullopt)) |
| stretchedHeight = block->contentBoxLogicalHeight(*usedChildOverridingLogicalHeightForGrid); |
| } |
| |
| // FIXME: This calculation is not patched for block-flow yet. |
| // https://bugs.webkit.org/show_bug.cgi?id=46500 |
| if (container->isOutOfFlowPositioned() |
| && container->style().height().isAuto() |
| && !(container->style().top().isAuto() || container->style().bottom().isAuto())) { |
| auto& block = downcast<RenderBlock>(*container); |
| auto computedValues = block.computeLogicalHeight(block.logicalHeight(), 0); |
| LayoutUnit borderPaddingAdjustment = isOutOfFlowPositioned() ? block.borderLogicalHeight() : block.borderAndPaddingLogicalHeight(); |
| LayoutUnit newContentHeight = computedValues.m_extent - block.scrollbarLogicalHeight() - borderPaddingAdjustment; |
| return adjustContentBoxLogicalHeightForBoxSizing(Style::evaluate(logicalHeight, newContentHeight)); |
| } |
| |
| LayoutUnit availableHeight; |
| if (isOutOfFlowPositioned()) { |
| PositionedLayoutConstraints constraints(*this, LogicalBoxAxis::Block); |
| availableHeight = constraints.containingSize(); |
| } else if (stretchedHeight) |
| availableHeight = stretchedHeight.value(); |
| else if (auto gridAreaLogicalHeight = isGridItem() ? this->gridAreaContentLogicalHeight() : std::nullopt; gridAreaLogicalHeight && *gridAreaLogicalHeight) |
| availableHeight = gridAreaLogicalHeight->value(); |
| else { |
| availableHeight = hasPerpendicularContainingBlock ? containingBlockLogicalWidthForContent() : containingBlockLogicalHeightForContent(AvailableLogicalHeightType::IncludeMarginBorderPadding); |
| // It is necessary to use the border-box to match WinIE's broken |
| // box model. This is essential for sizing inside |
| // table cells using percentage heights. |
| // FIXME: This needs to be made block-flow-aware. If the cell and image are perpendicular block-flows, this isn't right. |
| // https://bugs.webkit.org/show_bug.cgi?id=46997 |
| while (container && !is<RenderView>(*container) |
| && (container->style().logicalHeight().isAuto() || container->style().logicalHeight().isPercentOrCalculated())) { |
| if (container->isRenderTableCell()) { |
| // Don't let table cells squeeze percent-height replaced elements |
| // <http://bugs.webkit.org/show_bug.cgi?id=15359> |
| availableHeight = std::max(availableHeight, intrinsicLogicalHeight()); |
| return Style::evaluate(logicalHeight, availableHeight - borderAndPaddingLogicalHeight()); |
| } |
| downcast<RenderBlock>(*container).addPercentHeightDescendant(const_cast<RenderBox&>(*this)); |
| container = container->containingBlock(); |
| } |
| } |
| return adjustContentBoxLogicalHeightForBoxSizing(Style::evaluate(logicalHeight, availableHeight)); |
| }; |
| |
| auto content = [&] { |
| return adjustContentBoxLogicalHeightForBoxSizing(computeIntrinsicLogicalContentHeightUsing(logicalHeight, intrinsicLogicalHeight(), borderAndPaddingLogicalHeight())); |
| }; |
| |
| return WTF::switchOn(logicalHeight, |
| [&](const typename SizeType::Fixed& fixedLogicalHeight) -> LayoutUnit { |
| return adjustContentBoxLogicalHeightForBoxSizing(LayoutUnit { fixedLogicalHeight.value }); |
| }, |
| [&](const typename SizeType::Percentage& percentageLogicalHeight) -> LayoutUnit { |
| return percentageOrCalculated(percentageLogicalHeight); |
| }, |
| [&](const typename SizeType::Calc& calculatedLogicalHeight) -> LayoutUnit { |
| return percentageOrCalculated(calculatedLogicalHeight); |
| }, |
| [&](const CSS::Keyword::FitContent&) -> LayoutUnit { |
| return content(); |
| }, |
| [&](const CSS::Keyword::WebkitFillAvailable&) -> LayoutUnit { |
| return content(); |
| }, |
| [&](const CSS::Keyword::MinContent&) -> LayoutUnit { |
| return content(); |
| }, |
| [&](const CSS::Keyword::MaxContent&) -> LayoutUnit { |
| return content(); |
| }, |
| [&](const CSS::Keyword::Intrinsic&) -> LayoutUnit { |
| return intrinsicLogicalHeight(); |
| }, |
| [&](const CSS::Keyword::MinIntrinsic&) -> LayoutUnit { |
| return intrinsicLogicalHeight(); |
| }, |
| [&](const CSS::Keyword::Auto&) -> LayoutUnit { |
| if constexpr (std::same_as<SizeType, Style::MinimumSize>) |
| return adjustContentBoxLogicalHeightForBoxSizing(LayoutUnit { 0 }); |
| else |
| return intrinsicLogicalHeight(); |
| }, |
| [&](const CSS::Keyword::None&) -> LayoutUnit { |
| return intrinsicLogicalHeight(); |
| } |
| ); |
| } |
| |
| LayoutUnit RenderBox::computeReplacedLogicalHeightUsing(const Style::PreferredSize& logicalHeight) const |
| { |
| return computeReplacedLogicalHeightUsingGeneric(logicalHeight); |
| } |
| |
| LayoutUnit RenderBox::computeReplacedLogicalHeightUsing(const Style::MinimumSize& logicalHeight) const |
| { |
| return computeReplacedLogicalHeightUsingGeneric(logicalHeight); |
| } |
| |
| LayoutUnit RenderBox::computeReplacedLogicalHeightUsing(const Style::MaximumSize& logicalHeight) const |
| { |
| return computeReplacedLogicalHeightUsingGeneric(logicalHeight); |
| } |
| |
| 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<RenderFlexibleBox>(*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())); |
| } |
| |
| if (auto computedContentAndScrollbarLogicalHeight = computeContentAndScrollbarLogicalHeightUsing(logicalHeight, std::nullopt)) |
| return std::max<LayoutUnit>(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<RenderBlock>(*this); block && isOutOfFlowPositioned() && style().logicalHeight().isAuto() && !(style().logicalTop().isAuto() || style().logicalBottom().isAuto())) { |
| auto computedValues = block->computeLogicalHeight(block->logicalHeight(), 0); |
| return computedValues.m_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) |
| LayoutUnit cw = containingBlockLogicalWidthForContent(); |
| marginBefore = constrainBlockMarginInAvailableSpaceOrTrim(containingBlock, cw, MarginTrimType::BlockStart); |
| marginAfter = constrainBlockMarginInAvailableSpaceOrTrim(containingBlock, cw, MarginTrimType::BlockEnd); |
| } |
| |
| void RenderBox::computeAndSetBlockDirectionMargins(const RenderBlock& containingBlock) |
| { |
| LayoutUnit marginBefore; |
| LayoutUnit marginAfter; |
| computeBlockDirectionMargins(containingBlock, marginBefore, marginAfter); |
| containingBlock.setMarginBeforeForChild(*this, marginBefore); |
| containingBlock.setMarginAfterForChild(*this, marginAfter); |
| } |
| |
| LayoutUnit RenderBox::constrainBlockMarginInAvailableSpaceOrTrim(const RenderBox& containingBlock, LayoutUnit availableSpace, MarginTrimType marginSide) const |
| { |
| |
| ASSERT(marginSide == MarginTrimType::BlockStart || marginSide == MarginTrimType::BlockEnd); |
| 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()) |
| const_cast<RenderBox&>(*this).markMarginAsTrimmed(marginSide); |
| return 0_lu; |
| } |
| |
| return marginSide == MarginTrimType::BlockStart |
| ? Style::evaluateMinimum(style().marginBefore(containingBlock.writingMode()), availableSpace) |
| : Style::evaluateMinimum(style().marginAfter(containingBlock.writingMode()), availableSpace); |
| } |
| |
| // MARK: - Positioned Layout |
| |
| LayoutUnit RenderBox::containingBlockLogicalWidthForPositioned(const RenderBoxModelObject& containingBlock, bool checkForPerpendicularWritingMode) const |
| { |
| ASSERT(containingBlock.canContainAbsolutelyPositionedObjects() || containingBlock.canContainFixedPositionObjects()); |
| |
| if (checkForPerpendicularWritingMode && containingBlock.isHorizontalWritingMode() != isHorizontalWritingMode()) |
| return containingBlockLogicalHeightForPositioned(containingBlock, false); |
| |
| if (CheckedPtr inlineBox = containingBlock.inlineContinuation()) { |
| auto relativelyPositionedInlineBoxAncestor = [&] { |
| // Since we stop splitting inlines over 200 nested boxes (see RenderTreeBuilder::Inline::splitInlines), we may not be able to find the real containing block here. |
| CheckedPtr<RenderElement> ancestor = inlineBox; |
| for (; ancestor && !ancestor->isRelativelyPositioned(); ancestor = ancestor->parent()) { } |
| return ancestor; |
| }; |
| if (auto containingBlock = relativelyPositionedInlineBoxAncestor(); containingBlock && is<RenderInline>(*containingBlock)) |
| return containingBlockLogicalWidthForPositioned(*dynamicDowncast<RenderInline>(*containingBlock), checkForPerpendicularWritingMode); |
| } |
| |
| if (auto* box = dynamicDowncast<RenderBox>(containingBlock)) { |
| bool isFixedPosition = isFixedPositioned(); |
| |
| if (!enclosingFragmentedFlow()) { |
| if (isFixedPosition) { |
| if (auto* renderView = dynamicDowncast<RenderView>(containingBlock)) |
| return renderView->clientLogicalWidthForFixedPosition(); |
| } |
| return downcast<RenderBox>(containingBlock).clientLogicalWidth(); |
| } |
| |
| CheckedPtr cb = dynamicDowncast<RenderBlock>(containingBlock); |
| if (!cb) |
| return box->clientLogicalWidth(); |
| |
| RenderBoxFragmentInfo* boxInfo = nullptr; |
| if (auto* fragmentedFlow = dynamicDowncast<RenderFragmentedFlow>(containingBlock); fragmentedFlow && !checkForPerpendicularWritingMode) |
| return fragmentedFlow->contentLogicalWidthOfFirstFragment(); |
| if (isWritingModeRoot()) { |
| LayoutUnit cbPageOffset = cb->offsetFromLogicalTopOfFirstPage(); |
| RenderFragmentContainer* cbFragment = cb->fragmentAtBlockOffset(cbPageOffset); |
| if (cbFragment) |
| boxInfo = cb->renderBoxFragmentInfo(cbFragment); |
| } |
| return (boxInfo) ? std::max<LayoutUnit>(0, cb->clientLogicalWidth() - (cb->logicalWidth() - boxInfo->logicalWidth())) : cb->clientLogicalWidth(); |
| } |
| |
| if (auto* inlineBox = dynamicDowncast<RenderInline>(containingBlock)) |
| return inlineBox->innerPaddingBoxWidth(); |
| |
| ASSERT_NOT_REACHED(); |
| return { }; |
| } |
| |
| LayoutUnit RenderBox::containingBlockLogicalHeightForPositioned(const RenderBoxModelObject& containingBlock, bool checkForPerpendicularWritingMode) const |
| { |
| ASSERT(containingBlock.canContainAbsolutelyPositionedObjects() || containingBlock.canContainFixedPositionObjects()); |
| |
| if (checkForPerpendicularWritingMode && containingBlock.isHorizontalWritingMode() != isHorizontalWritingMode()) |
| return containingBlockLogicalWidthForPositioned(containingBlock, false); |
| |
| if (auto* box = dynamicDowncast<RenderBox>(containingBlock)) { |
| bool isFixedPosition = isFixedPositioned(); |
| |
| if (isFixedPosition) { |
| if (auto* renderView = dynamicDowncast<RenderView>(*box)) |
| return renderView->clientLogicalHeightForFixedPosition(); |
| } |
| |
| if (enclosingFragmentedFlow() && enclosingFragmentedFlow()->isHorizontalWritingMode() == containingBlock.isHorizontalWritingMode()) { |
| if (CheckedPtr containingBlockFragmentedFlow = dynamicDowncast<RenderFragmentedFlow>(containingBlock)) |
| return containingBlockFragmentedFlow->contentLogicalHeightOfFirstFragment(); |
| } |
| |
| auto logicalHeight = LayoutUnit { }; |
| if (CheckedPtr containingBlockAsRenderBlock = dynamicDowncast<RenderBlock>(*box)) |
| logicalHeight = containingBlockAsRenderBlock->clientLogicalHeight(); |
| else |
| logicalHeight = box->containingBlock()->clientLogicalHeight(); |
| |
| return logicalHeight; |
| } |
| |
| if (auto* inlineBox = dynamicDowncast<RenderInline>(containingBlock)) |
| return inlineBox->innerPaddingBoxHeight(); |
| |
| ASSERT_NOT_REACHED(); |
| return { }; |
| } |
| |
| void RenderBox::computePositionedLogicalWidth(LogicalExtentComputedValues& computedValues) const |
| { |
| if (isBlockLevelReplacedOrAtomicInline()) { |
| computePositionedLogicalWidthReplaced(computedValues); |
| return; |
| } |
| |
| // 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" |
| // <http://www.w3.org/TR/CSS21/visudet.html#abs-non-replaced-width> |
| // (block-style-comments in this function and in computePositionedLogicalWidthUsing() |
| // 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 = computePositionedLogicalWidthUsing(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 = computePositionedLogicalWidthUsing(logicalMaxWidth, inlineConstraints); |
| if (transferredMaxSize < usedMaxWidth) |
| usedMaxWidth = computePositionedLogicalWidthUsing(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.isZero() || logicalMinWidth.isIntrinsic()) |
| usedMinWidth = computePositionedLogicalWidthUsing(logicalMinWidth, inlineConstraints); |
| if (transferredMinSize > usedMinWidth) |
| usedMinWidth = computePositionedLogicalWidthUsing(Style::MinimumSize { Style::MinimumSize::Fixed { transferredMinSize } }, inlineConstraints); |
| if (usedWidth < usedMinWidth) |
| usedWidth = usedMinWidth; |
| |
| // Set the final width value. |
| computedValues.m_extent = 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<RenderBlock>(inlineConstraints.container())) { |
| ASSERT(inlineConstraints.container().canHaveBoxInfoInFragment()); |
| LayoutUnit logicalLeftPos = computedValues.m_position; |
| LayoutUnit cbPageOffset = container->offsetFromLogicalTopOfFirstPage(); |
| RenderFragmentContainer* cbFragment = container->fragmentAtBlockOffset(cbPageOffset); |
| if (cbFragment) { |
| RenderBoxFragmentInfo* boxInfo = container->renderBoxFragmentInfo(cbFragment); |
| if (boxInfo) { |
| logicalLeftPos += boxInfo->logicalLeft(); |
| computedValues.m_position = logicalLeftPos; |
| } |
| } |
| } |
| } |
| } |
| |
| template<typename SizeType> LayoutUnit RenderBox::computePositionedLogicalWidthUsing(const SizeType& logicalWidth, const PositionedLayoutConstraints& inlineConstraints) const |
| { |
| auto fallback = [&] -> LayoutUnit { |
| bool logicalLeftIsAuto = inlineConstraints.insetBefore().isAuto(); |
| bool logicalRightIsAuto = inlineConstraints.insetAfter().isAuto(); |
| bool shrinkToFit = logicalLeftIsAuto || logicalRightIsAuto || !inlineConstraints.alignmentAppliesStretch(ItemPosition::Stretch); |
| if (shrinkToFit) { |
| auto preferredWidth = maxPreferredLogicalWidth() - inlineConstraints.bordersPlusPadding(); |
| auto preferredMinWidth = minPreferredLogicalWidth() - inlineConstraints.bordersPlusPadding(); |
| return std::min(std::max(preferredMinWidth, inlineConstraints.availableContentSpace()), preferredWidth); |
| } |
| return std::max(0_lu, inlineConstraints.availableContentSpace()); |
| }; |
| |
| auto intrinsic = [&](const auto& keyword) -> LayoutUnit { |
| auto availableSpace = inlineConstraints.containingSize(); |
| if (!inlineConstraints.insetBefore().isAuto()) |
| availableSpace -= inlineConstraints.insetBeforeValue(); |
| if (!inlineConstraints.insetAfter().isAuto()) |
| availableSpace -= inlineConstraints.insetAfterValue(); |
| return std::max(0_lu, computeIntrinsicLogicalWidthUsing(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())); |
| }, |
| [&](const CSS::Keyword::FitContent& keyword) -> LayoutUnit { |
| return intrinsic(keyword); |
| }, |
| [&](const CSS::Keyword::WebkitFillAvailable& keyword) -> LayoutUnit { |
| return intrinsic(keyword); |
| }, |
| [&](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<SizeType, Style::MinimumSize>) { |
| if (shouldComputeLogicalWidthFromAspectRatio()) { |
| LayoutUnit minLogicalWidth; |
| LayoutUnit maxLogicalWidth; |
| computeIntrinsicLogicalWidths(minLogicalWidth, maxLogicalWidth); |
| return minLogicalWidth; |
| } |
| return 0_lu; |
| } else { |
| if (shouldComputeLogicalWidthFromAspectRatio()) |
| return computeLogicalWidthFromAspectRatio(); |
| return fallback(); |
| } |
| }, |
| [&](const CSS::Keyword::None&) -> LayoutUnit { |
| return inlineConstraints.containingSize(); |
| } |
| ); |
| } |
| |
| void RenderBox::computePositionedLogicalHeight(LogicalExtentComputedValues& computedValues) const |
| { |
| if (isBlockLevelReplacedOrAtomicInline()) { |
| computePositionedLogicalHeightReplaced(computedValues); |
| return; |
| } |
| |
| PositionedLayoutConstraints blockConstraints(*this, LogicalBoxAxis::Block); |
| blockConstraints.computeInsets(); |
| |
| // Calculate the used height. See CSS2 § 10.6.4. |
| auto& styleToUse = style(); |
| LayoutUnit computedHeight = computedValues.m_extent; |
| LayoutUnit usedHeight = computePositionedLogicalHeightUsing(styleToUse.logicalHeight(), computedHeight, blockConstraints); |
| |
| // Clamp by max height. |
| if (auto logicalMaxHeight = styleToUse.logicalMaxHeight(); !logicalMaxHeight.isNone()) { |
| auto usedMaxHeight = computePositionedLogicalHeightUsing(logicalMaxHeight, computedHeight, blockConstraints); |
| if (usedHeight > usedMaxHeight) |
| usedHeight = usedMaxHeight; |
| } |
| |
| // Clamp by min height. |
| if (auto& logicalMinHeight = styleToUse.logicalMinHeight(); logicalMinHeight.isAuto() || !logicalMinHeight.isZero() || logicalMinHeight.isIntrinsic()) { |
| auto usedMinHeight = computePositionedLogicalHeightUsing(logicalMinHeight, computedHeight, blockConstraints); |
| if (usedHeight < usedMinHeight) |
| usedHeight = usedMinHeight; |
| } |
| |
| // Set the final height value. |
| computedValues.m_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<RenderBlock>(blockConstraints.container())) { |
| ASSERT(blockConstraints.container().canHaveBoxInfoInFragment()); |
| LayoutUnit logicalTopPos = computedValues.m_position; |
| LayoutUnit cbPageOffset = container->offsetFromLogicalTopOfFirstPage() - logicalLeft(); |
| RenderFragmentContainer* cbFragment = container->fragmentAtBlockOffset(cbPageOffset); |
| if (cbFragment) { |
| RenderBoxFragmentInfo* boxInfo = container->renderBoxFragmentInfo(cbFragment); |
| if (boxInfo) { |
| logicalTopPos += boxInfo->logicalLeft(); |
| computedValues.m_position = logicalTopPos; |
| } |
| } |
| } |
| } |
| } |
| |
| LayoutUnit RenderBox::computePositionedLogicalHeightUsing(const Style::PreferredSize& logicalHeight, LayoutUnit computedHeight, const PositionedLayoutConstraints& blockConstraints) const |
| { |
| auto contentLogicalHeight = computedHeight - blockConstraints.bordersPlusPadding(); |
| |
| // Height is never unsolved for tables. |
| if (isRenderTable()) |
| return contentLogicalHeight; |
| |
| bool fromAspectRatio = shouldComputeLogicalHeightFromAspectRatio(); |
| bool logicalHeightIsAuto = logicalHeight.isAuto() && !fromAspectRatio; |
| |
| if (!logicalHeightIsAuto) { |
| if (logicalHeight.isIntrinsic()) |
| return adjustContentBoxLogicalHeightForBoxSizing(computeIntrinsicLogicalContentHeightUsing(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())); |
| } |
| |
| bool logicalLeftIsAuto = blockConstraints.insetBefore().isAuto(); |
| bool logicalRightIsAuto = blockConstraints.insetAfter().isAuto(); |
| bool shrinkToFit = logicalLeftIsAuto || logicalRightIsAuto || !blockConstraints.alignmentAppliesStretch(ItemPosition::Stretch); |
| if (!shrinkToFit) |
| return std::max<LayoutUnit>(0, blockConstraints.availableContentSpace()); |
| |
| return contentLogicalHeight; |
| } |
| |
| LayoutUnit RenderBox::computePositionedLogicalHeightUsing(const Style::MinimumSize& originalLogicalHeight, LayoutUnit computedHeight, const PositionedLayoutConstraints& blockConstraints) const |
| { |
| auto contentLogicalHeight = computedHeight - blockConstraints.bordersPlusPadding(); |
| |
| // Height is never unsolved for tables. |
| if (isRenderTable()) |
| return contentLogicalHeight; |
| |
| auto logicalHeight = originalLogicalHeight; |
| |
| if (logicalHeight.isAuto()) { |
| if (shouldComputeLogicalHeightFromAspectRatio()) |
| logicalHeight = Style::MinimumSize::Fixed { computedHeight }; |
| else |
| logicalHeight = 0_css_px; |
| } |
| |
| if (logicalHeight.isIntrinsic()) |
| return adjustContentBoxLogicalHeightForBoxSizing(computeIntrinsicLogicalContentHeightUsing(logicalHeight, contentLogicalHeight, blockConstraints.bordersPlusPadding()).value_or(0_lu)); |
| return adjustContentBoxLogicalHeightForBoxSizing(Style::evaluate(logicalHeight, blockConstraints.containingSize())); |
| } |
| |
| LayoutUnit RenderBox::computePositionedLogicalHeightUsing(const Style::MaximumSize& logicalHeight, LayoutUnit computedHeight, const PositionedLayoutConstraints& blockConstraints) const |
| { |
| auto contentLogicalHeight = computedHeight - blockConstraints.bordersPlusPadding(); |
| |
| // Height is never unsolved for tables. |
| if (isRenderTable()) |
| return contentLogicalHeight; |
| |
| if (logicalHeight.isIntrinsic()) |
| return adjustContentBoxLogicalHeightForBoxSizing(computeIntrinsicLogicalContentHeightUsing(logicalHeight, contentLogicalHeight, blockConstraints.bordersPlusPadding()).value_or(0_lu)); |
| return adjustContentBoxLogicalHeightForBoxSizing(Style::evaluate(logicalHeight, blockConstraints.containingSize())); |
| } |
| |
| void RenderBox::computePositionedLogicalWidthReplaced(LogicalExtentComputedValues& computedValues) const |
| { |
| PositionedLayoutConstraints inlineConstraints(*this, LogicalBoxAxis::Inline); |
| inlineConstraints.computeInsets(); |
| |
| // NOTE: This value of width is final in that the min/max width calculations |
| // are dealt with in computeReplacedWidth(). This means that the steps to produce |
| // correct max/min in the non-replaced version, are not necessary. |
| computedValues.m_extent = computeReplacedLogicalWidth() + borderAndPaddingLogicalWidth(); |
| |
| inlineConstraints.resolvePosition(computedValues); |
| inlineConstraints.fixupLogicalLeftPosition(computedValues); |
| } |
| |
| void RenderBox::computePositionedLogicalHeightReplaced(LogicalExtentComputedValues& computedValues) const |
| { |
| PositionedLayoutConstraints blockConstraints(*this, LogicalBoxAxis::Block); |
| blockConstraints.computeInsets(); |
| |
| // NOTE: This value of height is final in that the min/max height calculations |
| // are dealt with in computeReplacedHeight(). This means that the steps to produce |
| // correct max/min in the non-replaced version, are not necessary. |
| computedValues.m_extent = computeReplacedLogicalHeight() + borderAndPaddingLogicalHeight(); |
| |
| blockConstraints.resolvePosition(computedValues); |
| blockConstraints.adjustLogicalTopWithLogicalHeightIfNeeded(computedValues); |
| } |
| |
| VisiblePosition 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 createVisiblePosition(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 createVisiblePosition(firstPositionInOrBeforeNode(nonPseudoElement())); |
| return createVisiblePosition(lastPositionInOrAfterNode(nonPseudoElement())); |
| } |
| } |
| |
| // Pass off to the closest child. |
| LayoutUnit minDist = LayoutUnit::max(); |
| RenderBox* closestRenderer = nullptr; |
| LayoutPoint adjustedPoint = point; |
| if (isRenderTableRow()) |
| adjustedPoint.moveBy(location()); |
| |
| for (auto& renderer : childrenOfType<RenderBox>(*this)) { |
| if (CheckedPtr fragmentedFlow = dynamicDowncast<RenderFragmentedFlow>(*this)) { |
| ASSERT(fragment || fragmentedFlow->isSkippedContent()); |
| if (!fragmentedFlow->objectShouldFragmentInFlowFragment(&renderer, fragment)) |
| continue; |
| } |
| |
| if ((!renderer.firstChild() && !renderer.isInline() && !is<RenderBlockFlow>(renderer)) |
| || (source == HitTestSource::Script ? renderer.style().visibility() : renderer.style().usedVisibility()) != Visibility::Visible) |
| continue; |
| |
| LayoutUnit top = renderer.borderTop() + renderer.paddingTop() + (is<RenderTableRow>(*this) ? 0_lu : renderer.y()); |
| LayoutUnit bottom = top + renderer.contentBoxHeight(); |
| LayoutUnit left = renderer.borderLeft() + renderer.paddingLeft() + (is<RenderTableRow>(*this) ? 0_lu : renderer.x()); |
| LayoutUnit right = left + renderer.contentBoxWidth(); |
| |
| if (point.x() <= right && point.x() >= left && point.y() <= top && point.y() >= bottom) { |
| if (is<RenderTableRow>(renderer)) |
| return renderer.positionForPoint(point + adjustedPoint - renderer.locationOffset(), source, fragment); |
| 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(adjustedPoint - closestRenderer->locationOffset(), source, fragment); |
| |
| return createVisiblePosition(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<RenderReplaced>(*this) || isLegend() || (element() && element()->isFormControlElement())) |
| return true; |
| |
| #if ENABLE(MATHML) |
| if (is<RenderMathMLBlock>(*this)) |
| return true; |
| #endif |
| |
| if (CheckedPtr renderBlock = dynamicDowncast<RenderBlock>(*this)) |
| return renderBlock->createsNewFormattingContext(); |
| |
| return false; |
| } |
| |
| void RenderBox::addVisualEffectOverflow() |
| { |
| bool hasBoxShadow = style().hasBoxShadow(); |
| bool hasBorderImageOutsets = style().hasBorderImageOutsets(); |
| bool hasOutline = outlineStyleForRepaint().hasOutlineInVisualOverflow(); |
| if (!hasBoxShadow && !hasBorderImageOutsets && !hasOutline) |
| return; |
| |
| addVisualOverflow(applyVisualEffectOverflow(borderBoxRect())); |
| |
| if (CheckedPtr fragmentedFlow = enclosingFragmentedFlow()) |
| fragmentedFlow->addFragmentsVisualEffectOverflow(*this); |
| } |
| |
| static void convertOutsetsToOverflowCoordinates(LayoutBoxExtent& 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) const |
| { |
| LayoutUnit overflowMinX = borderBox.x(); |
| LayoutUnit overflowMaxX = borderBox.maxX(); |
| LayoutUnit overflowMinY = borderBox.y(); |
| LayoutUnit overflowMaxY = borderBox.maxY(); |
| |
| // Compute box-shadow overflow first. |
| if (style().hasBoxShadow()) { |
| auto shadowOutsets = Style::shadowOutsetExtent(style().boxShadow()); |
| // 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()) { |
| LayoutUnit outlineSize { outlineStyleForRepaint().outlineSize() }; |
| 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); |
| } |
| // Add in the final overflow with shadows and outsets combined. |
| return LayoutRect(overflowMinX, overflowMinY, overflowMaxX - overflowMinX, overflowMaxY - overflowMinY); |
| } |
| |
| void RenderBox::addOverflowFromChild(const RenderBox& child, const LayoutSize& delta) |
| { |
| addOverflowFromChild(child, delta, flippedClientBoxRect()); |
| } |
| |
| void RenderBox::addOverflowFromChild(const RenderBox& child, const LayoutSize& delta, const LayoutRect& flippedClientRect) |
| { |
| // Never allow flow threads to propagate overflow up to a parent. |
| if (child.isRenderFragmentedFlow()) |
| return; |
| |
| CheckedPtr fragmentedFlow = enclosingFragmentedFlow(); |
| if (fragmentedFlow) |
| fragmentedFlow->addFragmentsOverflowFromChild(*this, child, delta); |
| |
| // 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. |
| LayoutRect childLayoutOverflowRect = child.layoutOverflowRectForPropagation(writingMode()); |
| childLayoutOverflowRect.move(delta); |
| addLayoutOverflow(childLayoutOverflowRect, flippedClientRect); |
| |
| 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; |
| |
| std::optional<LayoutRect> childVisualOverflowRect; |
| auto computeChildVisualOverflowRect = [&] () { |
| childVisualOverflowRect = child.visualOverflowRectForPropagation(writingMode()); |
| childVisualOverflowRect->move(delta); |
| }; |
| // 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 (child.hasSelfPaintingLayer() && !hasFilter()) |
| return; |
| } |
| if (!childVisualOverflowRect) |
| computeChildVisualOverflowRect(); |
| addVisualOverflow(*childVisualOverflowRect); |
| } |
| |
| 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; |
| } |
| |
| void RenderBox::addLayoutOverflow(const LayoutRect& rect) |
| { |
| addLayoutOverflow(rect, flippedClientBoxRect()); |
| } |
| |
| void RenderBox::addLayoutOverflow(const LayoutRect& rect, const LayoutRect& clientBox) |
| { |
| if (clientBox.contains(rect) || rect.isEmpty()) |
| return; |
| |
| // For overflow clip objects, we don't want to propagate overflow into unreachable areas. |
| LayoutRect overflowRect(rect); |
| if (hasPotentiallyScrollableOverflow() || isRenderView()) { |
| LayoutOptionalOutsets allowance = allowedLayoutOverflow(); |
| // Non-negative values indicate a limit, let's apply them. |
| if (allowance.top()) |
| overflowRect.shiftYEdgeTo(std::max(overflowRect.y(), clientBox.y() - *allowance.top())); |
| if (allowance.bottom()) |
| overflowRect.shiftMaxYEdgeTo(std::min(overflowRect.maxY(), clientBox.maxY() + *allowance.bottom())); |
| if (allowance.left()) |
| overflowRect.shiftXEdgeTo(std::max(overflowRect.x(), clientBox.x() - *allowance.left())); |
| if (allowance.right()) |
| overflowRect.shiftMaxXEdgeTo(std::min(overflowRect.maxX(), clientBox.maxX() + *allowance.right())); |
| |
| // Now re-test with the adjusted rectangle and see if it has become unreachable or fully |
| // contained. |
| if (clientBox.contains(overflowRect) || overflowRect.isEmpty()) |
| return; |
| } |
| |
| if (!m_overflow) |
| m_overflow = makeUnique<RenderOverflow>(clientBox, borderBoxRect()); |
| |
| m_overflow->addLayoutOverflow(overflowRect); |
| } |
| |
| void RenderBox::addVisualOverflow(const LayoutRect& rect) |
| { |
| LayoutRect borderBox = borderBoxRect(); |
| if (borderBox.contains(rect) || rect.isEmpty()) |
| return; |
| |
| if (!m_overflow) |
| m_overflow = makeUnique<RenderOverflow>(flippedClientBoxRect(), borderBox); |
| |
| m_overflow->addVisualOverflow(rect); |
| } |
| |
| void RenderBox::clearOverflow() |
| { |
| m_overflow = { }; |
| if (CheckedPtr fragmentedFlow = enclosingFragmentedFlow()) |
| fragmentedFlow->clearFragmentsOverflow(*this); |
| } |
| |
| 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().isIntrinsicOrLegacyIntrinsicOrAuto() |
| || (!style().logicalMaxHeight().isIntrinsicOrLegacyIntrinsicOrAuto() && !style().logicalMaxHeight().isNone() && (!style().logicalMaxHeight().isPercentOrCalculated() || percentageLogicalHeightIsResolvable())) |
| || (!style().logicalMinHeight().isIntrinsicOrLegacyIntrinsicOrAuto() && style().logicalMinHeight().isPositive() && (!style().logicalMinHeight().isPercentOrCalculated() || percentageLogicalHeightIsResolvable())); |
| } |
| |
| bool RenderBox::isUnsplittableForPagination() const |
| { |
| return isBlockLevelReplacedOrAtomicInline() |
| || (is<HTMLFormControlElement>(element()) && !is<HTMLFieldSetElement>(element())) |
| || hasUnsplittableScrollingOverflow() |
| || (parent() && isWritingModeRoot()) |
| || (isFloating() && style().pseudoElementType() == PseudoId::FirstLetter && style().initialLetterDrop() > 0) |
| || shouldApplySizeContainment(); |
| } |
| |
| LayoutUnit RenderBox::lineHeight() const |
| { |
| auto shouldUseLineHeightFromStyle = [&] { |
| if (is<RenderBlock>(*this)) |
| return true; |
| if (CheckedPtr listMarkerRenderer = dynamicDowncast<RenderListMarker>(*this)) |
| return !listMarkerRenderer->isImage(); |
| return false; |
| }; |
| if (shouldUseLineHeightFromStyle()) |
| return LayoutUnit::fromFloatCeil(firstLineStyle().computedLineHeight()); |
| |
| if (isBlockLevelReplacedOrAtomicInline()) |
| return marginBefore() + logicalHeight() + marginAfter(); |
| |
| return { }; |
| } |
| |
| RenderLayer* RenderBox::enclosingFloatPaintingLayer() const |
| { |
| for (auto& box : lineageOfType<RenderBox>(*this)) { |
| if (box.layer() && box.layer()->isSelfPaintingLayer()) |
| return box.layer(); |
| } |
| return nullptr; |
| } |
| |
| 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().hasAspectRatio() || 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(); |
| } |
| |
| bool RenderBox::shouldComputeLogicalHeightFromAspectRatio() const |
| { |
| if (shouldIgnoreAspectRatio()) |
| return false; |
| |
| if (shouldComputeLogicalWidthFromAspectRatioAndInsets(*this)) |
| return false; |
| |
| auto h = style().logicalHeight(); |
| return h.isAuto() || h.isIntrinsic() || (!isOutOfFlowPositioned() && h.isPercentOrCalculated() && !percentageLogicalHeightIsResolvable()); |
| } |
| |
| bool RenderBox::shouldComputeLogicalWidthFromAspectRatio() const |
| { |
| if (shouldIgnoreAspectRatio()) |
| return false; |
| |
| if (isGridItem()) { |
| if (is<RenderReplaced>(*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(); |
| } |
| |
| LayoutUnit RenderBox::computeLogicalWidthFromAspectRatioInternal() const |
| { |
| ASSERT(shouldComputeLogicalWidthFromAspectRatio()); |
| auto computedValues = computeLogicalHeight(logicalHeight(), logicalTop()); |
| LayoutUnit logicalHeightforAspectRatio = computedValues.m_extent; |
| |
| return inlineSizeFromAspectRatio(horizontalBorderAndPaddingExtent(), verticalBorderAndPaddingExtent(), style().logicalAspectRatio(), style().boxSizingForAspectRatio(), logicalHeightforAspectRatio, style().aspectRatio(), isRenderReplaced()); |
| } |
| |
| LayoutUnit RenderBox::computeLogicalWidthFromAspectRatio() const |
| { |
| auto logicalWidth = computeLogicalWidthFromAspectRatioInternal(); |
| LayoutUnit containerWidthInInlineDirection = std::max<LayoutUnit>(0, containingBlockLogicalWidthForContent()); |
| return constrainLogicalWidthByMinMax(logicalWidth, containerWidthInInlineDirection, *containingBlock(), AllowIntrinsic::No); |
| } |
| |
| bool RenderBox::isRenderReplacedWithIntrinsicRatio() const |
| { |
| if (auto* replaced = dynamicDowncast<RenderReplaced>(this)) |
| return replaced->computeIntrinsicAspectRatio(); |
| return false; |
| } |
| |
| std::optional<double> RenderBox::resolveAspectRatio() const |
| { |
| if (auto* replacedElement = dynamicDowncast<RenderReplaced>(this)) |
| return replacedElement->computeIntrinsicAspectRatio(); |
| if (style().hasAspectRatio()) |
| return style().logicalAspectRatio(); |
| ASSERT_NOT_REACHED(); |
| return std::nullopt; |
| } |
| |
| std::pair<LayoutUnit, LayoutUnit> RenderBox::computeMinMaxLogicalWidthFromAspectRatio() const |
| { |
| LayoutUnit transferredMinSize = LayoutUnit(); |
| LayoutUnit transferredMaxSize = LayoutUnit::max(); |
| std::optional<double> aspectRatio = resolveAspectRatio(); |
| if (!aspectRatio) |
| return { transferredMinSize, transferredMaxSize }; |
| |
| if (style().logicalMinHeight().isSpecified()) { |
| if (LayoutUnit blockMinSize = constrainLogicalHeightByMinMax(LayoutUnit(), std::nullopt); blockMinSize > LayoutUnit()) |
| transferredMinSize = inlineSizeFromAspectRatio(borderAndPaddingLogicalWidth(), borderAndPaddingLogicalHeight(), *aspectRatio, style().boxSizingForAspectRatio(), blockMinSize, style().aspectRatio(), isRenderReplaced()); |
| } |
| if (style().logicalMaxHeight().isSpecified()) { |
| 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<LayoutUnit, LayoutUnit> RenderBox::computeMinMaxLogicalHeightFromAspectRatio() const |
| { |
| LayoutUnit transferredMinSize = LayoutUnit(); |
| LayoutUnit transferredMaxSize = LayoutUnit::max(); |
| std::optional<double> aspectRatio = resolveAspectRatio(); |
| if (!aspectRatio) |
| return { transferredMinSize, transferredMaxSize }; |
| |
| if (style().logicalMinWidth().isSpecified()) { |
| 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()) { |
| 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 |
| { |
| return style().height().isPercentOrCalculated() || style().width().isPercentOrCalculated() |
| || style().maxHeight().isPercentOrCalculated() || style().maxWidth().isPercentOrCalculated() |
| || style().minHeight().isPercentOrCalculated() || style().minWidth().isPercentOrCalculated(); |
| } |
| |
| bool RenderBox::hasRelativeLogicalHeight() const |
| { |
| return style().logicalHeight().isPercentOrCalculated() |
| || style().logicalMinHeight().isPercentOrCalculated() |
| || style().logicalMaxHeight().isPercentOrCalculated(); |
| } |
| |
| bool RenderBox::hasRelativeLogicalWidth() const |
| { |
| return style().logicalWidth().isPercentOrCalculated() |
| || style().logicalMinWidth().isPercentOrCalculated() |
| || style().logicalMaxWidth().isPercentOrCalculated(); |
| } |
| |
| 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); |
| } |
| |
| LayoutUnit RenderBox::intrinsicLogicalWidth() const |
| { |
| return writingMode().isHorizontal() ? intrinsicSize().width() : intrinsicSize().height(); |
| } |
| |
| bool RenderBox::shouldIgnoreLogicalMinMaxWidthSizes() const |
| { |
| if (!isFlexItem()) |
| return false; |
| if (auto* flexBox = dynamicDowncast<RenderFlexibleBox>(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<RenderFlexibleBox>(parent())) |
| return flexBox->isComputingFlexBaseSizes() && writingMode().isHorizontal() != flexBox->isHorizontalFlow(); |
| ASSERT_NOT_REACHED(); |
| return false; |
| } |
| |
| std::optional<LayoutUnit> RenderBox::explicitIntrinsicInnerWidth() const |
| { |
| ASSERT(isHorizontalWritingMode() ? shouldApplySizeOrInlineSizeContainment() : shouldApplySizeContainment()); |
| |
| 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->value }; |
| |
| ASSERT(containIntrinsicWidth.isAutoAndNone()); |
| return { }; |
| } |
| |
| std::optional<LayoutUnit> RenderBox::explicitIntrinsicInnerHeight() const |
| { |
| ASSERT(isHorizontalWritingMode() ? shouldApplySizeContainment() : shouldApplySizeOrInlineSizeContainment()); |
| |
| 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->value }; |
| |
| 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().containsLayout() || !style().hasAutoUsedZIndex() || 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<RenderBlockFlow>(*ancestor); |
| if (!blockFlow) { |
| ASSERT_NOT_REACHED(); |
| break; |
| } |
| auto* floatingObjects = blockFlow->floatingObjectSet(); |
| if (!floatingObjects) |
| break; |
| auto blockFlowContainsThisFloat = false; |
| for (auto& floatingObject : *floatingObjects) { |
| 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<const RenderBox, std::unique_ptr<ShapeOutsideInfo>>; |
| static ShapeOutsideInfoMap& shapeOutsideInfoMap() |
| { |
| static NeverDestroyed<ShapeOutsideInfoMap> 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<ShapeOutsideInfo>(*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<RenderBox&>(*this)); |
| |
| if (isFlexItem() && downcast<RenderFlexibleBox>(*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 |