blob: ba8fc6bfd6c1e56bc9c66b5d4a58df13d70dba7f [file] [log] [blame]
/*
* Copyright (C) 1999 Lars Knoll ([email protected])
* (C) 1999 Antti Koivisto ([email protected])
* Copyright (C) 2003-2025 Apple Inc. All rights reserved.
* Copyright (C) 2014 Google Inc. All rights reserved.
* Copyright (C) 2025 Samuel Weinig <[email protected]>
*
* 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 "RenderInline.h"
#include "Chrome.h"
#include "FloatQuad.h"
#include "FrameSelection.h"
#include "GraphicsContext.h"
#include "HitTestResult.h"
#include "InlineIteratorBoxInlines.h"
#include "InlineIteratorInlineBox.h"
#include "InlineIteratorLineBox.h"
#include "LayoutIntegrationLineLayout.h"
#include "LegacyInlineTextBox.h"
#include "OutlinePainter.h"
#include "RenderBlock.h"
#include "RenderBoxInlines.h"
#include "RenderChildIterator.h"
#include "RenderElementStyleInlines.h"
#include "RenderElementInlines.h"
#include "RenderFragmentedFlow.h"
#include "RenderGeometryMap.h"
#include "RenderIterator.h"
#include "RenderLayer.h"
#include "RenderLayoutState.h"
#include "RenderLineBreak.h"
#include "RenderListMarker.h"
#include "RenderObjectInlines.h"
#include "RenderTable.h"
#include "RenderTheme.h"
#include "RenderTreeBuilder.h"
#include "RenderView.h"
#include "Settings.h"
#include "TransformState.h"
#include "VisiblePosition.h"
#include <wtf/SetForScope.h>
#include <wtf/TZoneMallocInlines.h>
namespace WebCore {
WTF_MAKE_TZONE_ALLOCATED_IMPL(RenderInline);
RenderInline::RenderInline(Type type, Element& element, RenderStyle&& style)
: RenderBoxModelObject(type, element, WTF::move(style), TypeFlag::IsRenderInline, { })
{
setChildrenInline(true);
ASSERT(isRenderInline());
}
RenderInline::RenderInline(Type type, Document& document, RenderStyle&& style)
: RenderBoxModelObject(type, document, WTF::move(style), TypeFlag::IsRenderInline, { })
{
setChildrenInline(true);
ASSERT(isRenderInline());
}
RenderInline::~RenderInline() = default;
void RenderInline::willBeDestroyed()
{
#if ASSERT_ENABLED
// Make sure we do not retain "this" in the continuation outline table map of our containing blocks.
if (parent() && style().usedVisibility() == Visibility::Visible && hasOutline()) {
bool containingBlockPaintsContinuationOutline = continuation() || isContinuation();
if (containingBlockPaintsContinuationOutline) {
if (RenderBlock* cb = containingBlock()) {
if (RenderBlock* cbCb = cb->containingBlock())
ASSERT(!cbCb->paintsContinuationOutline(*this));
}
}
}
#endif // ASSERT_ENABLED
if (!renderTreeBeingDestroyed()) {
if (auto* inlineBox = firstLegacyInlineBox()) {
// We can't wait for RenderBoxModelObject::destroy to clear the selection,
// because by then we will have nuked the line boxes.
if (isSelectionBorder())
frame().selection().setNeedsSelectionUpdate();
// If line boxes are contained inside a root, that means we're an inline.
// In that case, we need to remove all the line boxes so that the parent
// lines aren't pointing to deleted children. If the first line box does
// not have a parent that means they are either already disconnected or
// root lines that can just be destroyed without disconnecting.
if (inlineBox->parent()) {
for (auto* box = inlineBox; box; box = box->nextLineBox())
box->removeFromParent();
}
} else if (auto* parent = this->parent(); parent && parent->isSVGRenderer())
parent->dirtyLineFromChangedChild();
}
m_legacyLineBoxes.deleteLineBoxes();
RenderBoxModelObject::willBeDestroyed();
}
void RenderInline::updateFromStyle()
{
RenderBoxModelObject::updateFromStyle();
// FIXME: Support transforms and reflections on inline flows someday.
setHasTransformRelatedProperty(false);
setHasReflection(false);
}
static RenderElement* inFlowPositionedInlineAncestor(RenderElement* p)
{
while (p && p->isRenderInline()) {
if (p->isInFlowPositioned())
return p;
p = p->parent();
}
return nullptr;
}
static void updateStyleOfAnonymousBlockContinuations(const RenderBlock& block, const RenderStyle* newStyle, const RenderStyle* oldStyle)
{
// If any descendant blocks exist then they will be in the next anonymous block and its siblings.
for (RenderBox* box = block.nextSiblingBox(); box && box->isAnonymousBlock(); box = box->nextSiblingBox()) {
if (box->style().position() == newStyle->position())
continue;
CheckedPtr block = dynamicDowncast<RenderBlock>(*box);
if (!block)
continue;
if (!block->isContinuation())
continue;
// If we are no longer in-flow positioned but our descendant block(s) still have an in-flow positioned ancestor then
// their containing anonymous block should keep its in-flow positioning.
RenderInline* continuation = block->inlineContinuation();
if (oldStyle->hasInFlowPosition() && inFlowPositionedInlineAncestor(continuation))
continue;
auto blockStyle = RenderStyle::createAnonymousStyleWithDisplay(block->style(), DisplayType::Block);
blockStyle.setPosition(newStyle->position());
block->setStyle(WTF::move(blockStyle));
}
}
void RenderInline::styleWillChange(Style::Difference diff, const RenderStyle& newStyle)
{
RenderBoxModelObject::styleWillChange(diff, newStyle);
// RenderInlines forward their absolute positioned descendants to their (non-anonymous) containing block.
// Check if this non-anonymous containing block can hold the absolute positioned elements when the inline is no longer positioned.
CheckedPtr container = containingBlock();
if (!container)
return;
const RenderStyle* oldStyle = hasInitializedStyle() ? &style() : nullptr;
if (oldStyle)
removeOutOfFlowBoxesIfNeededOnStyleChange(*container, *oldStyle, newStyle);
}
void RenderInline::styleDidChange(Style::Difference diff, const RenderStyle* oldStyle)
{
RenderBoxModelObject::styleDidChange(diff, oldStyle);
// Ensure that all of the split inlines pick up the new style. We
// only do this if we're an inline, since we don't want to propagate
// a block's style to the other inlines.
// e.g., <font>foo <h4>goo</h4> moo</font>. The <font> inlines before
// and after the block share the same style, but the block doesn't
// need to pass its style on to anyone else.
auto& newStyle = style();
RenderInline* continuation = inlineContinuation();
if (continuation && !isContinuation()) {
for (RenderInline* currCont = continuation; currCont; currCont = currCont->inlineContinuation())
currCont->setStyle(RenderStyle::clone(newStyle));
// If an inline's in-flow positioning has changed and it is part of an active continuation as a descendant of an anonymous containing block,
// then any descendant blocks will need to change their in-flow positioning accordingly.
// Do this by updating the position of the descendant blocks' containing anonymous blocks - there may be more than one.
if (containingBlock()->isAnonymousBlock() && oldStyle && newStyle.position() != oldStyle->position() && (newStyle.hasInFlowPosition() || oldStyle->hasInFlowPosition()))
updateStyleOfAnonymousBlockContinuations(*containingBlock(), &newStyle, oldStyle);
}
propagateStyleToAnonymousChildren(StylePropagationType::AllChildren);
}
bool RenderInline::mayAffectLayout() const
{
auto* parentStyle = &parent()->style();
auto* parentRenderInline = dynamicDowncast<RenderInline>(*parent());
auto hasHardLineBreakChildOnly = firstChild() && firstChild() == lastChild() && firstChild()->isBR();
bool checkFonts = document().inNoQuirksMode();
auto mayAffectLayout = (parentRenderInline && parentRenderInline->mayAffectLayout())
|| (parentRenderInline && !WTF::holdsAlternative<CSS::Keyword::Baseline>(parentStyle->verticalAlign()))
|| !WTF::holdsAlternative<CSS::Keyword::Baseline>(style().verticalAlign())
|| !style().textEmphasisStyle().isNone()
|| (checkFonts && (!parentStyle->fontCascade().metricsOfPrimaryFont().hasIdenticalAscentDescentAndLineGap(style().fontCascade().metricsOfPrimaryFont())
|| parentStyle->lineHeight() != style().lineHeight()))
|| hasHardLineBreakChildOnly;
if (!mayAffectLayout && checkFonts) {
// Have to check the first line style as well.
parentStyle = &parent()->firstLineStyle();
auto& childStyle = firstLineStyle();
mayAffectLayout = !parentStyle->fontCascade().metricsOfPrimaryFont().hasIdenticalAscentDescentAndLineGap(childStyle.fontCascade().metricsOfPrimaryFont())
|| !WTF::holdsAlternative<CSS::Keyword::Baseline>(childStyle.verticalAlign())
|| parentStyle->lineHeight() != childStyle.lineHeight();
}
return mayAffectLayout;
}
void RenderInline::paint(PaintInfo& paintInfo, const LayoutPoint& paintOffset)
{
if (auto* lineLayout = LayoutIntegration::LineLayout::containing(*this))
lineLayout->paint(paintInfo, paintOffset, this);
}
template<typename GeneratorContext>
void RenderInline::generateLineBoxRects(GeneratorContext& context) const
{
if (auto* lineLayout = LayoutIntegration::LineLayout::containing(*this)) {
auto inlineBoxRects = lineLayout->collectInlineBoxRects(*this);
if (inlineBoxRects.isEmpty()) {
context.addRect({ });
return;
}
for (auto inlineBoxRect : inlineBoxRects)
context.addRect(inlineBoxRect);
return;
}
if (auto* curr = firstLegacyInlineBox()) {
for (; curr; curr = curr->nextLineBox())
context.addRect(FloatRect(curr->topLeft(), curr->size()));
} else
context.addRect(FloatRect());
}
class AbsoluteRectsGeneratorContext {
public:
AbsoluteRectsGeneratorContext(Vector<LayoutRect>& rects, const LayoutPoint& accumulatedOffset)
: m_rects(rects)
, m_accumulatedOffset(accumulatedOffset) { }
void addRect(const FloatRect& rect)
{
LayoutRect adjustedRect = LayoutRect(rect);
adjustedRect.moveBy(m_accumulatedOffset);
m_rects.append(adjustedRect);
}
private:
Vector<LayoutRect>& m_rects;
const LayoutPoint& m_accumulatedOffset;
};
void RenderInline::boundingRects(Vector<LayoutRect>& rects, const LayoutPoint& accumulatedOffset) const
{
AbsoluteRectsGeneratorContext context(rects, accumulatedOffset);
generateLineBoxRects(context);
if (auto* continuation = this->continuation()) {
if (auto* box = dynamicDowncast<RenderBox>(*continuation)) {
continuation->boundingRects(rects, toLayoutPoint(accumulatedOffset - containingBlock()->location() + box->locationOffset()));
} else
continuation->boundingRects(rects, toLayoutPoint(accumulatedOffset - containingBlock()->location()));
}
}
namespace {
class AbsoluteQuadsGeneratorContext {
public:
AbsoluteQuadsGeneratorContext(const RenderInline* renderer, Vector<FloatQuad>& quads)
: m_quads(quads)
, m_geometryMap()
{
m_geometryMap.pushMappingsToAncestor(renderer, nullptr);
}
void addRect(const FloatRect& rect)
{
m_quads.append(m_geometryMap.absoluteRect(rect));
}
private:
Vector<FloatQuad>& m_quads;
RenderGeometryMap m_geometryMap;
};
} // unnamed namespace
void RenderInline::absoluteQuads(Vector<FloatQuad>& quads, bool* wasFixed) const
{
absoluteQuadsIgnoringContinuation({ }, quads, wasFixed);
if (continuation())
collectAbsoluteQuadsForContinuation(quads, wasFixed);
}
void RenderInline::absoluteQuadsIgnoringContinuation(const FloatRect&, Vector<FloatQuad>& quads, bool*) const
{
AbsoluteQuadsGeneratorContext context(this, quads);
generateLineBoxRects(context);
}
#if PLATFORM(IOS_FAMILY)
void RenderInline::absoluteQuadsForSelection(Vector<FloatQuad>& quads) const
{
AbsoluteQuadsGeneratorContext context(this, quads);
generateLineBoxRects(context);
}
#endif
LayoutUnit RenderInline::offsetLeft() const
{
return adjustedPositionRelativeToOffsetParent(firstInlineBoxTopLeft()).x();
}
LayoutUnit RenderInline::offsetTop() const
{
return adjustedPositionRelativeToOffsetParent(firstInlineBoxTopLeft()).y();
}
LayoutPoint RenderInline::firstInlineBoxTopLeft() const
{
if (auto* lineLayout = LayoutIntegration::LineLayout::containing(*this))
return lineLayout->firstInlineBoxRect(*this).location();
if (auto* inlineBox = firstLegacyInlineBox())
return flooredLayoutPoint(inlineBox->locationIncludingFlipping());
return { };
}
static LayoutUnit computeMargin(const RenderInline* renderer, const Style::MarginEdge& margin, const Style::ZoomFactor& zoomFactor)
{
return Style::evaluateMinimum<LayoutUnit>(margin, [&] ALWAYS_INLINE_LAMBDA {
return std::max<LayoutUnit>(0, renderer->containingBlock()->contentBoxLogicalWidth());
}, zoomFactor);
}
LayoutUnit RenderInline::marginLeft() const
{
return computeMargin(this, style().marginLeft(), style().usedZoomForLength());
}
LayoutUnit RenderInline::marginRight() const
{
return computeMargin(this, style().marginRight(), style().usedZoomForLength());
}
LayoutUnit RenderInline::marginTop() const
{
return computeMargin(this, style().marginTop(), style().usedZoomForLength());
}
LayoutUnit RenderInline::marginBottom() const
{
return computeMargin(this, style().marginBottom(), style().usedZoomForLength());
}
LayoutUnit RenderInline::marginStart(const WritingMode writingMode) const
{
return computeMargin(this, style().marginStart(writingMode), style().usedZoomForLength());
}
LayoutUnit RenderInline::marginEnd(const WritingMode writingMode) const
{
return computeMargin(this, style().marginEnd(writingMode), style().usedZoomForLength());
}
LayoutUnit RenderInline::marginBefore(const WritingMode writingMode) const
{
return computeMargin(this, style().marginBefore(writingMode), style().usedZoomForLength());
}
LayoutUnit RenderInline::marginAfter(const WritingMode writingMode) const
{
return computeMargin(this, style().marginAfter(writingMode), style().usedZoomForLength());
}
ASCIILiteral RenderInline::renderName() const
{
if (isRelativelyPositioned())
return "RenderInline (relative positioned)"_s;
if (isStickilyPositioned())
return "RenderInline (sticky positioned)"_s;
// FIXME: Temporary hack while the new generated content system is being implemented.
if (isPseudoElement())
return "RenderInline (generated)"_s;
if (isAnonymous())
return "RenderInline (generated)"_s;
return "RenderInline"_s;
}
bool RenderInline::nodeAtPoint(const HitTestRequest& request, HitTestResult& result,
const HitTestLocation& locationInContainer, const LayoutPoint& accumulatedOffset, HitTestAction hitTestAction)
{
ASSERT(layer());
if (auto* lineLayout = LayoutIntegration::LineLayout::containing(*this))
return lineLayout->hitTest(request, result, locationInContainer, accumulatedOffset, hitTestAction, this);
return false;
}
PositionWithAffinity RenderInline::positionForPoint(const LayoutPoint& point, HitTestSource source, const RenderFragmentContainer* fragment)
{
auto& containingBlock = *this->containingBlock();
if (auto* continuation = this->continuation()) {
// Translate the coords from the pre-anonymous block to the post-anonymous block.
LayoutPoint parentBlockPoint = containingBlock.location() + point;
while (continuation) {
RenderBlock* currentBlock = continuation->isInline() ? continuation->containingBlock() : downcast<RenderBlock>(continuation);
if (continuation->isInline() || continuation->firstChild())
return continuation->positionForPoint(parentBlockPoint - currentBlock->locationOffset(), source, fragment);
continuation = continuation->inlineContinuation();
}
return RenderBoxModelObject::positionForPoint(point, source, fragment);
}
return containingBlock.positionForPoint(point, source, fragment);
}
LayoutUnit RenderInline::innerPaddingBoxWidth() const
{
auto firstInlineBoxPaddingBoxLeft = LayoutUnit { };
auto lastInlineBoxPaddingBoxRight = LayoutUnit { };
if (LayoutIntegration::LineLayout::containing(*this)) {
if (auto inlineBox = InlineIterator::lineLeftmostInlineBoxFor(*this)) {
if (writingMode().isBidiLTR()) {
firstInlineBoxPaddingBoxLeft = inlineBox->logicalLeftIgnoringInlineDirection() + borderStart();
for (; inlineBox->nextInlineBoxLineRightward(); inlineBox.traverseInlineBoxLineRightward()) { }
ASSERT(inlineBox);
lastInlineBoxPaddingBoxRight = inlineBox->logicalRightIgnoringInlineDirection() - borderEnd();
} else {
lastInlineBoxPaddingBoxRight = inlineBox->logicalRightIgnoringInlineDirection() - borderStart();
for (; inlineBox->nextInlineBoxLineRightward(); inlineBox.traverseInlineBoxLineRightward()) { }
ASSERT(inlineBox);
firstInlineBoxPaddingBoxLeft = inlineBox->logicalLeftIgnoringInlineDirection() + borderEnd();
}
return std::max(0_lu, lastInlineBoxPaddingBoxRight - firstInlineBoxPaddingBoxLeft);
}
return { };
}
auto* firstInlineBox = firstLegacyInlineBox();
auto* lastInlineBox = lastLegacyInlineBox();
if (!firstInlineBox || !lastInlineBox)
return { };
if (writingMode().isBidiLTR()) {
firstInlineBoxPaddingBoxLeft = firstInlineBox->logicalLeft();
lastInlineBoxPaddingBoxRight = lastInlineBox->logicalRight();
} else {
lastInlineBoxPaddingBoxRight = firstInlineBox->logicalRight();
firstInlineBoxPaddingBoxLeft = lastInlineBox->logicalLeft();
}
return std::max(0_lu, lastInlineBoxPaddingBoxRight - firstInlineBoxPaddingBoxLeft);
}
LayoutUnit RenderInline::innerPaddingBoxHeight() const
{
auto innerPaddingBoxLogicalHeight = LayoutUnit { isHorizontalWritingMode() ? linesBoundingBox().height() : linesBoundingBox().width() };
innerPaddingBoxLogicalHeight -= (borderBefore() + borderAfter());
return innerPaddingBoxLogicalHeight;
}
IntRect RenderInline::linesBoundingBox() const
{
if (auto* layout = LayoutIntegration::LineLayout::containing(*this)) {
if (!layoutBox() || !layout->contains(*this)) {
// Repaint may be issued on subtrees during content mutation with newly inserted renderers
// (or we just forgot to initiate layout before querying geometry on stale content after moving inline boxes between blocks).
ASSERT(needsLayout());
return { };
}
if (isRenderSVGInline()) {
// FIXME: Always build the bounding box like this. LineLayouyt::enclosingBorderBoxRectFor does not include
// any post-layout box adjustments.
FloatRect result;
for (auto box = InlineIterator::lineLeftmostInlineBoxFor(*this); box; box.traverseInlineBoxLineRightward()) {
auto rect = box->visualRectIgnoringBlockDirection();
result.unite(rect);
}
return enclosingIntRect(result);
}
return enclosingIntRect(layout->enclosingBorderBoxRectFor(*this));
}
// See <rdar://problem/5289721>, for an unknown reason the linked list here is sometimes inconsistent, first is non-zero and last is zero. We have been
// unable to reproduce this at all (and consequently unable to figure ot why this is happening). The assert will hopefully catch the problem in debug
// builds and help us someday figure out why. We also put in a redundant check of lastLineBox() to avoid the crash for now.
ASSERT(!firstLegacyInlineBox() == !lastLegacyInlineBox()); // Either both are null or both exist.
IntRect result;
if (firstLegacyInlineBox() && lastLegacyInlineBox()) {
// Return the width of the minimal left side and the maximal right side.
float logicalLeftSide = 0;
float logicalRightSide = 0;
for (auto* curr = firstLegacyInlineBox(); curr; curr = curr->nextLineBox()) {
if (curr == firstLegacyInlineBox() || curr->logicalLeft() < logicalLeftSide)
logicalLeftSide = curr->logicalLeft();
if (curr == firstLegacyInlineBox() || curr->logicalRight() > logicalRightSide)
logicalRightSide = curr->logicalRight();
}
bool isHorizontal = writingMode().isHorizontal();
float x = isHorizontal ? logicalLeftSide : firstLegacyInlineBox()->x();
float y = isHorizontal ? firstLegacyInlineBox()->y() : logicalLeftSide;
float width = isHorizontal ? logicalRightSide - logicalLeftSide : lastLegacyInlineBox()->logicalBottom() - x;
float height = isHorizontal ? lastLegacyInlineBox()->logicalBottom() - y : logicalRightSide - logicalLeftSide;
result = enclosingIntRect(FloatRect(x, y, width, height));
}
return result;
}
LayoutRect RenderInline::linesVisualOverflowBoundingBox() const
{
if (auto* layout = LayoutIntegration::LineLayout::containing(*this)) {
if (!layoutBox()) {
// Repaint may be issued on subtrees during content mutation with newly inserted renderers.
ASSERT(needsLayout());
return { };
}
return layout->inkOverflowBoundingBoxRectFor(*this);
}
if (!firstLegacyInlineBox() || !lastLegacyInlineBox())
return { };
// Return the width of the minimal left side and the maximal right side.
LayoutUnit logicalLeftSide = LayoutUnit::max();
LayoutUnit logicalRightSide = LayoutUnit::min();
for (auto* curr = firstLegacyInlineBox(); curr; curr = curr->nextLineBox()) {
logicalLeftSide = std::min(logicalLeftSide, curr->logicalLeftVisualOverflow());
logicalRightSide = std::max(logicalRightSide, curr->logicalRightVisualOverflow());
}
const LegacyRootInlineBox& firstRootBox = firstLegacyInlineBox()->root();
const LegacyRootInlineBox& lastRootBox = lastLegacyInlineBox()->root();
LayoutUnit logicalTop = firstLegacyInlineBox()->logicalTopVisualOverflow(firstRootBox.lineTop());
LayoutUnit logicalWidth = logicalRightSide - logicalLeftSide;
LayoutUnit logicalHeight = lastLegacyInlineBox()->logicalBottomVisualOverflow(lastRootBox.lineBottom()) - logicalTop;
LayoutRect rect(logicalLeftSide, logicalTop, logicalWidth, logicalHeight);
if (!writingMode().isHorizontal())
rect = rect.transposedRect();
return rect;
}
LayoutRect RenderInline::clippedOverflowRect(const RenderLayerModelObject* repaintContainer, VisibleRectContext context) const
{
// Only first-letter renderers are allowed in here during layout. They mutate the tree triggering repaints.
#ifndef NDEBUG
auto insideSelfPaintingInlineBox = [&] {
if (hasSelfPaintingLayer())
return true;
auto* containingBlock = this->containingBlock();
for (auto* ancestor = this->parent(); ancestor && ancestor != containingBlock; ancestor = ancestor->parent()) {
if (ancestor->hasSelfPaintingLayer())
return true;
}
return false;
};
ASSERT_UNUSED(insideSelfPaintingInlineBox, !view().frameView().layoutContext().isPaintOffsetCacheEnabled() || style().pseudoElementType() == PseudoElementType::FirstLetter || insideSelfPaintingInlineBox());
#endif
auto knownEmpty = [&] {
if (firstLegacyInlineBox())
return false;
if (continuation())
return false;
if (LayoutIntegration::LineLayout::containing(*this))
return false;
return true;
};
if (knownEmpty())
return LayoutRect();
auto repaintRect = linesVisualOverflowBoundingBox();
bool hitRepaintContainer = false;
// We need to add in the in-flow position offsets of any inlines (including us) up to our
// containing block.
RenderBlock* containingBlock = this->containingBlock();
for (const RenderElement* inlineFlow = this; inlineFlow; inlineFlow = inlineFlow->parent()) {
auto* renderInline = dynamicDowncast<RenderInline>(*inlineFlow);
if (!renderInline || inlineFlow == containingBlock)
break;
if (inlineFlow == repaintContainer) {
hitRepaintContainer = true;
break;
}
if (inlineFlow->style().hasInFlowPosition() && inlineFlow->hasLayer())
repaintRect.move(renderInline->layer()->offsetForInFlowPosition());
}
LayoutUnit outlineSize { style().usedOutlineSize() };
repaintRect.inflate(outlineSize);
if (hitRepaintContainer || !containingBlock)
return repaintRect;
auto rects = RepaintRects { repaintRect };
if (containingBlock->hasNonVisibleOverflow())
containingBlock->applyCachedClipAndScrollPosition(rects, repaintContainer, context);
rects = containingBlock->computeRects(rects, repaintContainer, context);
repaintRect = rects.clippedOverflowRect;
if (outlineSize) {
for (auto& child : childrenOfType<RenderElement>(*this))
repaintRect.unite(child.rectWithOutlineForRepaint(repaintContainer, outlineSize));
if (RenderBoxModelObject* continuation = this->continuation()) {
if (!continuation->isInline() && continuation->parent())
repaintRect.unite(continuation->rectWithOutlineForRepaint(repaintContainer, outlineSize));
}
}
return repaintRect;
}
auto RenderInline::rectsForRepaintingAfterLayout(const RenderLayerModelObject* repaintContainer, RepaintOutlineBounds) const -> RepaintRects
{
// RepaintOutlineBounds is unused for inlines.
return { clippedOverflowRect(repaintContainer, visibleRectContextForRepaint()) };
}
LayoutRect RenderInline::rectWithOutlineForRepaint(const RenderLayerModelObject* repaintContainer, LayoutUnit outlineWidth) const
{
LayoutRect r(RenderBoxModelObject::rectWithOutlineForRepaint(repaintContainer, outlineWidth));
for (auto& child : childrenOfType<RenderElement>(*this))
r.unite(child.rectWithOutlineForRepaint(repaintContainer, outlineWidth));
return r;
}
auto RenderInline::computeVisibleRectsUsingPaintOffset(const RepaintRects& rects) const -> RepaintRects
{
auto adjustedRects = rects;
auto* layoutState = view().frameView().layoutContext().layoutState();
if (style().hasInFlowPosition() && layer())
adjustedRects.move(layer()->offsetForInFlowPosition());
adjustedRects.move(layoutState->paintOffset());
if (layoutState->isClipped())
adjustedRects.clippedOverflowRect.intersect(layoutState->clipRect());
return adjustedRects;
}
auto RenderInline::computeVisibleRectsInContainer(const RepaintRects& rects, const RenderLayerModelObject* container, VisibleRectContext context) const -> std::optional<RepaintRects>
{
// Repaint offset cache is only valid for root-relative repainting
if (view().frameView().layoutContext().isPaintOffsetCacheEnabled() && !container && !context.options.contains(VisibleRectContext::Option::UseEdgeInclusiveIntersection))
return computeVisibleRectsUsingPaintOffset(rects);
if (container == this)
return rects;
bool containerSkipped;
RenderElement* localContainer = this->container(container, containerSkipped);
if (!localContainer)
return rects;
auto adjustedRects = rects;
if (style().hasInFlowPosition() && layer()) {
// Apply the in-flow 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 or sticky 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()) {
// FIXME: Respect the value of context.options.
SetForScope change(context.options, context.options | VisibleRectContext::Option::ApplyCompositedContainerScrolls);
bool isEmpty = !downcast<RenderLayerModelObject>(*localContainer).applyCachedClipAndScrollPosition(adjustedRects, container, context);
if (isEmpty) {
if (context.options.contains(VisibleRectContext::Option::UseEdgeInclusiveIntersection))
return std::nullopt;
return adjustedRects;
}
}
if (containerSkipped) {
// If the repaintContainer is below o, then we need to map the rect into repaintContainer's coordinates.
auto containerOffset = container->offsetFromAncestorContainer(*localContainer);
adjustedRects.move(-containerOffset);
return adjustedRects;
}
return localContainer->computeVisibleRectsInContainer(adjustedRects, container, context);
}
LayoutSize RenderInline::offsetFromContainer(const RenderElement& container, const LayoutPoint&, bool* offsetDependsOnPoint) const
{
ASSERT(&container == this->container());
LayoutSize offset;
if (isInFlowPositioned())
offset += offsetForInFlowPosition();
if (auto* box = dynamicDowncast<RenderBox>(container))
offset -= toLayoutSize(box->scrollPosition());
if (offsetDependsOnPoint)
*offsetDependsOnPoint = (is<RenderBox>(container) && container.writingMode().isBlockFlipped()) || is<RenderFragmentedFlow>(container);
return offset;
}
void RenderInline::mapLocalToContainer(const RenderLayerModelObject* ancestorContainer, TransformState& transformState, OptionSet<MapCoordinatesMode> mode, bool* wasFixed) const
{
if (ancestorContainer == this)
return;
if (view().frameView().layoutContext().isPaintOffsetCacheEnabled() && !ancestorContainer) {
auto* layoutState = view().frameView().layoutContext().layoutState();
LayoutSize offset = layoutState->paintOffset();
if (style().hasInFlowPosition() && layer())
offset += layer()->offsetForInFlowPosition();
transformState.move(offset);
return;
}
bool containerSkipped;
RenderElement* container = this->container(ancestorContainer, containerSkipped);
if (!container)
return;
if (mode.contains(ApplyContainerFlip)) {
if (CheckedPtr box = dynamicDowncast<RenderBox>(*container)) {
if (container->writingMode().isBlockFlipped()) {
LayoutPoint centerPoint(transformState.mappedPoint());
transformState.move(box->flipForWritingMode(centerPoint) - centerPoint);
}
mode.remove(ApplyContainerFlip);
}
}
LayoutSize containerOffset = offsetFromContainer(*container, LayoutPoint(transformState.mappedPoint()));
pushOntoTransformState(transformState, mode, ancestorContainer, container, containerOffset, containerSkipped);
if (containerSkipped)
return;
container->mapLocalToContainer(ancestorContainer, transformState, mode, wasFixed);
}
const RenderElement* RenderInline::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 RenderInline::updateHitTestResult(HitTestResult& result, const LayoutPoint& point) const
{
if (result.innerNode())
return;
LayoutPoint localPoint(point);
if (RefPtr node = nodeForHitTest()) {
if (isContinuation()) {
// We're in the continuation of a split inline. Adjust our local point to be in the coordinate space
// of the principal renderer's containing block. This will end up being the innerNonSharedNode.
auto* firstBlock = node->renderer()->containingBlock();
localPoint.moveBy(containingBlock()->location() - firstBlock->locationOffset());
}
result.setInnerNode(node.get());
if (!result.innerNonSharedNode())
result.setInnerNonSharedNode(node.get());
result.setPseudoElementIdentifier(style().pseudoElementIdentifier());
result.setLocalPoint(localPoint);
}
}
void RenderInline::deleteLegacyLineBoxes()
{
m_legacyLineBoxes.deleteLineBoxes();
}
std::unique_ptr<LegacyInlineFlowBox> RenderInline::createInlineFlowBox()
{
return makeUnique<LegacyInlineFlowBox>(*this);
}
LegacyInlineFlowBox* RenderInline::createAndAppendInlineFlowBox()
{
auto newFlowBox = createInlineFlowBox();
auto flowBox = newFlowBox.get();
m_legacyLineBoxes.appendLineBox(WTF::move(newFlowBox));
return flowBox;
}
LayoutSize RenderInline::offsetForInFlowPositionedInline(const RenderBox* child) const
{
// FIXME: This function isn't right with mixed writing modes.
if (!isInFlowPositioned()) {
ASSERT_NOT_REACHED();
return { };
}
if (!hasLayer()) {
// It looks like we are inflow positioned but no layer created yet. It essentially means we don't have an position offset yet.
return { };
}
// When we have an enclosing relpositioned inline, we need to add in the offset of the first line
// box from the rest of the content, but only in the cases where we know we're positioned
// relative to the inline itself.
auto inlinePosition = layer()->staticInlinePosition();
auto blockPosition = layer()->staticBlockPosition();
if (auto* inlineBox = firstLegacyInlineBox()) {
inlinePosition = LayoutUnit::fromFloatRound(inlineBox->logicalLeft());
blockPosition = inlineBox->logicalTop();
} else if (LayoutIntegration::LineLayout::containing(*this)) {
if (!layoutBox()) {
// Repaint may be issued on subtrees during content mutation with newly inserted renderers.
ASSERT(needsLayout());
return { };
}
if (auto inlineBox = InlineIterator::lineLeftmostInlineBoxFor(*this)) {
inlinePosition = LayoutUnit::fromFloatRound(inlineBox->logicalLeftIgnoringInlineDirection());
blockPosition = inlineBox->logicalTop();
} else if (auto* blockContainer = containingBlock()) {
// This must be a block with no in-flow content e.g. <div><span><abs pos box></span></div> where we don't construct any display box at all.
auto contentBoxLocation = blockContainer->contentBoxLocation();
inlinePosition = contentBoxLocation.x();
blockPosition = contentBoxLocation.y();
}
}
// Per http://www.w3.org/TR/CSS2/visudet.html#abs-non-replaced-width an absolute positioned box with a static position
// should locate itself as though it is a normal flow box in relation to its containing block.
LayoutSize logicalOffset;
if (!child->style().hasStaticInlinePosition(writingMode().isHorizontal())
|| !child->style().positionArea().isNone() || child->style().justifySelf().isAnchorCenter())
logicalOffset.setWidth(inlinePosition);
if (!child->style().hasStaticBlockPosition(writingMode().isHorizontal())
|| !child->style().positionArea().isNone() || child->style().alignSelf().isAnchorCenter())
logicalOffset.setHeight(blockPosition);
return writingMode().isHorizontal() ? logicalOffset : logicalOffset.transposedSize();
}
void RenderInline::imageChanged(WrappedImagePtr image, const IntRect*)
{
if (!parent())
return;
bool isNonEmpty;
RefPtr styleImage = Style::findLayerUsedImage(style().backgroundLayers(), image, isNonEmpty);
if (styleImage && isNonEmpty) {
if (auto styleable = Styleable::fromRenderer(*this))
protectedDocument()->didLoadImage(styleable->protectedElement().get(), styleImage->cachedImage());
}
// FIXME: We can do better.
repaint();
}
namespace {
class AbsoluteRectsIgnoringEmptyGeneratorContext : public AbsoluteRectsGeneratorContext {
public:
AbsoluteRectsIgnoringEmptyGeneratorContext(Vector<LayoutRect>& rects, const LayoutPoint& accumulatedOffset)
: AbsoluteRectsGeneratorContext(rects, accumulatedOffset) { }
void addRect(const FloatRect& rect)
{
if (!rect.isEmpty())
AbsoluteRectsGeneratorContext::addRect(rect);
}
};
} // unnamed namespace
void RenderInline::collectLineBoxRects(Vector<LayoutRect>& rects, const LayoutPoint& additionalOffset) const
{
AbsoluteRectsIgnoringEmptyGeneratorContext context(rects, additionalOffset);
generateLineBoxRects(context);
}
bool isEmptyInline(const RenderInline& renderer)
{
for (auto& current : childrenOfType<RenderObject>(renderer)) {
if (current.isFloatingOrOutOfFlowPositioned())
continue;
if (auto* text = dynamicDowncast<RenderText>(current)) {
if (!text->containsOnlyCollapsibleWhitespace())
return false;
continue;
}
auto* renderInline = dynamicDowncast<RenderInline>(current);
if (!renderInline || !isEmptyInline(*renderInline))
return false;
}
return true;
}
bool RenderInline::requiresLayer() const
{
return isInFlowPositioned()
|| createsGroup()
|| hasClipPath()
|| style().willChange().canCreateStackingContext()
|| hasRunningAcceleratedAnimations()
|| requiresRenderingConsolidationForViewTransition();
}
} // namespace WebCore