| /* |
| * Copyright (C) 1999 Lars Knoll ([email protected]) |
| * (C) 1999 Antti Koivisto ([email protected]) |
| * (C) 2005 Allan Sandfeld Jensen ([email protected]) |
| * (C) 2005, 2006 Samuel Weinig ([email protected]) |
| * Copyright (C) 2005-2025 Apple Inc. All rights reserved. |
| * Copyright (C) 2010-2018 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 "RenderBoxModelObject.h" |
| |
| #include "BitmapImage.h" |
| #include "BorderEdge.h" |
| #include "BorderPainter.h" |
| #include "BorderShape.h" |
| #include "CachedImage.h" |
| #include "ColorBlending.h" |
| #include "Document.h" |
| #include "FloatRoundedRect.h" |
| #include "GeometryUtilities.h" |
| #include "GraphicsContext.h" |
| #include "HTMLImageElement.h" |
| #include "HTMLNames.h" |
| #include "ImageBuffer.h" |
| #include "ImageQualityController.h" |
| #include "InlineIteratorInlineBox.h" |
| #include "LocalFrame.h" |
| #include "LocalFrameView.h" |
| #include "Path.h" |
| #include "RenderBlock.h" |
| #include "RenderBoxInlines.h" |
| #include "RenderBoxModelObjectInlines.h" |
| #include "RenderElementInlines.h" |
| #include "RenderElementStyleInlines.h" |
| #include "RenderFlexibleBox.h" |
| #include "RenderFragmentContainer.h" |
| #include "RenderInline.h" |
| #include "RenderLayer.h" |
| #include "RenderLayerBacking.h" |
| #include "RenderLayerCompositor.h" |
| #include "RenderLayerScrollableArea.h" |
| #include "RenderMultiColumnFlow.h" |
| #include "RenderObjectInlines.h" |
| #include "RenderTable.h" |
| #include "RenderTableRow.h" |
| #include "RenderText.h" |
| #include "RenderTextFragment.h" |
| #include "RenderTreeBuilder.h" |
| #include "RenderView.h" |
| #include "ScrollingConstraints.h" |
| #include "Settings.h" |
| #include "Styleable.h" |
| #include "TextBoxPainter.h" |
| #include "TransformState.h" |
| #include <wtf/NeverDestroyed.h> |
| #if ASSERT_ENABLED |
| #include <wtf/SetForScope.h> |
| #endif |
| #include <wtf/TZoneMallocInlines.h> |
| |
| #if PLATFORM(IOS_FAMILY) |
| #include <wtf/RuntimeApplicationChecks.h> |
| #endif |
| |
| namespace WebCore { |
| |
| using namespace HTMLNames; |
| |
| WTF_MAKE_TZONE_ALLOCATED_IMPL(RenderBoxModelObject); |
| |
| // The HashMap for storing continuation pointers. |
| // An inline can be split with blocks occuring in between the inline content. |
| // When this occurs we need a pointer to the next object. We can basically be |
| // split into a sequence of inlines and blocks. The continuation will either be |
| // an anonymous block (that houses other blocks) or it will be an inline flow. |
| // <b><i><p>Hello</p></i></b>. In this example the <i> will have a block as |
| // its continuation but the <b> will just have an inline as its continuation. |
| RenderBoxModelObject::ContinuationChainNode::ContinuationChainNode(RenderBoxModelObject& renderer) |
| : renderer(renderer) |
| { |
| } |
| |
| RenderBoxModelObject::ContinuationChainNode::~ContinuationChainNode() |
| { |
| if (next) { |
| ASSERT(previous); |
| ASSERT(next->previous == this); |
| next->previous = previous; |
| } |
| if (previous) { |
| ASSERT(previous->next == this); |
| previous->next = next; |
| } |
| } |
| |
| void RenderBoxModelObject::ContinuationChainNode::insertAfter(ContinuationChainNode& after) |
| { |
| ASSERT(!previous); |
| ASSERT(!next); |
| if ((next = after.next)) { |
| ASSERT(next->previous == &after); |
| next->previous = this; |
| } |
| previous = &after; |
| after.next = this; |
| } |
| |
| using ContinuationChainNodeMap = SingleThreadWeakHashMap<const RenderBoxModelObject, std::unique_ptr<RenderBoxModelObject::ContinuationChainNode>>; |
| |
| static ContinuationChainNodeMap& continuationChainNodeMap() |
| { |
| static NeverDestroyed<ContinuationChainNodeMap> map; |
| return map; |
| } |
| |
| using FirstLetterRemainingTextMap = SingleThreadWeakHashMap<const RenderBoxModelObject, SingleThreadWeakPtr<RenderTextFragment>>; |
| |
| static FirstLetterRemainingTextMap& firstLetterRemainingTextMap() |
| { |
| static NeverDestroyed<FirstLetterRemainingTextMap> map; |
| return map; |
| } |
| |
| void RenderBoxModelObject::styleWillChange(Style::Difference diff, const RenderStyle& newStyle) |
| { |
| const RenderStyle* oldStyle = hasInitializedStyle() ? &style() : nullptr; |
| |
| if (Style::AnchorPositionEvaluator::isAnchor(newStyle)) |
| view().registerAnchor(*this); |
| else if (oldStyle && Style::AnchorPositionEvaluator::isAnchor(*oldStyle)) |
| view().unregisterAnchor(*this); |
| |
| RenderLayerModelObject::styleWillChange(diff, newStyle); |
| } |
| |
| void RenderBoxModelObject::setSelectionState(HighlightState state) |
| { |
| if (state == HighlightState::Inside && selectionState() != HighlightState::None) |
| return; |
| |
| if ((state == HighlightState::Start && selectionState() == HighlightState::End) |
| || (state == HighlightState::End && selectionState() == HighlightState::Start)) |
| RenderLayerModelObject::setSelectionState(HighlightState::Both); |
| else |
| RenderLayerModelObject::setSelectionState(state); |
| |
| // FIXME: We should consider whether it is OK propagating to ancestor RenderInlines. |
| // This is a workaround for http://webkit.org/b/32123 |
| // The containing block can be null in case of an orphaned tree. |
| RenderBlock* containingBlock = this->containingBlock(); |
| if (containingBlock && !containingBlock->isRenderView()) |
| containingBlock->setSelectionState(state); |
| } |
| |
| void RenderBoxModelObject::contentChanged(ContentChangeType changeType, const std::optional<FloatRect>& dirtyRect) |
| { |
| if (!hasLayer()) |
| return; |
| |
| layer()->contentChanged(changeType, dirtyRect); |
| } |
| |
| bool RenderBoxModelObject::hasAcceleratedCompositing() const |
| { |
| return view().compositor().hasAcceleratedCompositing(); |
| } |
| |
| RenderBoxModelObject::RenderBoxModelObject(Type type, Element& element, RenderStyle&& style, OptionSet<TypeFlag> baseTypeFlags, TypeSpecificFlags typeSpecificFlags) |
| : RenderLayerModelObject(type, element, WTF::move(style), baseTypeFlags | TypeFlag::IsBoxModelObject, typeSpecificFlags) |
| { |
| ASSERT(isRenderBoxModelObject()); |
| } |
| |
| RenderBoxModelObject::RenderBoxModelObject(Type type, Document& document, RenderStyle&& style, OptionSet<TypeFlag> baseTypeFlags, TypeSpecificFlags typeSpecificFlags) |
| : RenderLayerModelObject(type, document, WTF::move(style), baseTypeFlags | TypeFlag::IsBoxModelObject, typeSpecificFlags) |
| { |
| ASSERT(isRenderBoxModelObject()); |
| } |
| |
| RenderBoxModelObject::~RenderBoxModelObject() |
| { |
| // Do not add any code here. Add it to willBeDestroyed() instead. |
| ASSERT(!continuation()); |
| } |
| |
| void RenderBoxModelObject::willBeDestroyed() |
| { |
| if (!renderTreeBeingDestroyed()) |
| view().imageQualityController().rendererWillBeDestroyed(*this); |
| |
| RenderLayerModelObject::willBeDestroyed(); |
| } |
| |
| bool RenderBoxModelObject::hasVisibleBoxDecorationStyle() const |
| { |
| return hasBackground() || style().hasVisibleBorderDecoration() || style().hasUsedAppearance() || style().hasBoxShadow(); |
| } |
| |
| void RenderBoxModelObject::updateFromStyle() |
| { |
| RenderLayerModelObject::updateFromStyle(); |
| |
| // Set the appropriate bits for a box model object. Since all bits are cleared in styleWillChange, |
| // we only check for bits that could possibly be set to true. |
| const auto& styleToUse = style(); |
| setHasVisibleBoxDecorations(hasVisibleBoxDecorationStyle()); |
| setInline(styleToUse.isDisplayInlineType()); |
| setPositionState(styleToUse.position()); |
| setHorizontalWritingMode(styleToUse.writingMode().isHorizontal()); |
| setPaintContainmentApplies(shouldApplyPaintContainment()); |
| if (writingMode().isBlockFlipped()) |
| view().frameView().setHasFlippedBlockRenderers(true); |
| } |
| |
| static LayoutSize accumulateInFlowPositionOffsets(const RenderBoxModelObject& child) |
| { |
| if (!child.isAnonymousBlock() || !child.isInFlowPositioned()) |
| return LayoutSize(); |
| LayoutSize offset; |
| for (RenderElement* parent = downcast<RenderBlock>(child).inlineContinuation(); parent; parent = parent->parent()) { |
| auto* parentRenderInline = dynamicDowncast<RenderInline>(*parent); |
| if (!parentRenderInline) |
| break; |
| if (parent->isInFlowPositioned()) |
| offset += parentRenderInline->offsetForInFlowPosition(); |
| } |
| return offset; |
| } |
| |
| static inline bool isOutOfFlowPositionedWithImplicitHeight(const RenderBoxModelObject& child) |
| { |
| return child.isOutOfFlowPositioned() && !child.style().logicalTop().isAuto() && !child.style().logicalBottom().isAuto(); |
| } |
| |
| RenderBlock* RenderBoxModelObject::containingBlockForAutoHeightDetectionGeneric(const auto& logicalHeight) const |
| { |
| // For percentage heights: The percentage is calculated with respect to the |
| // height of the generated box's containing block. If the height of the |
| // containing block is not specified explicitly (i.e., it depends on content |
| // height), and this element is not absolutely positioned, the used height is |
| // calculated as if 'auto' was specified. |
| if (!logicalHeight.isPercentOrCalculated() || isOutOfFlowPositioned()) |
| return nullptr; |
| |
| // Anonymous block boxes are ignored when resolving percentage values that |
| // would refer to it: the closest non-anonymous ancestor box is used instead. |
| auto* cb = containingBlock(); |
| while (cb && cb->isAnonymousForPercentageResolution() && !is<RenderView>(cb)) |
| cb = cb->containingBlock(); |
| if (!cb) |
| return nullptr; |
| |
| // Matching RenderBox::percentageLogicalHeightIsResolvable() by |
| // ignoring table cell's attribute value, where it says that 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. |
| if (cb->isRenderTableCell()) |
| return nullptr; |
| |
| // Match RenderBox::availableLogicalHeightUsing by special casing the layout |
| // view. The available height is taken from the frame. |
| if (cb->isRenderView()) |
| return nullptr; |
| |
| if (isOutOfFlowPositionedWithImplicitHeight(*cb)) |
| return nullptr; |
| |
| return cb; |
| } |
| |
| RenderBlock* RenderBoxModelObject::containingBlockForAutoHeightDetection(const Style::PreferredSize& logicalHeight) const |
| { |
| return containingBlockForAutoHeightDetectionGeneric(logicalHeight); |
| } |
| |
| RenderBlock* RenderBoxModelObject::containingBlockForAutoHeightDetection(const Style::MinimumSize& logicalHeight) const |
| { |
| return containingBlockForAutoHeightDetectionGeneric(logicalHeight); |
| } |
| |
| RenderBlock* RenderBoxModelObject::containingBlockForAutoHeightDetection(const Style::MaximumSize& logicalHeight) const |
| { |
| return containingBlockForAutoHeightDetectionGeneric(logicalHeight); |
| } |
| |
| DecodingMode RenderBoxModelObject::decodingModeForImageDraw(const Image& image, const PaintInfo& paintInfo) const |
| { |
| // Some document types force synchronous decoding. |
| if (document().isImageDocument()) |
| return DecodingMode::Synchronous; |
| |
| // A PaintBehavior may force synchronous decoding. |
| if (paintInfo.paintBehavior.contains(PaintBehavior::Snapshotting)) |
| return DecodingMode::Synchronous; |
| |
| |
| auto* bitmapImage = dynamicDowncast<BitmapImage>(image); |
| if (!bitmapImage) |
| return DecodingMode::Synchronous; |
| |
| auto defaultDecodingMode = [&]() -> DecodingMode { |
| if (paintInfo.paintBehavior.contains(PaintBehavior::ForceSynchronousImageDecode)) |
| return DecodingMode::Synchronous; |
| |
| // First tile paint. |
| if (paintInfo.paintBehavior.contains(PaintBehavior::DefaultAsynchronousImageDecode)) { |
| // No image has been painted in this element yet and it should not flicker with previous painting. |
| auto observer = bitmapImage->imageObserver(); |
| bool mayOverlapOtherClients = observer && observer->numberOfClients() > 1 && bitmapImage->currentFrameDecodingOptions().decodingMode() == DecodingMode::Asynchronous; |
| if (element() && !element()->hasEverPaintedImages() && !mayOverlapOtherClients) |
| return DecodingMode::Asynchronous; |
| } |
| |
| // FIXME: Calling isVisibleInViewport() is not cheap. Find a way to make this faster. |
| return isVisibleInViewport() ? DecodingMode::Synchronous : DecodingMode::Asynchronous; |
| }; |
| |
| if (RefPtr imgElement = dynamicDowncast<HTMLImageElement>(element())) { |
| // <img decoding="sync"> forces synchronous decoding. |
| if (imgElement->decodingMode() == DecodingMode::Synchronous) |
| return DecodingMode::Synchronous; |
| |
| // <img decoding="async"> forces asynchronous decoding but make sure this |
| // will not cause flickering. |
| if (imgElement->decodingMode() == DecodingMode::Asynchronous) { |
| if (bitmapImage->isAsyncDecodingEnabledForTesting() || bitmapImage->isAnimated()) |
| return DecodingMode::Asynchronous; |
| |
| // Choose a decodingMode such that the image does not flicker. |
| return defaultDecodingMode(); |
| } |
| } |
| |
| // isAsyncDecodingEnabledForTesting() forces async image decoding regardless of the size. |
| if (bitmapImage->isAsyncDecodingEnabledForTesting()) |
| return DecodingMode::Asynchronous; |
| |
| // Animated image case. |
| if (bitmapImage->isAnimated()) { |
| if (bitmapImage->isLargeForDecoding() && settings().animatedImageAsyncDecodingEnabled()) |
| return DecodingMode::Asynchronous; |
| return DecodingMode::Synchronous; |
| } |
| |
| // Large image case. |
| if (!(bitmapImage->isLargeForDecoding() && settings().largeImageAsyncDecodingEnabled())) |
| return DecodingMode::Synchronous; |
| |
| // Choose a decodingMode such that the image does not flicker. |
| return defaultDecodingMode(); |
| } |
| |
| LayoutSize RenderBoxModelObject::relativePositionOffset() const |
| { |
| auto* containingBlock = this->containingBlock(); |
| |
| auto& style = this->style(); |
| auto& left = style.left(); |
| auto& right = style.right(); |
| auto& top = style.top(); |
| auto& bottom = style.bottom(); |
| |
| auto offset = accumulateInFlowPositionOffsets(*this); |
| auto topFixed = top.tryFixed(); |
| auto leftFixed = left.tryFixed(); |
| if (topFixed && leftFixed && bottom.isAuto() && right.isAuto() && containingBlock->writingMode().isAnyLeftToRight()) { |
| offset.expand(leftFixed->resolveZoom(style.usedZoomForLength()), topFixed->resolveZoom(style.usedZoomForLength())); |
| return offset; |
| } |
| |
| // Objects that shrink to avoid floats normally use available line width when computing containing block width. However |
| // in the case of relative positioning using percentages, we can't do this. The offset should always be resolved using the |
| // available width of the containing block. Therefore we don't use containingBlockLogicalWidthForContent() here, but instead explicitly |
| // call availableWidth on our containing block. |
| if (!left.isAuto() || !right.isAuto()) { |
| auto availableWidth = [&] { |
| auto* renderBox = dynamicDowncast<RenderBox>(*this); |
| if (!renderBox || !renderBox->isGridItem()) |
| return containingBlock->contentBoxWidth(); |
| // For grid items the containing block is the grid area, so offsets should be resolved against that. |
| auto containingBlockContentWidth = renderBox->gridAreaContentWidth(containingBlock->writingMode()); |
| if (!containingBlockContentWidth || !*containingBlockContentWidth) { |
| ASSERT_NOT_REACHED(); |
| return containingBlock->contentBoxWidth(); |
| } |
| return **containingBlockContentWidth; |
| }; |
| if (!left.isAuto()) { |
| if (!right.isAuto() && !containingBlock->writingMode().isAnyLeftToRight()) |
| offset.setWidth(-Style::evaluate<LayoutUnit>(right, !right.isFixed() ? availableWidth() : 0_lu, style.usedZoomForLength())); |
| else |
| offset.expand(Style::evaluate<LayoutUnit>(left, !left.isFixed() ? availableWidth() : 0_lu, style.usedZoomForLength()), 0_lu); |
| } else if (!right.isAuto()) |
| offset.expand(-Style::evaluate<LayoutUnit>(right, !right.isFixed() ? availableWidth() : 0_lu, style.usedZoomForLength()), 0_lu); |
| } |
| |
| // If the containing block of a relatively positioned element does not |
| // specify a height, a percentage top or bottom offset should be resolved as |
| // auto. An exception to this is if the containing block has the WinIE quirk |
| // where <html> and <body> assume the size of the viewport. In this case, |
| // calculate the percent offset based on this height. |
| // See <https://bugs.webkit.org/show_bug.cgi?id=26396>. |
| // Another exception is a grid item, as the containing block is the grid area: |
| // https://drafts.csswg.org/css-grid/#grid-item-sizing |
| if (top.isAuto() && bottom.isAuto()) |
| return offset; |
| |
| auto containingBlockHasDefiniteHeight = !containingBlock->hasAutoHeightOrContainingBlockWithAutoHeight() || containingBlock->stretchesToViewport(); |
| auto availableHeight = [&] { |
| auto* renderBox = dynamicDowncast<RenderBox>(*this); |
| if (!renderBox || !renderBox->isGridItem()) |
| return containingBlock->contentBoxHeight(); |
| // For grid items the containing block is the grid area, so offsets should be resolved against that. |
| auto containingBlockContentHeight = renderBox->gridAreaContentHeight(containingBlock->style().writingMode()); |
| if (!containingBlockContentHeight || !*containingBlockContentHeight) { |
| ASSERT_NOT_REACHED(); |
| return containingBlock->contentBoxHeight(); |
| } |
| return **containingBlockContentHeight; |
| }; |
| if (!top.isAuto() && (!top.isPercentOrCalculated() || containingBlockHasDefiniteHeight)) { |
| // FIXME: The computation of the available height is repeated later for "bottom". |
| // We could refactor this and move it to some common code for both ifs, however moving it outside of the ifs |
| // is not possible as it'd cause performance regressions. |
| offset.expand(0_lu, Style::evaluate<LayoutUnit>(top, !top.isFixed() ? availableHeight() : 0_lu, style.usedZoomForLength())); |
| } else if (!bottom.isAuto() && (!bottom.isPercentOrCalculated() || containingBlockHasDefiniteHeight)) { |
| // FIXME: Check comment above for "top", it applies here too. |
| offset.expand(0_lu, -Style::evaluate<LayoutUnit>(bottom, !bottom.isFixed() ? availableHeight() : 0_lu, style.usedZoomForLength())); |
| } |
| return offset; |
| } |
| |
| LayoutPoint RenderBoxModelObject::adjustedPositionRelativeToOffsetParent(const LayoutPoint& startPoint) const |
| { |
| // If the element is the HTML body element or doesn't have a parent |
| // return 0 and stop this algorithm. |
| if (isBody() || !parent()) |
| return LayoutPoint(); |
| |
| LayoutPoint referencePoint = startPoint; |
| |
| // If the offsetParent of the element is null, or is the HTML body element, |
| // return the distance between the canvas origin and the left border edge |
| // of the element and stop this algorithm. |
| if (const RenderBoxModelObject* offsetParent = this->offsetParent()) { |
| if (auto* renderBox = dynamicDowncast<RenderBox>(*offsetParent); renderBox && !offsetParent->isBody() && !is<RenderTable>(*offsetParent)) |
| referencePoint.move(-renderBox->borderLeft(), -renderBox->borderTop()); |
| else if (auto* renderInline = dynamicDowncast<RenderInline>(*offsetParent)) { |
| // Inside inline formatting context both inflow and statically positioned out-of-flow boxes are positioned relative to the root block container. |
| auto topLeft = renderInline->firstInlineBoxTopLeft(); |
| if (isOutOfFlowPositioned()) { |
| auto& outOfFlowStyle = style(); |
| ASSERT(containingBlock()); |
| auto isHorizontalWritingMode = containingBlock() ? containingBlock()->writingMode().isHorizontal() : true; |
| if (!outOfFlowStyle.hasStaticInlinePosition(isHorizontalWritingMode)) |
| topLeft.setX(LayoutUnit { }); |
| if (!outOfFlowStyle.hasStaticBlockPosition(isHorizontalWritingMode)) |
| topLeft.setY(LayoutUnit { }); |
| } |
| referencePoint.move(-topLeft.x(), -topLeft.y()); |
| } |
| |
| if (!isOutOfFlowPositioned() || enclosingFragmentedFlow()) { |
| if (isRelativelyPositioned()) |
| referencePoint.move(relativePositionOffset()); |
| else if (isStickilyPositioned()) |
| referencePoint.move(stickyPositionOffset()); |
| |
| // CSS regions specification says that region flows should return the body element as their offsetParent. |
| // Since we will bypass the body’s renderer anyway, just end the loop if we encounter a region flow (named flow thread). |
| // See http://dev.w3.org/csswg/css-regions/#cssomview-offset-attributes |
| auto* ancestor = parent(); |
| while (ancestor != offsetParent) { |
| // FIXME: What are we supposed to do inside SVG content? |
| |
| if (auto* renderMultiColumnFlow = dynamicDowncast<RenderMultiColumnFlow>(*ancestor)) { |
| // We need to apply a translation based off what region we are inside. |
| if (auto* fragment = renderMultiColumnFlow->physicalTranslationFromFlowToFragment(referencePoint)) |
| referencePoint.moveBy(fragment->topLeftLocation()); |
| } else if (!isOutOfFlowPositioned()) { |
| if (auto* renderBox = dynamicDowncast<RenderBox>(*ancestor); renderBox && !is<RenderTableRow>(*ancestor)) |
| referencePoint.moveBy(renderBox->topLeftLocation()); |
| } |
| |
| ancestor = ancestor->parent(); |
| } |
| |
| if (auto* renderBox = dynamicDowncast<RenderBox>(*offsetParent); renderBox && offsetParent->isBody() && !offsetParent->isPositioned()) |
| referencePoint.moveBy(renderBox->topLeftLocation()); |
| } |
| } |
| |
| return referencePoint; |
| } |
| |
| std::pair<const RenderBox&, const RenderLayer*> RenderBoxModelObject::enclosingClippingBoxForStickyPosition() const |
| { |
| ASSERT(isStickilyPositioned()); |
| CheckedPtr clipLayer = hasLayer() ? layer()->enclosingOverflowClipLayer(ExcludeSelf) : nullptr; |
| const RenderBox& box = clipLayer ? downcast<RenderBox>(clipLayer->renderer()) : view(); |
| return { box, clipLayer }; |
| } |
| |
| void RenderBoxModelObject::computeStickyPositionConstraints(StickyPositionViewportConstraints& constraints, const FloatRect& constrainingRect) const |
| { |
| constraints.setConstrainingRectAtLastLayout(constrainingRect); |
| |
| // Do not use anonymous containing blocks to determine sticky constraints. We want the size |
| // of the first true containing block, because that is what imposes the limitation on the |
| // movement of stickily positioned items. |
| RenderBlock* containingBlock = this->containingBlock(); |
| while (containingBlock && (!is<RenderBlock>(*containingBlock) || containingBlock->isAnonymousBlock())) |
| containingBlock = containingBlock->containingBlock(); |
| ASSERT(containingBlock); |
| |
| auto [enclosingClippingBox, enclosingClippingLayer] = enclosingClippingBoxForStickyPosition(); |
| |
| LayoutRect containerContentRect; |
| if (!enclosingClippingLayer || (containingBlock != &enclosingClippingBox)) { |
| // In this case either the scrolling element is the view or there is another containing block in |
| // the hierarchy between this stickily positioned item and its scrolling ancestor. In both cases, |
| // we use the content box rectangle of the containing block, which is what should constrain the |
| // movement. |
| containerContentRect = containingBlock->computedCSSContentBoxRect(); |
| } else { |
| containerContentRect = containingBlock->layoutOverflowRect(); |
| containerContentRect.contract(LayoutBoxExtent { |
| containingBlock->computedCSSPaddingTop(), containingBlock->computedCSSPaddingRight(), |
| containingBlock->computedCSSPaddingBottom(), containingBlock->computedCSSPaddingLeft() }); |
| } |
| |
| LayoutUnit maxWidth = containingBlock->contentBoxLogicalWidth(); |
| |
| const auto& zoomFactor = style().usedZoomForLength(); |
| |
| // Sticky positioned element ignore any override logical width on the containing block (as they don't call |
| // containingBlockLogicalWidthForContent). It's unclear whether this is totally fine. |
| LayoutBoxExtent minMargin( |
| Style::evaluateMinimum<LayoutUnit>(style().marginTop(), maxWidth, zoomFactor), |
| Style::evaluateMinimum<LayoutUnit>(style().marginRight(), maxWidth, zoomFactor), |
| Style::evaluateMinimum<LayoutUnit>(style().marginBottom(), maxWidth, zoomFactor), |
| Style::evaluateMinimum<LayoutUnit>(style().marginLeft(), maxWidth, zoomFactor) |
| ); |
| |
| // Compute the container-relative area within which the sticky element is allowed to move. |
| containerContentRect.contract(minMargin); |
| |
| // Finally compute container rect relative to the scrolling ancestor. We pass an empty |
| // mode here, because sticky positioning should ignore transforms. |
| FloatRect containerRectRelativeToScrollingAncestor = containingBlock->localToContainerQuad(FloatRect(containerContentRect), &enclosingClippingBox, { } /* ignore transforms */).boundingBox(); |
| if (enclosingClippingLayer) { |
| FloatPoint containerLocationRelativeToScrollingAncestor = containerRectRelativeToScrollingAncestor.location() - |
| FloatSize(enclosingClippingBox.borderLeft() + enclosingClippingBox.paddingLeft(), |
| enclosingClippingBox.borderTop() + enclosingClippingBox.paddingTop()); |
| if (&enclosingClippingBox != containingBlock) { |
| if (auto* scrollableArea = enclosingClippingLayer->scrollableArea()) |
| containerLocationRelativeToScrollingAncestor += scrollableArea->scrollOffset(); |
| } |
| containerRectRelativeToScrollingAncestor.setLocation(containerLocationRelativeToScrollingAncestor); |
| } |
| constraints.setContainingBlockRect(containerRectRelativeToScrollingAncestor); |
| |
| // Now compute the sticky box rect, also relative to the scrolling ancestor. |
| LayoutRect stickyBoxRect = frameRectForStickyPositioning(); |
| |
| // Ideally, it would be possible to call this->localToContainerQuad to determine the frame |
| // rectangle in the coordinate system of the scrolling ancestor, but localToContainerQuad |
| // itself depends on sticky positioning! Instead, start from the parent but first adjusting |
| // the rectangle for the writing mode of this stickily-positioned element. We also pass an |
| // empty mode here because sticky positioning should ignore transforms. |
| // |
| // FIXME: It would also be nice to not have to call localToContainerQuad again since we |
| // have already done a similar call to move from the containing block to the scrolling |
| // ancestor above, but localToContainerQuad takes care of a lot of complex situations |
| // involving inlines, tables, and transformations. |
| if (CheckedPtr parentBox = dynamicDowncast<RenderBox>(*parent())) |
| parentBox->flipForWritingMode(stickyBoxRect); |
| auto stickyBoxRelativeToScrollingAncestor = parent()->localToContainerQuad(FloatRect(stickyBoxRect), &enclosingClippingBox, { } /* ignore transforms */).boundingBox(); |
| |
| if (enclosingClippingLayer) { |
| stickyBoxRelativeToScrollingAncestor.move(-FloatSize(enclosingClippingBox.borderLeft() + enclosingClippingBox.paddingLeft(), |
| enclosingClippingBox.borderTop() + enclosingClippingBox.paddingTop())); |
| |
| if (&enclosingClippingBox != parent()) { |
| if (auto* scrollableArea = enclosingClippingLayer->scrollableArea()) |
| stickyBoxRelativeToScrollingAncestor.moveBy(scrollableArea->scrollOffset()); |
| } |
| } |
| constraints.setStickyBoxRect(stickyBoxRelativeToScrollingAncestor); |
| |
| if (!style().left().isAuto()) { |
| constraints.setLeftOffset(Style::evaluate<float>(style().left(), constrainingRect.width(), style().usedZoomForLength())); |
| constraints.addAnchorEdge(ViewportConstraints::AnchorEdgeLeft); |
| } |
| |
| if (!style().right().isAuto()) { |
| constraints.setRightOffset(Style::evaluate<float>(style().right(), constrainingRect.width(), style().usedZoomForLength())); |
| constraints.addAnchorEdge(ViewportConstraints::AnchorEdgeRight); |
| } |
| |
| if (!style().top().isAuto()) { |
| constraints.setTopOffset(Style::evaluate<float>(style().top(), constrainingRect.height(), style().usedZoomForLength())); |
| constraints.addAnchorEdge(ViewportConstraints::AnchorEdgeTop); |
| } |
| |
| if (!style().bottom().isAuto()) { |
| constraints.setBottomOffset(Style::evaluate<float>(style().bottom(), constrainingRect.height(), style().usedZoomForLength())); |
| constraints.addAnchorEdge(ViewportConstraints::AnchorEdgeBottom); |
| } |
| |
| if (constraints.hasAnchorEdge(ViewportConstraints::AnchorEdgeRight) && constraints.hasAnchorEdge(ViewportConstraints::AnchorEdgeLeft)) { |
| float availableSpace = constrainingRect.width() - constraints.leftOffset() - constraints.rightOffset(); |
| if (constraints.stickyBoxRect().width() > availableSpace) { |
| float delta = constraints.stickyBoxRect().width() - availableSpace; |
| if (containingBlock->writingMode().isAnyLeftToRight()) |
| constraints.setRightOffset(constraints.rightOffset() - delta); |
| else |
| constraints.setLeftOffset(constraints.leftOffset() - delta); |
| } |
| } |
| |
| if (constraints.hasAnchorEdge(ViewportConstraints::AnchorEdgeBottom) && constraints.hasAnchorEdge(ViewportConstraints::AnchorEdgeTop)) { |
| float availableSpace = constrainingRect.height() - constraints.topOffset() - constraints.bottomOffset(); |
| if (constraints.stickyBoxRect().height() > availableSpace) { |
| float delta = constraints.stickyBoxRect().height() - availableSpace; |
| if (containingBlock->writingMode().isAnyTopToBottom()) |
| constraints.setBottomOffset(constraints.bottomOffset() - delta); |
| else |
| constraints.setTopOffset(constraints.topOffset() - delta); |
| } |
| } |
| } |
| |
| FloatRect RenderBoxModelObject::constrainingRectForStickyPosition() const |
| { |
| CheckedPtr enclosingClippingLayer = hasLayer() ? layer()->enclosingOverflowClipLayer(ExcludeSelf) : nullptr; |
| |
| if (enclosingClippingLayer) { |
| RenderBox& enclosingClippingBox = downcast<RenderBox>(enclosingClippingLayer->renderer()); |
| LayoutRect clipRect = enclosingClippingBox.overflowClipRect({ }); |
| clipRect.contract(LayoutSize(enclosingClippingBox.paddingLeft() + enclosingClippingBox.paddingRight(), |
| enclosingClippingBox.paddingTop() + enclosingClippingBox.paddingBottom())); |
| |
| FloatRect constrainingRect = enclosingClippingBox.localToContainerQuad(FloatRect(clipRect), &view()).boundingBox(); |
| |
| auto* scrollableArea = enclosingClippingLayer->scrollableArea(); |
| FloatPoint scrollOffset; |
| if (scrollableArea) |
| scrollOffset = FloatPoint() + scrollableArea->scrollOffset(); |
| |
| float scrollbarOffset = 0; |
| if (scrollableArea && enclosingClippingBox.hasLayer() && enclosingClippingBox.shouldPlaceVerticalScrollbarOnLeft()) |
| scrollbarOffset = scrollableArea->verticalScrollbarWidth(OverlayScrollbarSizeRelevancy::IgnoreOverlayScrollbarSize, isHorizontalWritingMode()); |
| |
| constrainingRect.setLocation(FloatPoint(scrollOffset.x() + scrollbarOffset, scrollOffset.y())); |
| return constrainingRect; |
| } |
| |
| return view().frameView().rectForFixedPositionLayout(); |
| } |
| |
| LayoutSize RenderBoxModelObject::stickyPositionOffset() const |
| { |
| FloatRect constrainingRect = constrainingRectForStickyPosition(); |
| StickyPositionViewportConstraints constraints; |
| computeStickyPositionConstraints(constraints, constrainingRect); |
| |
| // The sticky offset is physical, so we can just return the delta computed in absolute coords (though it may be wrong with transforms). |
| return LayoutSize(constraints.computeStickyOffset(constrainingRect)); |
| } |
| |
| LayoutSize RenderBoxModelObject::offsetForInFlowPosition() const |
| { |
| if (isRelativelyPositioned()) |
| return relativePositionOffset(); |
| |
| if (isStickilyPositioned()) |
| return stickyPositionOffset(); |
| |
| return LayoutSize(); |
| } |
| |
| LayoutUnit RenderBoxModelObject::offsetLeft() const |
| { |
| // Note that RenderInline and RenderBox override this to pass a different |
| // startPoint to adjustedPositionRelativeToOffsetParent. |
| return adjustedPositionRelativeToOffsetParent(LayoutPoint()).x(); |
| } |
| |
| LayoutUnit RenderBoxModelObject::offsetTop() const |
| { |
| // Note that RenderInline and RenderBox override this to pass a different |
| // startPoint to adjustedPositionRelativeToOffsetParent. |
| return adjustedPositionRelativeToOffsetParent(LayoutPoint()).y(); |
| } |
| |
| InterpolationQuality RenderBoxModelObject::chooseInterpolationQuality(GraphicsContext& context, Image& image, const void* layer, const LayoutSize& size) const |
| { |
| return view().imageQualityController().chooseInterpolationQuality(context, const_cast<RenderBoxModelObject*>(this), image, layer, size); |
| } |
| |
| void RenderBoxModelObject::paintMaskForTextFillBox(GraphicsContext& context, const FloatRect& paintRect, const InlineIterator::InlineBoxIterator& inlineBox, const LayoutRect& scrolledPaintRect) |
| { |
| // Now add the text to the clip. We do this by painting using a special paint phase that signals to |
| // the painter it should just modify the clip. |
| PaintInfo maskInfo(context, LayoutRect { paintRect }, PaintPhase::TextClip, PaintBehavior::ForceBlackText); |
| if (inlineBox) { |
| auto paintOffset = scrolledPaintRect.location() - toLayoutSize(LayoutPoint(inlineBox->visualRectIgnoringBlockDirection().location())); |
| |
| for (auto box = inlineBox->firstLeafBox(), end = inlineBox->endLeafBox(); box != end; box.traverseLineRightwardOnLine()) { |
| if (!box->isText()) |
| continue; |
| TextBoxPainter { box->modernPath().inlineContent(), box->modernPath().box(), box->modernPath().box().style(), maskInfo, paintOffset }.paint(); |
| } |
| return; |
| } |
| |
| auto* renderBox = dynamicDowncast<RenderBox>(*this); |
| auto localOffset = renderBox ? renderBox->locationOffset() : LayoutSize(); |
| paint(maskInfo, scrolledPaintRect.location() - localOffset); |
| } |
| |
| static inline LayoutUnit resolveWidthForRatio(LayoutUnit height, const LayoutSize& intrinsicRatio) |
| { |
| return height * intrinsicRatio.width() / intrinsicRatio.height(); |
| } |
| |
| static inline LayoutUnit resolveHeightForRatio(LayoutUnit width, const LayoutSize& intrinsicRatio) |
| { |
| return width * intrinsicRatio.height() / intrinsicRatio.width(); |
| } |
| |
| static inline LayoutSize resolveAgainstIntrinsicWidthOrHeightAndRatio(const LayoutSize& size, const LayoutSize& intrinsicRatio, LayoutUnit useWidth, LayoutUnit useHeight) |
| { |
| if (intrinsicRatio.isEmpty()) { |
| if (useWidth) |
| return LayoutSize(useWidth, size.height()); |
| return LayoutSize(size.width(), useHeight); |
| } |
| |
| if (useWidth) |
| return LayoutSize(useWidth, resolveHeightForRatio(useWidth, intrinsicRatio)); |
| return LayoutSize(resolveWidthForRatio(useHeight, intrinsicRatio), useHeight); |
| } |
| |
| static inline LayoutSize resolveAgainstIntrinsicRatio(const LayoutSize& size, const LayoutSize& intrinsicRatio) |
| { |
| // Two possible solutions: (size.width(), solutionHeight) or (solutionWidth, size.height()) |
| // "... must be assumed to be the largest dimensions..." = easiest answer: the rect with the largest surface area. |
| |
| LayoutUnit solutionWidth = resolveWidthForRatio(size.height(), intrinsicRatio); |
| LayoutUnit solutionHeight = resolveHeightForRatio(size.width(), intrinsicRatio); |
| if (solutionWidth <= size.width()) { |
| if (solutionHeight <= size.height()) { |
| // If both solutions fit, choose the one covering the larger area. |
| LayoutUnit areaOne = solutionWidth * size.height(); |
| LayoutUnit areaTwo = size.width() * solutionHeight; |
| if (areaOne < areaTwo) |
| return LayoutSize(size.width(), solutionHeight); |
| return LayoutSize(solutionWidth, size.height()); |
| } |
| |
| // Only the first solution fits. |
| return LayoutSize(solutionWidth, size.height()); |
| } |
| |
| // Only the second solution fits, assert that. |
| ASSERT(solutionHeight <= size.height()); |
| return LayoutSize(size.width(), solutionHeight); |
| } |
| |
| LayoutSize RenderBoxModelObject::calculateImageIntrinsicDimensions(StyleImage* image, const LayoutSize& positioningAreaSize, ScaleByUsedZoom scaleByUsedZoom) const |
| { |
| // A generated image without a fixed size, will always return the container size as intrinsic size. |
| if (!image->imageHasNaturalDimensions()) |
| return LayoutSize(positioningAreaSize.width(), positioningAreaSize.height()); |
| |
| float intrinsicWidth = 0; |
| float intrinsicHeight = 0; |
| FloatSize intrinsicRatio; |
| image->computeIntrinsicDimensions(this, intrinsicWidth, intrinsicHeight, intrinsicRatio); |
| |
| LayoutSize resolvedSize(intrinsicWidth, intrinsicHeight); |
| LayoutSize minimumSize(resolvedSize.width() > 0 ? 1 : 0, resolvedSize.height() > 0 ? 1 : 0); |
| |
| if (scaleByUsedZoom == ScaleByUsedZoom::Yes) |
| resolvedSize.scale(style().usedZoom()); |
| resolvedSize.clampToMinimumSize(minimumSize); |
| |
| if (!resolvedSize.isEmpty()) |
| return resolvedSize; |
| |
| // If the image has one of either an intrinsic width or an intrinsic height: |
| // * and an intrinsic aspect ratio, then the missing dimension is calculated from the given dimension and the ratio. |
| // * and no intrinsic aspect ratio, then the missing dimension is assumed to be the size of the rectangle that |
| // establishes the coordinate system for the 'background-position' property. |
| if (resolvedSize.width() > 0 || resolvedSize.height() > 0) |
| return resolveAgainstIntrinsicWidthOrHeightAndRatio(positioningAreaSize, LayoutSize(intrinsicRatio), resolvedSize.width(), resolvedSize.height()); |
| |
| // If the image has no intrinsic dimensions and has an intrinsic ratio the dimensions must be assumed to be the |
| // largest dimensions at that ratio such that neither dimension exceeds the dimensions of the rectangle that |
| // establishes the coordinate system for the 'background-position' property. |
| if (!intrinsicRatio.isEmpty()) |
| return resolveAgainstIntrinsicRatio(positioningAreaSize, LayoutSize(intrinsicRatio)); |
| |
| // If the image has no intrinsic ratio either, then the dimensions must be assumed to be the rectangle that |
| // establishes the coordinate system for the 'background-position' property. |
| return positioningAreaSize; |
| } |
| |
| bool RenderBoxModelObject::fixedBackgroundPaintsInLocalCoordinates() const |
| { |
| if (!isDocumentElementRenderer()) |
| return false; |
| |
| if (view().frameView().paintBehavior().contains(PaintBehavior::FlattenCompositingLayers)) |
| return false; |
| |
| CheckedPtr rootLayer = view().layer(); |
| if (!rootLayer || !rootLayer->isComposited()) |
| return false; |
| |
| return rootLayer->backing()->backgroundLayerPaintsFixedRootBackground(); |
| } |
| |
| bool RenderBoxModelObject::borderObscuresBackgroundEdge(const FloatSize& contextScale) const |
| { |
| auto edges = borderEdges(style(), document().deviceScaleFactor()); |
| |
| for (auto side : allBoxSides) { |
| auto& currEdge = edges.at(side); |
| // FIXME: for vertical text |
| float axisScale = (side == BoxSide::Top || side == BoxSide::Bottom) ? contextScale.height() : contextScale.width(); |
| if (!currEdge.obscuresBackgroundEdge(axisScale)) |
| return false; |
| } |
| |
| return true; |
| } |
| |
| bool RenderBoxModelObject::borderObscuresBackground() const |
| { |
| if (!style().hasBorder()) |
| return false; |
| |
| // Bail if we have any border-image for now. We could look at the image alpha to improve this. |
| if (!style().borderImageSource().isNone()) |
| return false; |
| |
| auto edges = borderEdges(style(), document().deviceScaleFactor()); |
| |
| for (auto side : allBoxSides) { |
| if (!edges.at(side).obscuresBackground()) |
| return false; |
| } |
| |
| return true; |
| } |
| |
| BorderShape RenderBoxModelObject::borderShapeForContentClipping(const LayoutRect& borderBoxRect, RectEdges<bool> closedEdges) const |
| { |
| auto borderWidths = this->borderWidths(); |
| auto padding = this->padding(); |
| |
| auto contentBoxInsets = RectEdges<LayoutUnit> { |
| borderWidths.top() + padding.top(), |
| borderWidths.right() + padding.right(), |
| borderWidths.bottom() + padding.bottom(), |
| borderWidths.left() + padding.left(), |
| }; |
| |
| return BorderShape::shapeForBorderRect(style(), borderBoxRect, contentBoxInsets, closedEdges); |
| } |
| |
| LayoutUnit RenderBoxModelObject::containingBlockLogicalWidthForContent() const |
| { |
| if (auto* containingBlock = this->containingBlock()) |
| return containingBlock->contentBoxLogicalWidth(); |
| return { }; |
| } |
| |
| RenderBoxModelObject* RenderBoxModelObject::continuation() const |
| { |
| if (!hasContinuationChainNode()) |
| return nullptr; |
| |
| auto& continuationChainNode = *continuationChainNodeMap().get(*this); |
| if (!continuationChainNode.next) |
| return nullptr; |
| return continuationChainNode.next->renderer.get(); |
| } |
| |
| RenderInline* RenderBoxModelObject::inlineContinuation() const |
| { |
| if (!hasContinuationChainNode()) |
| return nullptr; |
| |
| for (auto* next = continuationChainNodeMap().get(*this)->next; next; next = next->next) { |
| if (auto* renderInline = dynamicDowncast<RenderInline>(*next->renderer)) |
| return renderInline; |
| } |
| return nullptr; |
| } |
| |
| void RenderBoxModelObject::forRendererAndContinuations(RenderBoxModelObject& renderer, const std::function<void(RenderBoxModelObject&)>& function) |
| { |
| function(renderer); |
| if (!renderer.hasContinuationChainNode()) |
| return; |
| |
| for (auto* next = continuationChainNodeMap().get(renderer)->next; next; next = next->next) { |
| if (!next->renderer) |
| continue; |
| function(*next->renderer); |
| } |
| } |
| |
| RenderBoxModelObject::ContinuationChainNode* RenderBoxModelObject::continuationChainNode() const |
| { |
| return continuationChainNodeMap().get(*this); |
| } |
| |
| void RenderBoxModelObject::insertIntoContinuationChainAfter(RenderBoxModelObject& afterRenderer) |
| { |
| ASSERT(isContinuation()); |
| ASSERT(!continuationChainNodeMap().contains(*this)); |
| |
| auto& after = afterRenderer.ensureContinuationChainNode(); |
| ensureContinuationChainNode().insertAfter(after); |
| } |
| |
| void RenderBoxModelObject::removeFromContinuationChain() |
| { |
| ASSERT(hasContinuationChainNode()); |
| ASSERT(continuationChainNodeMap().contains(*this)); |
| setHasContinuationChainNode(false); |
| continuationChainNodeMap().remove(*this); |
| } |
| |
| auto RenderBoxModelObject::ensureContinuationChainNode() -> ContinuationChainNode& |
| { |
| setHasContinuationChainNode(true); |
| return *continuationChainNodeMap().ensure(*this, [&] { |
| return makeUnique<ContinuationChainNode>(*this); |
| }).iterator->value; |
| } |
| |
| RenderTextFragment* RenderBoxModelObject::firstLetterRemainingText() const |
| { |
| if (!isFirstLetter()) |
| return nullptr; |
| return firstLetterRemainingTextMap().get(*this); |
| } |
| |
| void RenderBoxModelObject::setFirstLetterRemainingText(RenderTextFragment& remainingText) |
| { |
| ASSERT(isFirstLetter()); |
| firstLetterRemainingTextMap().set(*this, remainingText); |
| } |
| |
| void RenderBoxModelObject::clearFirstLetterRemainingText() |
| { |
| ASSERT(isFirstLetter()); |
| firstLetterRemainingTextMap().remove(*this); |
| } |
| |
| void RenderBoxModelObject::mapAbsoluteToLocalPoint(OptionSet<MapCoordinatesMode> mode, TransformState& transformState) const |
| { |
| RenderElement* container = this->container(); |
| if (!container) |
| return; |
| |
| container->mapAbsoluteToLocalPoint(mode, transformState); |
| |
| LayoutSize containerOffset = offsetFromContainer(*container, LayoutPoint()); |
| |
| pushOntoTransformState(transformState, mode, nullptr, container, containerOffset, false); |
| } |
| |
| bool RenderBoxModelObject::hasRunningAcceleratedAnimations() const |
| { |
| if (auto styleable = Styleable::fromRenderer(*this)) |
| return styleable->hasRunningAcceleratedAnimations(); |
| return false; |
| } |
| |
| void RenderBoxModelObject::collectAbsoluteQuadsForContinuation(Vector<FloatQuad>& quads, bool* wasFixed) const |
| { |
| ASSERT(continuation()); |
| for (auto* nextInContinuation = this->continuation(); nextInContinuation; nextInContinuation = nextInContinuation->continuation()) { |
| if (auto blockBox = dynamicDowncast<RenderBlock>(*nextInContinuation); blockBox && blockBox->height() && blockBox->width()) { |
| // For blocks inside inlines, we include margins so that we run right up to the inline boxes |
| // above and below us (thus getting merged with them to form a single irregular shape). |
| auto logicalRect = FloatRect { 0, -blockBox->collapsedMarginBefore(), blockBox->width(), |
| blockBox->height() + blockBox->collapsedMarginBefore() + blockBox->collapsedMarginAfter() }; |
| nextInContinuation->absoluteQuadsIgnoringContinuation(logicalRect, quads, wasFixed); |
| continue; |
| } |
| nextInContinuation->absoluteQuadsIgnoringContinuation({ }, quads, wasFixed); |
| } |
| } |
| |
| void RenderBoxModelObject::applyTransform(TransformationMatrix&, const RenderStyle&, const FloatRect&, OptionSet<Style::TransformResolverOption>) const |
| { |
| // applyTransform() is only used through RenderLayer*, which only invokes this for RenderBox derived renderers, thus not for |
| // RenderInline/RenderLineBreak - the other two renderers that inherit from RenderBoxModelObject. |
| ASSERT_NOT_REACHED(); |
| } |
| |
| bool RenderBoxModelObject::requiresLayer() const |
| { |
| return isDocumentElementRenderer() || isPositioned() || createsGroup() || hasTransformRelatedProperty() || hasHiddenBackface() || hasReflection() || requiresRenderingConsolidationForViewTransition() || isRenderViewTransitionCapture(); |
| } |
| |
| void RenderBoxModelObject::removeOutOfFlowBoxesIfNeededOnStyleChange(RenderBlock& delegateBlock, const RenderStyle& oldStyle, const RenderStyle& newStyle) |
| { |
| auto wasContainingBlockForFixedContent = canContainFixedPositionObjects(&oldStyle); |
| auto wasContainingBlockForAbsoluteContent = canContainAbsolutelyPositionedObjects(&oldStyle); |
| auto isContainingBlockForFixedContent = canContainFixedPositionObjects(&newStyle); |
| auto isContainingBlockForAbsoluteContent = canContainAbsolutelyPositionedObjects(&newStyle); |
| |
| // FIXME: If an inline becomes a containing block, but the delegate was already one (or vice-versa), |
| // then we don't really need to remove the out-of-flows from the delegate only for them to be re-added |
| // to the same spot. We would need to correctly mark for layout instead though. |
| |
| if ((wasContainingBlockForFixedContent && !isContainingBlockForFixedContent) || (wasContainingBlockForAbsoluteContent && !isContainingBlockForAbsoluteContent)) { |
| // We are no longer the containing block for out-of-flow descendants. |
| delegateBlock.removeOutOfFlowBoxes({ }, RenderBlock::ContainingBlockState::NewContainingBlock); |
| } |
| |
| if (!wasContainingBlockForFixedContent && isContainingBlockForFixedContent) { |
| // We are a new containing block for all out-of-flow boxes. Find first ancestor that has our fixed positioned boxes and remove them. |
| // They will be inserted into our positioned objects list during their static position layout. |
| if (CheckedPtr containingBlock = RenderObject::containingBlockForPositionType(PositionType::Fixed, *this)) |
| containingBlock->removeOutOfFlowBoxes(&delegateBlock, RenderBlock::ContainingBlockState::NewContainingBlock); |
| } |
| |
| if (!wasContainingBlockForAbsoluteContent && isContainingBlockForAbsoluteContent) { |
| // We are a new containing block for absolute positioning. |
| // Remove our absolutely positioned descendants from their current containing block. |
| // They will be inserted into our positioned objects list during layout. |
| if (CheckedPtr containingBlock = RenderObject::containingBlockForPositionType(PositionType::Absolute, *this)) |
| containingBlock->removeOutOfFlowBoxes(&delegateBlock, RenderBlock::ContainingBlockState::NewContainingBlock); |
| } |
| } |
| |
| } // namespace WebCore |