Bug Summary

File:Volumes/Data/worker/Apple-iOS-26-Safer-CPP-Checks-EWS/build/Source/WebCore/layout/integration/inline/LayoutIntegrationLineLayout.cpp
Warning:line 409, column 24
A function 'textBoxTrim' has [[clang::annotate_type("webkit.nodelete")]] but it contains code that could destruct an object

Annotated Source Code

Press '?' to see keyboard shortcuts

clang -cc1 -cc1 -triple arm64-apple-ios26.2.0-simulator -O3 -Wundef-prefix=TARGET_OS_ -Werror=undef-prefix -Wdeprecated-objc-isa-usage -Werror=deprecated-objc-isa-usage -Werror=implicit-function-declaration -analyze -disable-free -clear-ast-before-backend -disable-llvm-verifier -discard-value-names -main-file-name UnifiedSource276.cpp -analyzer-checker=core -analyzer-checker=apiModeling -analyzer-checker=unix -analyzer-checker=osx -analyzer-checker=security.insecureAPI.decodeValueOfObjCType -analyzer-checker=deadcode -analyzer-checker=cplusplus -analyzer-checker=security.insecureAPI.UncheckedReturn -analyzer-checker=security.insecureAPI.getpw -analyzer-checker=security.insecureAPI.gets -analyzer-checker=security.insecureAPI.mktemp -analyzer-checker=security.insecureAPI.mkstemp -analyzer-checker=security.insecureAPI.vfork -analyzer-checker=nullability.NullPassedToNonnull -analyzer-checker=nullability.NullReturnedFromNonnull -analyzer-output plist -w -setup-static-analyzer -analyzer-config-compatibility-mode=true -mrelocation-model pic -pic-level 2 -mframe-pointer=non-leaf -ffp-contract=on -fno-rounding-math -target-sdk-version=26.2 -fdepfile-entry=/Applications/Xcode.app/Contents/Developer/Platforms/iPhoneSimulator.platform/Developer/SDKs/iPhoneSimulator26.2.sdk/SDKSettings.json -fcompatibility-qualified-id-block-type-checking -fvisibility-inlines-hidden-static-local-var -fno-modulemap-allow-subdirectory-search -fdefine-target-os-macros -enable-tlsdesc -target-cpu apple-m1 -target-feature +v8.4a -target-feature +aes -target-feature +altnzcv -target-feature +ccdp -target-feature +ccpp -target-feature +complxnum -target-feature +crc -target-feature +dotprod -target-feature +flagm -target-feature +fp-armv8 -target-feature +fp16fml -target-feature +fptoint -target-feature +fullfp16 -target-feature +jsconv -target-feature +lse -target-feature +neon -target-feature +pauth -target-feature +perfmon -target-feature +predres -target-feature +ras -target-feature +rcpc -target-feature +rdm -target-feature +sb -target-feature +sha2 -target-feature +sha3 -target-feature +specrestrict -target-feature +ssbs -target-abi darwinpcs -dwarf-ext-refs -fmodule-format=obj -debug-info-kind=standalone -dwarf-version=5 -debugger-tuning=lldb -fdebug-compilation-dir=/Volumes/Data/worker/Apple-iOS-26-Safer-CPP-Checks-EWS/build/Source/WebCore -target-linker-version 1230.1 -fcoverage-compilation-dir=/Volumes/Data/worker/Apple-iOS-26-Safer-CPP-Checks-EWS/build/Source/WebCore -resource-dir /Users/buildbot/Library/Developer/Toolchains/swift-webkit.xctoolchain/usr/lib/clang/21 -dependency-file /Volumes/Data/worker/Apple-iOS-26-Safer-CPP-Checks-EWS/build/scan-build-output/StaticAnalyzer/WebCore/WebCore/normal/arm64/UnifiedSource276.d -skip-unused-modulemap-deps -MT dependencies -isysroot /Applications/Xcode.app/Contents/Developer/Platforms/iPhoneSimulator.platform/Developer/SDKs/iPhoneSimulator26.2.sdk -iquote /Volumes/Data/worker/Apple-iOS-26-Safer-CPP-Checks-EWS/build/WebKitBuild/WebCore.build/Release-iphonesimulator/WebCore.build/WebCore-generated-files.hmap -iquote /Volumes/Data/worker/Apple-iOS-26-Safer-CPP-Checks-EWS/build/WebKitBuild/WebCore.build/Release-iphonesimulator/WebCore.build/WebCore-project-headers.hmap -isystem /Volumes/Data/worker/Apple-iOS-26-Safer-CPP-Checks-EWS/build/Source/WebCore/PAL/ThirdParty/dav1d/include -isystem /Volumes/Data/worker/Apple-iOS-26-Safer-CPP-Checks-EWS/build/WebKitBuild/Release-iphonesimulator/SDKAdditions/usr/include -iframework /Volumes/Data/worker/Apple-iOS-26-Safer-CPP-Checks-EWS/build/WebKitBuild/Release-iphonesimulator/SDKAdditions/System/Library/Frameworks -iframework /Volumes/Data/worker/Apple-iOS-26-Safer-CPP-Checks-EWS/build/WebKitLibraries/SDKs/iphonesimulator26.0-additions.sdk/System/Library/PrivateFrameworks -iframework /Applications/Xcode.app/Contents/Developer/Platforms/iPhoneSimulator.platform/Developer/SDKs/iPhoneSimulator26.2.sdk/System/Library/Frameworks -include-pch /Volumes/Data/worker/Apple-iOS-26-Safer-CPP-Checks-EWS/build/WebKitBuild/PrecompiledHeaders/SharedPrecompiledHeaders/11721353671419897710/WebCorePrefix.h.gch -D _LIBCPP_HARDENING_MODE=_LIBCPP_HARDENING_MODE_EXTENSIVE -D NDEBUG -D BUILDING_WEBKIT -D GL_SILENCE_DEPRECATION=1 -D GLES_SILENCE_DEPRECATION=1 -D __clang_analyzer__ -I /Volumes/Data/worker/Apple-iOS-26-Safer-CPP-Checks-EWS/build/WebKitBuild/WebCore.build/Release-iphonesimulator/WebCore.build/WebCore-own-target-headers.hmap -I /Volumes/Data/worker/Apple-iOS-26-Safer-CPP-Checks-EWS/build/WebKitBuild/WebCore.build/Release-iphonesimulator/WebCore.build/WebCore-all-target-headers.hmap -I /Volumes/Data/worker/Apple-iOS-26-Safer-CPP-Checks-EWS/build/WebKitBuild/Release-iphonesimulator/include -I PAL -I ForwardingHeaders -I /usr/include/libxslt -I /Applications/Xcode.app/Contents/Developer/Platforms/iPhoneSimulator.platform/Developer/SDKs/iPhoneSimulator26.2.sdk/usr/include/libxml2 -I /Volumes/Data/worker/Apple-iOS-26-Safer-CPP-Checks-EWS/build/WebKitBuild/Release-iphonesimulator/DerivedSources/WebCore -I /Volumes/Data/worker/Apple-iOS-26-Safer-CPP-Checks-EWS/build/WebKitBuild/Release-iphonesimulator/usr/local/include -I /Volumes/Data/worker/Apple-iOS-26-Safer-CPP-Checks-EWS/build/WebKitBuild/Release-iphonesimulator/usr/local/include/WebKitAdditions -I /Applications/Xcode.app/Contents/Developer/Platforms/iPhoneSimulator.platform/Developer/SDKs/iPhoneSimulator26.2.sdk/usr/local/include/WebKitAdditions -I /Volumes/Data/worker/Apple-iOS-26-Safer-CPP-Checks-EWS/build/WebKitBuild/Release-iphonesimulator/usr/local/include -I /Volumes/Data/worker/Apple-iOS-26-Safer-CPP-Checks-EWS/build/WebKitLibraries/SDKs/iphonesimulator26.0-additions.sdk/usr/local/include -I /Applications/Xcode.app/Contents/Developer/Platforms/iPhoneSimulator.platform/Developer/SDKs/iPhoneSimulator26.2.sdk/usr/local/include -I /Volumes/Data/worker/Apple-iOS-26-Safer-CPP-Checks-EWS/build/WebKitBuild/Release-iphonesimulator/usr/local/include/webrtc -I /Applications/Xcode.app/Contents/Developer/Platforms/iPhoneSimulator.platform/Developer/SDKs/iPhoneSimulator26.2.sdk/usr/local/include/webrtc -I /Volumes/Data/worker/Apple-iOS-26-Safer-CPP-Checks-EWS/build/WebKitBuild/Release-iphonesimulator/usr/local/include/webm -I /Applications/Xcode.app/Contents/Developer/Platforms/iPhoneSimulator.platform/Developer/SDKs/iPhoneSimulator26.2.sdk/usr/local/include/webm -I /Volumes/Data/worker/Apple-iOS-26-Safer-CPP-Checks-EWS/build/WebKitBuild/PAL.build/PAL.build/DerivedSources -I /Volumes/Data/worker/Apple-iOS-26-Safer-CPP-Checks-EWS/build/WebKitBuild/PAL.build/Release-iphonesimulator/PAL.build/DerivedSources -I /Volumes/Data/worker/Apple-iOS-26-Safer-CPP-Checks-EWS/build/Source/WebCore -I /Volumes/Data/worker/Apple-iOS-26-Safer-CPP-Checks-EWS/build/WebKitBuild/WebCore.build/Release-iphonesimulator/WebCore.build/DerivedSources-normal/arm64 -I /Volumes/Data/worker/Apple-iOS-26-Safer-CPP-Checks-EWS/build/WebKitBuild/WebCore.build/Release-iphonesimulator/WebCore.build/DerivedSources/arm64 -I /Volumes/Data/worker/Apple-iOS-26-Safer-CPP-Checks-EWS/build/WebKitBuild/WebCore.build/Release-iphonesimulator/WebCore.build/DerivedSources -F/Volumes/Data/worker/Apple-iOS-26-Safer-CPP-Checks-EWS/build/WebKitBuild/Release-iphonesimulator -F/Volumes/Data/worker/Apple-iOS-26-Safer-CPP-Checks-EWS/build/WebKitBuild/Release-iphonesimulator -I/usr/local/include -stdlib=libc++ -internal-isystem /Applications/Xcode.app/Contents/Developer/Platforms/iPhoneSimulator.platform/Developer/SDKs/iPhoneSimulator26.2.sdk/usr/include/c++/v1 -internal-isystem /Applications/Xcode.app/Contents/Developer/Platforms/iPhoneSimulator.platform/Developer/SDKs/iPhoneSimulator26.2.sdk/usr/local/include -internal-isystem /Users/buildbot/Library/Developer/Toolchains/swift-webkit.xctoolchain/usr/lib/clang/21/include -internal-externc-isystem /Applications/Xcode.app/Contents/Developer/Platforms/iPhoneSimulator.platform/Developer/SDKs/iPhoneSimulator26.2.sdk/usr/include -internal-iframework /Applications/Xcode.app/Contents/Developer/Platforms/iPhoneSimulator.platform/Developer/SDKs/iPhoneSimulator26.2.sdk/System/Library/Frameworks -internal-iframework /Applications/Xcode.app/Contents/Developer/Platforms/iPhoneSimulator.platform/Developer/SDKs/iPhoneSimulator26.2.sdk/System/Library/SubFrameworks -internal-iframework /Applications/Xcode.app/Contents/Developer/Platforms/iPhoneSimulator.platform/Developer/SDKs/iPhoneSimulator26.2.sdk/Library/Frameworks -Wno-trigraphs -Werror -Wno-missing-field-initializers -Wmissing-prototypes -Wunreachable-code -Wnon-virtual-dtor -Wno-overloaded-virtual -Wno-exit-time-destructors -Wno-missing-braces -Wparentheses -Wswitch -Wunused-function -Wno-unused-label -Wno-unused-parameter -Wunused-variable -Wunused-value -Wempty-body -Wuninitialized -Wno-unknown-pragmas -Wno-shadow -Wno-four-char-constants -Wno-conversion -Wconstant-conversion -Wint-conversion -Wbool-conversion -Wenum-conversion -Wno-float-conversion -Wnon-literal-null-conversion -Wobjc-literal-conversion -Wsign-compare -Wno-shorten-64-to-32 -Wnewline-eof -Wno-c++11-extensions -Wno-implicit-fallthrough -Wdeprecated-declarations -Winvalid-offsetof -Wno-sign-conversion -Winfinite-recursion -Wmove -Wcomma -Wblock-capture-autoreleasing -Wstrict-prototypes -Wrange-loop-analysis -Wno-semicolon-before-method-body -Wall -Wc99-designator -Wconditional-uninitialized -Wextra -Wdeprecated-enum-enum-conversion -Wdeprecated-enum-float-conversion -Wenum-float-conversion -Wfinal-dtor-non-final-class -Wformat=2 -Wmisleading-indentation -Wpointer-to-int-cast -Wreorder-init-list -Wundef -Wunused-but-set-variable -Wvla -Werror=unguarded-availability -Wno-elaborated-enum-base -Wcast-qual -Wchar-subscripts -Wextra-tokens -Winit-self -Wmissing-noreturn -Wpacked -Wpointer-arith -Wredundant-decls -Wwrite-strings -Wexit-time-destructors -Wglobal-constructors -Wtautological-compare -Wimplicit-fallthrough -Wvla -Wno-unknown-warning-option -Wliteral-conversion -Wthread-safety -Wno-profile-instr-out-of-date -Wno-profile-instr-unprofiled -Wno-backend-plugin -Wunsafe-buffer-usage -std=c++2b -fdeprecated-macro -ferror-limit 19 -fmacro-backtrace-limit=0 -fvisibility=hidden -fvisibility-inlines-hidden -stack-protector 1 -fblocks -fencode-extended-block-signature -fno-rtti -fregister-global-dtors-with-atexit -fgnuc-version=4.2.1 -fno-cxx-modules -fno-implicit-modules -fskip-odr-check-in-gmf -fpascal-strings -fmax-type-align=16 -fdiagnostics-show-note-include-stack -vectorize-loops -vectorize-slp -fsafe-buffer-usage-suggestions -analyzer-output=html -analyzer-config report-in-main-source-file=true -analyzer-config nullability:NoDiagnoseCallsToSystemHeaders=true -analyzer-checker optin.osx.cocoa.localizability.NonLocalizedStringChecker -analyzer-checker security.insecureAPI.UncheckedReturn -analyzer-checker security.insecureAPI.getpw -analyzer-checker security.insecureAPI.gets -analyzer-checker security.insecureAPI.mkstemp -analyzer-checker security.insecureAPI.mktemp -analyzer-disable-checker security.insecureAPI.rand -analyzer-disable-checker security.insecureAPI.strcpy -analyzer-checker security.insecureAPI.vfork -analyzer-disable-checker alpha,apiModeling,core,cplusplus,deadcode,debug,fuchsia,nullability,optin,osx,security,unix,webkit -analyzer-checker alpha.webkit.ForwardDeclChecker,alpha.webkit.MemoryUnsafeCastChecker,alpha.webkit.NoDeleteChecker,alpha.webkit.NoUncheckedPtrMemberChecker,alpha.webkit.NoUnretainedMemberChecker,alpha.webkit.RetainPtrCtorAdoptChecker,alpha.webkit.UncheckedCallArgsChecker,alpha.webkit.UncheckedLocalVarsChecker,alpha.webkit.UncountedCallArgsChecker,alpha.webkit.UncountedLocalVarsChecker,alpha.webkit.UnretainedCallArgsChecker,alpha.webkit.UnretainedLambdaCapturesChecker,alpha.webkit.UnretainedLocalVarsChecker,webkit.NoUncountedMemberChecker,webkit.RefCntblBaseVirtualDtor,webkit.UncountedLambdaCapturesChecker -analyzer-config max-nodes=10000000 -analyzer-config verbose-report-filename=true -fdwarf2-cfi-asm -o /Volumes/Data/worker/Apple-iOS-26-Safer-CPP-Checks-EWS/build/scan-build-output/StaticAnalyzer/WebCore/WebCore/normal/arm64/UnifiedSource276.plist -x c++ /Volumes/Data/worker/Apple-iOS-26-Safer-CPP-Checks-EWS/build/WebKitBuild/Release-iphonesimulator/DerivedSources/WebCore/unified-sources/UnifiedSource276.cpp
1/*
2 * Copyright (C) 2019 Apple Inc. All rights reserved.
3 *
4 * Redistribution and use in source and binary forms, with or without
5 * modification, are permitted provided that the following conditions
6 * are met:
7 * 1. Redistributions of source code must retain the above copyright
8 * notice, this list of conditions and the following disclaimer.
9 * 2. Redistributions in binary form must reproduce the above copyright
10 * notice, this list of conditions and the following disclaimer in the
11 * documentation and/or other materials provided with the distribution.
12 *
13 * THIS SOFTWARE IS PROVIDED BY APPLE INC. AND ITS CONTRIBUTORS ``AS IS''
14 * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
15 * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
16 * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL APPLE INC. OR ITS CONTRIBUTORS
17 * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
18 * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
19 * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
20 * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
21 * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
22 * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF
23 * THE POSSIBILITY OF SUCH DAMAGE.
24 */
25
26#include "config.h"
27#include "LayoutIntegrationLineLayout.h"
28
29#include "BlockFormattingState.h"
30#include "BlockLayoutState.h"
31#include "EventRegion.h"
32#include "FormattingContextBoxIterator.h"
33#include "HitTestLocation.h"
34#include "HitTestRequest.h"
35#include "HitTestResult.h"
36#include "InlineContentCache.h"
37#include "InlineDisplayBoxInlines.h"
38#include "InlineDamage.h"
39#include "InlineFormattingContext.h"
40#include "InlineInvalidation.h"
41#include "InlineItemsBuilder.h"
42#include "LayoutBoxGeometry.h"
43#include "LayoutIntegrationCoverage.h"
44#include "LayoutIntegrationInlineContentBuilder.h"
45#include "LayoutIntegrationInlineContentPainter.h"
46#include "LayoutIntegrationPagination.h"
47#include "LayoutIntegrationUtils.h"
48#include "LayoutTreeBuilder.h"
49#include "PaintInfo.h"
50#include "PlacedFloats.h"
51#include "RenderBlockFlow.h"
52#include "RenderBoxInlines.h"
53#include "RenderBoxModelObjectInlines.h"
54#include "RenderDescendantIterator.h"
55#include "RenderElementStyleInlines.h"
56#include "RenderInline.h"
57#include "RenderLayer.h"
58#include "RenderLayoutState.h"
59#include "RenderLineBreak.h"
60#include "RenderObjectInlines.h"
61#include "RenderView.h"
62#include "SVGTextFragment.h"
63#include "Settings.h"
64#include "ShapeOutsideInfo.h"
65#include <ranges>
66#include <wtf/Assertions.h>
67#include <wtf/Range.h>
68
69namespace WebCore {
70namespace LayoutIntegration {
71
72DEFINE_ALLOCATOR_WITH_HEAP_IDENTIFIER(LayoutIntegration_LineLayout)struct MakeDebugHeapMallocedImplMacroSemicolonifierLayoutIntegration_LineLayout
{ }
;
73
74enum class TypeOfChangeForInvalidation : uint8_t {
75 NodeInsertion,
76 NodeRemoval,
77 NodeMutation
78};
79static bool shouldInvalidateLineLayoutAfterChangeFor(const RenderBlockFlow& rootBlockContainer, const RenderObject& renderer, const LineLayout& lineLayout, TypeOfChangeForInvalidation typeOfChange)
80{
81 auto isSupportedRendererWithChange = [&](auto& renderer) {
82 if (is<RenderText>(renderer))
83 return true;
84 if (!renderer.isInFlow())
85 return false;
86 if (is<RenderLineBreak>(renderer))
87 return true;
88 if (auto* renderBox = dynamicDowncast<RenderBox>(renderer); renderBox && renderBox->hasRelativeDimensions())
89 return false;
90 if (is<RenderReplaced>(renderer))
91 return typeOfChange == TypeOfChangeForInvalidation::NodeInsertion;
92 if (auto* inlineRenderer = dynamicDowncast<RenderInline>(renderer))
93 return typeOfChange == TypeOfChangeForInvalidation::NodeInsertion && !inlineRenderer->firstChild();
94 return false;
95 };
96 if (!isSupportedRendererWithChange(renderer))
97 return true;
98
99 auto isSupportedParent = [&] {
100 auto* parent = renderer.parent();
101 // Content append under existing inline box is not yet supported.
102 return is<RenderBlockFlow>(parent) || (is<RenderInline>(parent) && !parent->everHadLayout());
103 };
104 if (!isSupportedParent())
105 return true;
106 if (rootBlockContainer.containsFloats())
107 return true;
108
109 auto isBidiContent = [&] {
110 if (lineLayout.contentNeedsVisualReordering())
111 return true;
112 if (auto* textRenderer = dynamicDowncast<RenderText>(renderer)) {
113 auto hasStrongDirectionalityContent = textRenderer->hasStrongDirectionalityContent();
114 if (!hasStrongDirectionalityContent) {
115 hasStrongDirectionalityContent = Layout::TextUtil::containsStrongDirectionalityText(textRenderer->text());
116 const_cast<RenderText*>(textRenderer)->setHasStrongDirectionalityContent(*hasStrongDirectionalityContent);
117 }
118 return *hasStrongDirectionalityContent;
119 }
120 if (CheckedPtr renderInline = dynamicDowncast<RenderInline>(renderer)) {
121 auto& style = renderInline->style();
122 return style.writingMode().isBidiRTL() || (style.rtlOrdering() == Order::Logical && style.unicodeBidi() != UnicodeBidi::Normal);
123 }
124 return false;
125 };
126 if (isBidiContent()) {
127 // FIXME: InlineItemsBuilder needs some work to support paragraph level bidi handling.
128 return true;
129 }
130 auto hasFirstLetter = [&] {
131 // FIXME: RenderTreeUpdater::updateTextRenderer produces odd values for offset/length when first-letter is present webkit.org/b/263343
132 if (rootBlockContainer.style().hasPseudoStyle(PseudoElementType::FirstLetter))
133 return true;
134 if (rootBlockContainer.isAnonymous())
135 return rootBlockContainer.containingBlock() && rootBlockContainer.containingBlock()->style().hasPseudoStyle(PseudoElementType::FirstLetter);
136 return false;
137 };
138 if (hasFirstLetter())
139 return true;
140
141 if (auto* previousDamage = lineLayout.damage(); previousDamage && (previousDamage->reasons() != Layout::InlineDamage::Reason::Append || !previousDamage->layoutStartPosition())) {
142 // Only support subsequent append operations where we managed to invalidate the content for partial layout.
143 return true;
144 }
145
146 CheckedRef rootBlockContainerStyle = rootBlockContainer.style();
147 auto shouldBalance = rootBlockContainerStyle->textWrapMode() == TextWrapMode::Wrap && rootBlockContainerStyle->textWrapStyle() == TextWrapStyle::Balance;
148 auto shouldPrettify = rootBlockContainerStyle->textWrapMode() == TextWrapMode::Wrap && rootBlockContainerStyle->textWrapStyle() == TextWrapStyle::Pretty;
149 auto hasAutospace = !rootBlockContainerStyle->textAutospace().isNoAutospace();
150 if (rootBlockContainer.writingMode().isBidiRTL() || shouldBalance || shouldPrettify || hasAutospace)
151 return true;
152
153 auto rootHasNonSupportedRenderer = [&] (bool shouldOnlyCheckForRelativeDimension = false) {
154 for (CheckedPtr sibling = rootBlockContainer.firstChild(); sibling; sibling = sibling->nextSibling()) {
155 if (CheckedPtr inlineBox = dynamicDowncast<RenderInline>(*sibling); inlineBox && !inlineBox->style().textAutospace().isNoAutospace())
156 return true;
157
158 auto siblingHasRelativeDimensions = false;
159 if (CheckedPtr renderBox = dynamicDowncast<RenderBox>(*sibling); renderBox && renderBox->hasRelativeDimensions())
160 siblingHasRelativeDimensions = true;
161
162 if (shouldOnlyCheckForRelativeDimension && !siblingHasRelativeDimensions)
163 continue;
164
165 if (siblingHasRelativeDimensions || (!isAnyOf<RenderText, RenderLineBreak, RenderReplaced>(*sibling)))
166 return true;
167 }
168 return !canUseForLineLayout(rootBlockContainer);
169 };
170 switch (typeOfChange) {
171 case TypeOfChangeForInvalidation::NodeRemoval:
172 return (!renderer.previousSibling() && !renderer.nextSibling()) || rootHasNonSupportedRenderer();
173 case TypeOfChangeForInvalidation::NodeInsertion:
174 return rootHasNonSupportedRenderer(!renderer.nextSibling());
175 case TypeOfChangeForInvalidation::NodeMutation:
176 return rootHasNonSupportedRenderer();
177 default:
178 ASSERT_NOT_REACHED()((void)0);
179 return true;
180 }
181}
182
183static const InlineDisplay::Line& lastLineWithInflowContent(const InlineDisplay::Lines& lines)
184{
185 // Out-of-flow/float content only don't produce lines with inline content. They should not be taken into
186 // account when computing content box height/baselines.
187 for (auto& line : lines | std::views::reverse) {
188 ASSERT(line.boxCount())((void)0);
189 if (line.hasContentfulInFlowBox())
190 return line;
191 }
192 return lines.first();
193}
194
195LineLayout::LineLayout(RenderBlockFlow& flow)
196 : m_rootLayoutBox(BoxTreeUpdater { flow }.build())
197 , m_document(flow.document())
198 , m_layoutState(flow.view().layoutState())
199 , m_blockFormattingState(layoutState().ensureBlockFormattingState(rootLayoutBox()))
200 , m_inlineContentCache(layoutState().inlineContentCache(rootLayoutBox()))
201 , m_boxGeometryUpdater(flow.view().layoutState(), rootLayoutBox())
202{
203}
204
205LineLayout::~LineLayout()
206{
207 CheckedRef rootRenderer = flow();
208 auto shouldPopulateBreakingPositionCache = [&] {
209 auto mayHaveInvalidContent = isDamaged() || !m_inlineContent;
210 if (m_document->renderTreeBeingDestroyed() || mayHaveInvalidContent)
211 return false;
212 return !m_inlineContentCache.inlineItems().isPopulatedFromCache();
213 };
214 if (shouldPopulateBreakingPositionCache())
215 Layout::InlineItemsBuilder::populateBreakingPositionCache(m_inlineContentCache.inlineItems().content(), rootRenderer->document());
216 clearInlineContent();
217 layoutState().destroyInlineContentCache(rootLayoutBox());
218 layoutState().destroyBlockFormattingState(rootLayoutBox());
219 m_boxGeometryUpdater.clear();
220 m_lineDamage = { };
221 m_rootLayoutBox = nullptr;
222
223 BoxTreeUpdater { rootRenderer, *m_document }.tearDown();
224}
225
226static inline bool NODELETE[[clang::annotate_type("webkit.nodelete")]] isContentRenderer(const RenderObject& renderer)
227{
228 // FIXME: These fake renderers have their parent set but are not actually in the tree.
229 return !renderer.isRenderReplica() && !renderer.isRenderScrollbarPart();
230}
231
232RenderBlockFlow* LineLayout::blockContainer(const RenderObject& renderer)
233{
234 if (!isContentRenderer(renderer))
235 return nullptr;
236
237 for (auto* parent = renderer.parent(); parent; parent = parent->parent()) {
238 if (!parent->childrenInline())
239 return nullptr;
240 if (auto* renderBlockFlow = dynamicDowncast<RenderBlockFlow>(*parent))
241 return renderBlockFlow;
242 }
243
244 return nullptr;
245}
246
247bool LineLayout::contains(const RenderElement& renderer) const
248{
249 if (!renderer.layoutBox())
250 return false;
251 if (!renderer.layoutBox()->isInFormattingContextEstablishedBy(rootLayoutBox()))
252 return false;
253 return layoutState().hasBoxGeometry(*renderer.layoutBox());
254}
255
256LineLayout* LineLayout::containing(RenderObject& renderer)
257{
258 if (!isContentRenderer(renderer))
259 return { };
260
261 if (!renderer.isInline()) {
262 // IFC may contain block level boxes (floats and out-of-flow boxes).
263 if (renderer.isRenderSVGBlock()) {
264 // SVG content inside svg root shows up as block (see RenderSVGBlock). We only support inline root svg as "atomic content".
265 return { };
266 }
267 if (renderer.isRenderFrameSet()) {
268 // Since RenderFrameSet is not a RenderBlock, finding container for nested framesets can't use containingBlock ancestor walk.
269 if (CheckedPtr parent = dynamicDowncast<RenderBlockFlow>(renderer.parent()))
270 return parent->inlineLayout();
271 return { };
272 }
273 auto adjustedContainingBlock = [&]() -> RenderBlockFlow* {
274 if (renderer.isOutOfFlowPositioned()) {
275 // Here we are looking for the containing block as if the out-of-flow box was inflow (for static position purpose).
276 CheckedPtr containingBlock = renderer.parent();
277 if (is<RenderInline>(containingBlock))
278 containingBlock = containingBlock->containingBlock();
279 return dynamicDowncast<RenderBlockFlow>(containingBlock.get());
280 }
281 if (renderer.isFloating()) {
282 // Note that containigBlock() on boxes in top layer (i.e. dialog) may return incorrect result during style change even with not-yet-updated style.
283 return dynamicDowncast<RenderBlockFlow>(RenderObject::containingBlockForPositionType(downcast<RenderBox>(renderer).style().position(), renderer));
284 }
285 if (CheckedPtr parentInlineBox = dynamicDowncast<RenderInline>(renderer.parent())) {
286 ASSERT(parentInlineBox->settings().blocksInInlineLayoutEnabled())((void)0);
287 return dynamicDowncast<RenderBlockFlow>(parentInlineBox->containingBlock());
288 }
289 if (auto* parentBlock = dynamicDowncast<RenderBlockFlow>(renderer.parent())) {
290 if (parentBlock->childrenInline()) {
291 ASSERT(parentBlock->settings().anonymousBlockGenerationDisabled())((void)0);
292 return parentBlock;
293 }
294 }
295 return { };
296 };
297 if (CheckedPtr blockContainer = adjustedContainingBlock())
298 return blockContainer->inlineLayout();
299 return { };
300 }
301
302 if (CheckedPtr container = blockContainer(renderer))
303 return container->inlineLayout();
304
305 return { };
306}
307
308const LineLayout* LineLayout::containing(const RenderObject& renderer)
309{
310 return containing(const_cast<RenderObject&>(renderer));
311}
312
313bool LineLayout::canUseFor(const RenderBlockFlow& flow)
314{
315 return canUseForLineLayout(flow);
316}
317
318bool LineLayout::canUseForPreferredWidthComputation(const RenderBlockFlow& flow)
319{
320 return LayoutIntegration::canUseForPreferredWidthComputation(flow);
321}
322
323bool LineLayout::shouldInvalidateLineLayoutAfterContentChange(const RenderBlockFlow& parent, const RenderObject& rendererWithNewContent, const LineLayout& lineLayout)
324{
325 return shouldInvalidateLineLayoutAfterChangeFor(parent, rendererWithNewContent, lineLayout, TypeOfChangeForInvalidation::NodeMutation);
326}
327
328bool LineLayout::shouldInvalidateLineLayoutAfterTreeMutation(const RenderBlockFlow& parent, const RenderObject& renderer, const LineLayout& lineLayout, bool isRemoval)
329{
330 return shouldInvalidateLineLayoutAfterChangeFor(parent, renderer, lineLayout, isRemoval ? TypeOfChangeForInvalidation::NodeRemoval : TypeOfChangeForInvalidation::NodeInsertion);
331}
332
333void LineLayout::updateFormattingContexGeometries(LayoutUnit availableLogicalWidth)
334{
335 m_boxGeometryUpdater.setFormattingContextRootGeometry(availableLogicalWidth);
336 m_inlineContentConstraints = m_boxGeometryUpdater.formattingContextConstraints(availableLogicalWidth);
337 m_boxGeometryUpdater.setFormattingContextContentGeometry(m_inlineContentConstraints->horizontal().logicalWidth, { });
338}
339
340void LineLayout::updateStyle(const RenderObject& renderer)
341{
342 BoxTreeUpdater::updateStyle(renderer);
343}
344
345bool LineLayout::rootStyleWillChange(const RenderBlockFlow& root, const RenderStyle& newStyle)
346{
347 if (!root.layoutBox() || !root.layoutBox()->isElementBox()) {
348 ASSERT_NOT_REACHED()((void)0);
349 return false;
350 }
351 if (!m_inlineContent)
352 return false;
353
354 return Layout::InlineInvalidation { ensureLineDamage(), m_inlineContentCache.inlineItems().content(), m_inlineContent->displayContent() }.rootStyleWillChange(downcast<Layout::ElementBox>(*root.layoutBox()), newStyle);
355}
356
357bool LineLayout::styleWillChange(const RenderElement& renderer, const RenderStyle& newStyle, Style::Difference diff)
358{
359 if (!renderer.layoutBox()) {
360 ASSERT_NOT_REACHED()((void)0);
361 return false;
362 }
363 if (!m_inlineContent)
364 return false;
365
366 return Layout::InlineInvalidation { ensureLineDamage(), m_inlineContentCache.inlineItems().content(), m_inlineContent->displayContent() }.styleWillChange(*renderer.layoutBox(), newStyle, diff);
367}
368
369bool LineLayout::boxContentWillChange(const RenderBox& renderer)
370{
371 if (!m_inlineContent || !renderer.layoutBox())
372 return false;
373
374 return Layout::InlineInvalidation { ensureLineDamage(), m_inlineContentCache.inlineItems().content(), m_inlineContent->displayContent() }.inlineLevelBoxContentWillChange(*renderer.layoutBox());
375}
376
377void LineLayout::updateOverflow()
378{
379 if (!m_inlineContent)
380 return;
381 InlineContentBuilder { flow() }.updateLineOverflow(*m_inlineContent);
382}
383
384std::pair<LayoutUnit, LayoutUnit> LineLayout::computeIntrinsicWidthConstraints()
385{
386 auto parentBlockLayoutState = Layout::BlockLayoutState { m_blockFormattingState.placedFloats(), { } };
387 auto inlineFormattingContext = Layout::InlineFormattingContext { rootLayoutBox(), layoutState(), parentBlockLayoutState };
388 if (m_lineDamage)
389 m_inlineContentCache.resetMinimumMaximumContentSizes();
390 // FIXME: This is where we need to switch between minimum and maximum box geometries.
391 // Currently we only support content where min == max.
392 m_boxGeometryUpdater.setFormattingContextContentGeometry({ }, Layout::IntrinsicWidthMode::Minimum);
393 auto [minimumContentSize, maximumContentSize] = inlineFormattingContext.minimumMaximumContentSize(m_lineDamage.get());
394 return { minimumContentSize, maximumContentSize };
395}
396
397static inline std::optional<Layout::BlockLayoutState::LineClamp> lineClamp(const RenderBlockFlow& rootRenderer)
398{
399 auto& layoutState = *rootRenderer.view().frameView().layoutContext().layoutState();
400 if (auto legacyLineClamp = layoutState.legacyLineClamp())
401 return Layout::BlockLayoutState::LineClamp { std::max(legacyLineClamp->maximumLineCount - legacyLineClamp->currentLineCount, static_cast<size_t>(0)), false, true };
402 if (auto lineClamp = layoutState.lineClamp())
403 return Layout::BlockLayoutState::LineClamp { lineClamp->maximumLines, lineClamp->shouldDiscardOverflow, false };
404 return { };
405}
406
407static inline Layout::BlockLayoutState::TextBoxTrim NODELETE[[clang::annotate_type("webkit.nodelete")]] textBoxTrim(const RenderBlockFlow& rootRenderer)
408{
409 auto textBoxTrim = rootRenderer.view().frameView().layoutContext().textBoxTrim();
A function 'textBoxTrim' has [[clang::annotate_type("webkit.nodelete")]] but it contains code that could destruct an object
410 if (!textBoxTrim)
411 return { };
412
413 auto textBoxTrimForIFC = Layout::BlockLayoutState::TextBoxTrim { };
414 auto isLineInverted = rootRenderer.writingMode().isLineInverted();
415 if (textBoxTrim->trimFirstFormattedLine)
416 textBoxTrimForIFC.add(isLineInverted ? Layout::BlockLayoutState::TextBoxTrimSide::End : Layout::BlockLayoutState::TextBoxTrimSide::Start);
417
418 if (textBoxTrim->lastFormattedLineRoot.get() == &rootRenderer)
419 textBoxTrimForIFC.add(isLineInverted ? Layout::BlockLayoutState::TextBoxTrimSide::Start : Layout::BlockLayoutState::TextBoxTrimSide::End);
420 return textBoxTrimForIFC;
421}
422
423static inline std::optional<Layout::BlockLayoutState::LineGrid> lineGrid(const RenderBlockFlow& rootRenderer)
424{
425 auto& layoutState = *rootRenderer.view().frameView().layoutContext().layoutState();
426 if (CheckedPtr lineGrid = layoutState.lineGrid()) {
427 if (lineGrid->writingMode().computedWritingMode() != rootRenderer.writingMode().computedWritingMode())
428 return { };
429
430 auto layoutOffset = layoutState.layoutOffset();
431 auto lineGridOffset = layoutState.lineGridOffset();
432 if (lineGrid->style().writingMode().isVertical()) {
433 layoutOffset = layoutOffset.transposedSize();
434 lineGridOffset = lineGridOffset.transposedSize();
435 }
436
437 auto columnWidth = lineGrid->style().fontCascade().primaryFont().maxCharWidth();
438 auto rowHeight = LayoutUnit::fromFloatCeil(lineGrid->style().computedLineHeight());
439 auto topRowOffset = lineGrid->borderAndPaddingBefore();
440
441 std::optional<LayoutSize> paginationOrigin;
442 auto pageLogicalTop = 0_lu;
443 if (layoutState.isPaginated()) {
444 paginationOrigin = layoutState.lineGridPaginationOrigin();
445 if (lineGrid->writingMode().isVertical())
446 paginationOrigin = paginationOrigin->transposedSize();
447 pageLogicalTop = rootRenderer.pageLogicalTopForOffset(0_lu);
448 }
449
450 return Layout::BlockLayoutState::LineGrid { layoutOffset, lineGridOffset, columnWidth, rowHeight, topRowOffset, lineGrid->style().fontCascade().primaryFont(), paginationOrigin, pageLogicalTop };
451 }
452
453 return { };
454}
455
456std::optional<LayoutRect> LineLayout::layout(RenderBlockFlow::MarginInfo& marginInfo, ForceFullLayout forcedFullLayout)
457{
458 if (forcedFullLayout == ForceFullLayout::Yes && m_lineDamage)
459 Layout::InlineInvalidation::resetInlineDamage(*m_lineDamage);
460
461 preparePlacedFloats();
462
463 auto isPartialLayout = Layout::InlineInvalidation::mayOnlyNeedPartialLayout(m_lineDamage.get());
464 if (!isPartialLayout) {
465 // FIXME: Partial layout should not rely on previous inline display content.
466 clearInlineContent();
467 }
468
469 ASSERT(m_inlineContentConstraints)((void)0);
470 auto intrusiveInitialLetterBottom = [&]() -> std::optional<LayoutUnit> {
471 if (auto lowestInitialLetterLogicalBottom = flow().lowestInitialLetterLogicalBottom())
472 return { *lowestInitialLetterLogicalBottom - m_inlineContentConstraints->logicalTop() };
473 return { };
474 };
475 auto inlineContentConstraints = [&]() -> Layout::ConstraintsForInlineContent {
476 if (!isPartialLayout || !m_inlineContent)
477 return *m_inlineContentConstraints;
478 auto damagedLineIndex = m_lineDamage->layoutStartPosition()->lineIndex;
479 if (!damagedLineIndex)
480 return *m_inlineContentConstraints;
481 if (damagedLineIndex >= m_inlineContent->displayContent().lines.size()) {
482 ASSERT_NOT_REACHED()((void)0);
483 return *m_inlineContentConstraints;
484 }
485 auto constraintsForInFlowContent = Layout::ConstraintsForInFlowContent { m_inlineContentConstraints->horizontal(), m_lineDamage->layoutStartPosition()->partialContentTop };
486 return { constraintsForInFlowContent, m_inlineContentConstraints->visualLeft(), m_inlineContentConstraints->formattingRootBorderBoxSize() };
487 };
488
489 auto parentBlockLayoutState = Layout::BlockLayoutState {
490 m_blockFormattingState.placedFloats(),
491 Layout::IntegrationUtils::toMarginState(marginInfo),
492 lineClamp(flow()),
493 textBoxTrim(flow()),
494 flow().style().textBoxEdge(),
495 intrusiveInitialLetterBottom(),
496 lineGrid(flow())
497 };
498 auto inlineFormattingContext = Layout::InlineFormattingContext { rootLayoutBox(), layoutState(), parentBlockLayoutState };
499 // Temporary, integration only.
500 inlineFormattingContext.layoutState().setNestedListMarkerOffsets(m_boxGeometryUpdater.takeNestedListMarkerOffsets());
501
502 auto layoutResult = inlineFormattingContext.layout(inlineContentConstraints(), m_lineDamage.get());
503
504 auto didDiscardContent = layoutResult && layoutResult->didDiscardContent;
505 auto repaintRect = constructContent(inlineFormattingContext.layoutState(), WTF::move(layoutResult));
506
507 m_lineDamage = { };
508
509 auto adjustments = adjustContentForPagination(parentBlockLayoutState, isPartialLayout);
510
511 updateRenderTreePositions(adjustments, inlineFormattingContext.layoutState(), didDiscardContent);
512
513 if (m_lineDamage) {
514 // Pagination may require another layout pass.
515 layout(marginInfo);
516
517 ASSERT(!m_lineDamage)((void)0);
518 }
519
520 marginInfo = Layout::IntegrationUtils::toMarginInfo(parentBlockLayoutState.marginState());
521
522 if (!m_inlineContent->hasPaintedInlineLevelBoxes()) {
523 // Nothing to repaint. If layout affects painted block level content those issue their own repaints.
524 return LayoutRect { };
525 }
526
527 return isPartialLayout ? std::optional { LayoutRect { repaintRect } } : std::nullopt;
528}
529
530FloatRect LineLayout::constructContent(const Layout::InlineLayoutState& inlineLayoutState, std::unique_ptr<Layout::InlineLayoutResult>&& layoutResult)
531{
532 auto damagedRect = InlineContentBuilder { flow() }.build(WTF::move(layoutResult), ensureInlineContent(), m_lineDamage.get());
533
534 m_inlineContent->setClearGapBeforeFirstLine(inlineLayoutState.clearGapBeforeFirstLine());
535 m_inlineContent->setClearGapAfterLastLine(inlineLayoutState.clearGapAfterLastLine());
536 m_inlineContent->shrinkToFit();
537
538 m_inlineContentCache.inlineItems().shrinkToFit();
539 m_blockFormattingState.shrinkToFit();
540
541 // FIXME: These needs to be incorporated into the partial damage.
542 auto offsetAndGaps = m_inlineContent->firstLinePaginationOffset() + m_inlineContent->clearBeforeAfterGaps();
543 damagedRect.expand({ 0, offsetAndGaps });
544 return damagedRect;
545}
546
547void LineLayout::updateRenderTreePositions(const Vector<LineAdjustment>& lineAdjustments, const Layout::InlineLayoutState& inlineLayoutState, bool didDiscardContent)
548{
549 if (!m_inlineContent && !didDiscardContent)
550 return;
551
552 CheckedRef blockFlow = flow();
553 auto placedFloatsWritingMode = m_blockFormattingState.placedFloats().blockFormattingContextRoot().style().writingMode();
554
555 auto visualAdjustmentOffset = [&](auto lineIndex) {
556 if (lineAdjustments.isEmpty())
557 return LayoutSize { };
558 if (!placedFloatsWritingMode.isHorizontal())
559 return LayoutSize { lineAdjustments[lineIndex].offset, 0_lu };
560 return LayoutSize { 0_lu, lineAdjustments[lineIndex].offset };
561 };
562
563 if (m_inlineContent) {
564 for (auto& box : m_inlineContent->displayContent().boxes) {
565 if (box.isInlineBox() || box.isTextOrSoftLineBreak())
566 continue;
567
568 CheckedRef layoutBox = box.layoutBox();
569 if (!layoutBox->isAtomicInlineBox())
570 continue;
571
572 CheckedRef renderer = downcast<RenderBox>(*box.layoutBox().rendererForIntegration());
573
574 if (CheckedPtr layer = renderer->layer())
575 layer->setIsHiddenByOverflowTruncation(box.isFullyTruncated());
576
577 renderer->setLocation(Layout::toLayoutPoint(box.visualRectIgnoringBlockDirection().location()));
578 }
579 }
580
581 HashMap<CheckedRef<const Layout::Box>, LayoutSize> floatPaginationOffsetMap;
582 if (!lineAdjustments.isEmpty()) {
583 for (auto& floatItem : m_blockFormattingState.placedFloats().list()) {
584 if (!floatItem.layoutBox() || !floatItem.placedByLine())
585 continue;
586 auto adjustmentOffset = visualAdjustmentOffset(*floatItem.placedByLine());
587 floatPaginationOffsetMap.add(*floatItem.layoutBox(), adjustmentOffset);
588 }
589 }
590
591 for (CheckedRef layoutBox : formattingContextBoxes(rootLayoutBox())) {
592 if (didDiscardContent)
593 layoutBox->rendererForIntegration()->clearNeedsLayout();
594
595 if (!layoutBox->isFloatingPositioned() && !layoutBox->isOutOfFlowPositioned())
596 continue;
597 if (layoutBox->isLineBreakBox())
598 continue;
599 CheckedRef renderer = downcast<RenderBox>(*layoutBox->rendererForIntegration());
600 auto& logicalGeometry = layoutState().geometryForBox(layoutBox);
601
602 if (layoutBox->isFloatingPositioned()) {
603 // FIXME: Find out what to do with discarded (see line-clamp) floats in render tree.
604 auto isInitialLetter = layoutBox->style().pseudoElementType() == PseudoElementType::FirstLetter;
605 auto& floatingObject = flow().insertFloatingBox(renderer);
606 auto [marginBoxVisualRect, borderBoxVisualRect] = Layout::IntegrationUtils::toMarginAndBorderBoxVisualRect(logicalGeometry, m_inlineContentConstraints->formattingRootBorderBoxSize(), placedFloatsWritingMode);
607
608 auto paginationOffset = floatPaginationOffsetMap.getOptional(layoutBox);
609 if (paginationOffset) {
610 marginBoxVisualRect.move(*paginationOffset);
611 borderBoxVisualRect.move(*paginationOffset);
612 }
613 if (isInitialLetter) {
614 auto firstLineTrim = LayoutUnit { inlineLayoutState.firstLineStartTrimForInitialLetter() };
615 marginBoxVisualRect.move(0_lu, -firstLineTrim);
616 borderBoxVisualRect.move(0_lu, -firstLineTrim);
617 }
618
619 floatingObject.setFrameRect(marginBoxVisualRect);
620 floatingObject.setMarginOffset({ borderBoxVisualRect.x() - marginBoxVisualRect.x(), borderBoxVisualRect.y() - marginBoxVisualRect.y() });
621 floatingObject.setIsPlaced(true);
622
623 auto oldRect = renderer->frameRect();
624 renderer->setLocation(borderBoxVisualRect.location());
625
626 if (renderer->checkForRepaintDuringLayout()) {
627 auto hasMoved = oldRect.location() != renderer->location();
628 if (hasMoved)
629 renderer->repaintDuringLayoutIfMoved(oldRect);
630 else
631 renderer->repaint();
632 }
633
634 if (paginationOffset) {
635 // Float content may be affected by the new position.
636 renderer->markForPaginationRelayoutIfNeeded();
637 renderer->layoutIfNeeded();
638 }
639
640 continue;
641 }
642
643 if (layoutBox->isOutOfFlowPositioned()) {
644 ASSERT(renderer->layer())((void)0);
645 CheckedRef layer = *renderer->layer();
646 auto borderBoxLogicalTopLeft = Layout::BoxGeometry::borderBoxTopLeft(logicalGeometry);
647 auto previousStaticPosition = LayoutPoint { layer->staticInlinePosition(), layer->staticBlockPosition() };
648 auto delta = borderBoxLogicalTopLeft - previousStaticPosition;
649 auto hasStaticInlinePositioning = layoutBox->style().hasStaticInlinePosition(renderer->isHorizontalWritingMode());
650
651 if (layoutBox->style().originalDisplay().isInlineType()) {
652 blockFlow->setStaticInlinePositionForChild(renderer, borderBoxLogicalTopLeft.x());
653 if (hasStaticInlinePositioning)
654 renderer->move(delta.width(), delta.height());
655 }
656
657 layer->setStaticBlockPosition(borderBoxLogicalTopLeft.y());
658 layer->setStaticInlinePosition(borderBoxLogicalTopLeft.x());
659 continue;
660 }
661 }
662}
663
664FloatRect LineLayout::applySVGTextFragments(SVGTextFragmentMap&& fragmentMap)
665{
666 auto& boxes = m_inlineContent->displayContent().boxes;
667 auto& lines = m_inlineContent->displayContent().lines;
668 auto& fragments = m_inlineContent->svgTextFragmentsForBoxes();
669 fragments.resize(m_inlineContent->displayContent().boxes.size());
670
671 FloatRect fullBoundaries;
672
673 struct Parent {
674 size_t index;
675 FloatRect boundaries;
676 };
677 Vector<Parent, 8> parentStack;
678
679 auto popParent = [&](const Layout::Box* parent) {
680 while (!parentStack.isEmpty() && &boxes[parentStack.last().index].layoutBox() != parent) {
681 auto boundaries = parentStack.last().boundaries;
682 boxes[parentStack.last().index].setRect(boundaries, boundaries);
683 parentStack.removeLast();
684 if (!parentStack.isEmpty())
685 parentStack.last().boundaries.unite(boundaries);
686 else
687 fullBoundaries = boundaries;
688 }
689 };
690
691 for (size_t i = 0; i < boxes.size(); ++i) {
692 popParent(&boxes[i].layoutBox().parent());
693
694 auto textBox = InlineIterator::svgTextBoxFor(*m_inlineContent, i);
695 if (!textBox) {
696 parentStack.append({ i, { } });
697 continue;
698 }
699
700 auto it = fragmentMap.find(makeKey(*textBox));
701 if (it != fragmentMap.end())
702 fragments[i] = WTF::move(it->value);
703
704 auto boundaries = textBox->calculateBoundariesIncludingSVGTransform();
705 boxes[i].setRect(boundaries, boundaries);
706 parentStack.last().boundaries.unite(boundaries);
707 }
708
709 popParent(nullptr);
710
711 // Move so the top-left text box is at (0, 0).
712 for (auto& box : boxes) {
713 box.moveHorizontally(-fullBoundaries.x());
714 box.moveVertically(-fullBoundaries.y());
715 }
716
717 if (lines.size())
718 lines[0].setLineBoxRectForSVGText(FloatRect { { }, fullBoundaries.size() });
719
720 return fullBoundaries;
721}
722
723void LineLayout::preparePlacedFloats()
724{
725 auto& placedFloats = m_blockFormattingState.placedFloats();
726 placedFloats.clear();
727
728 if (!flow().containsFloats())
729 return;
730
731 auto placedFloatsWritingMode = placedFloats.blockFormattingContextRoot().style().writingMode();
732 auto placedFloatsIsLeftToRight = placedFloatsWritingMode.isLogicalLeftInlineStart();
733 auto isHorizontalWritingMode = placedFloatsWritingMode.isHorizontal();
734 for (auto& floatingObject : *flow().floatingObjectSet()) {
735 if (!floatingObject->renderer())
736 continue;
737
738 auto& visualRect = floatingObject->frameRect();
739
740 auto usedPosition = RenderStyle::usedFloat(*floatingObject->renderer());
741 auto logicalPosition = (usedPosition == UsedFloat::Left) == placedFloatsIsLeftToRight ? Layout::PlacedFloats::Item::Position::Start : Layout::PlacedFloats::Item::Position::End;
742
743 auto boxGeometry = Layout::BoxGeometry { };
744 auto logicalRect = [&] {
745 // FIXME: We are flooring here for legacy compatibility. See FloatingObjects::intervalForFloatingObject and RenderBlockFlow::clearFloats.
746 auto logicalTop = isHorizontalWritingMode ? LayoutUnit(visualRect.y().floor()) : visualRect.x();
747 auto logicalLeft = isHorizontalWritingMode ? visualRect.x() : LayoutUnit(visualRect.y().floor());
748 auto logicalHeight = (isHorizontalWritingMode ? LayoutUnit(visualRect.maxY().floor()) : visualRect.maxX()) - logicalTop;
749 auto logicalWidth = (isHorizontalWritingMode ? visualRect.maxX() : LayoutUnit(visualRect.maxY().floor())) - logicalLeft;
750 if (!placedFloatsIsLeftToRight) {
751 auto rootBorderBoxWidth = m_inlineContentConstraints->visualLeft() + m_inlineContentConstraints->horizontal().logicalWidth + m_inlineContentConstraints->horizontal().logicalLeft;
752 logicalLeft = rootBorderBoxWidth - (logicalLeft + logicalWidth);
753 }
754 return LayoutRect { logicalLeft, logicalTop, logicalWidth, logicalHeight };
755 }();
756
757 boxGeometry.setTopLeft(logicalRect.location());
758 boxGeometry.setContentBoxWidth(logicalRect.width());
759 boxGeometry.setContentBoxHeight(logicalRect.height());
760 boxGeometry.setBorder({ });
761 boxGeometry.setPadding({ });
762 boxGeometry.setHorizontalMargin({ });
763 boxGeometry.setVerticalMargin({ });
764
765 auto shapeOutsideInfo = floatingObject->renderer()->shapeOutsideInfo();
766 RefPtr shape = shapeOutsideInfo ? &shapeOutsideInfo->computedShape() : nullptr;
767
768 placedFloats.add({ logicalPosition, boxGeometry, logicalRect.location(), WTF::move(shape) });
769 }
770}
771
772bool LineLayout::isPaginated() const
773{
774 return m_inlineContent && m_inlineContent->isPaginated();
775}
776
777bool LineLayout::hasEllipsisInBlockDirectionOnLastFormattedLine() const
778{
779 if (!m_inlineContent)
780 return false;
781
782 auto& displayContent = m_inlineContent->displayContent();
783 for (size_t lineIndex = displayContent.lines.size(); lineIndex--;) {
784 auto& line = displayContent.lines[lineIndex];
785 if (!line.hasContentfulInFlowBox()) {
786 // Out-of-flow content could initiate a line with no inline content.
787 continue;
788 }
789 if (!line.hasEllipsis())
790 return false;
791 auto lastFormattedLineEllipsis = displayContent.lineEllipsis(lineIndex);
792 return lastFormattedLineEllipsis->type == InlineDisplay::Line::Ellipsis::Type::Block;
793 }
794 return false;
795}
796
797std::optional<LayoutUnit> LineLayout::clampedContentLogicalHeight() const
798{
799 if (!m_inlineContent)
800 return { };
801
802 auto& lines = m_inlineContent->displayContent().lines;
803 if (lines.isEmpty()) {
804 // Out-of-flow only content (and/or with floats) may produce blank inline content.
805 return { };
806 }
807
808 auto firstTruncatedLineIndex = [&]() -> std::optional<size_t> {
809 for (size_t lineIndex = 0; lineIndex < lines.size(); ++lineIndex) {
810 if (lines[lineIndex].isFullyTruncatedInBlockDirection())
811 return lineIndex;
812 }
813 return { };
814 }();
815 if (!firstTruncatedLineIndex)
816 return { };
817 if (!*firstTruncatedLineIndex) {
818 // This content is fully truncated in the block direction.
819 return LayoutUnit { };
820 }
821
822 auto contentHeight = lines[*firstTruncatedLineIndex - 1].lineBoxLogicalRect().maxY() - lines.first().lineBoxLogicalRect().y();
823 auto offsetAndGaps = m_inlineContent->firstLinePaginationOffset() + m_inlineContent->clearBeforeAfterGaps();
824 return LayoutUnit { contentHeight + offsetAndGaps };
825}
826
827LayoutUnit LineLayout::contentLogicalHeight() const
828{
829 if (!m_inlineContent)
830 return { };
831
832 auto& lines = m_inlineContent->displayContent().lines;
833 if (lines.isEmpty()) {
834 // Out-of-flow only content (and/or with floats) may produce blank inline content.
835 return { };
836 }
837
838 auto contentHeight = lastLineWithInflowContent(lines).lineBoxLogicalRect().maxY() - lines.first().lineBoxLogicalRect().y();
839 auto offsetAndGaps = m_inlineContent->firstLinePaginationOffset() + m_inlineContent->clearBeforeAfterGaps();
840 return LayoutUnit { contentHeight + offsetAndGaps };
841}
842
843bool LineLayout::isSelfCollapsingContent() const
844{
845 if (!m_inlineContent || !m_inlineContent->hasContentfulInFlowBox())
846 return true;
847
848 auto& displayContent = m_inlineContent->displayContent();
849 for (auto& line : displayContent.lines) {
850 if (line.hasContentfulInlineLevelBox())
851 return false;
852 if (line.hasBlockLevelBox()) {
853 auto blockLevelBox = [&]() -> RenderBox* {
854 for (auto index = line.firstBoxIndex(); index < line.lastBoxIndex(); ++index) {
855 if (displayContent.boxes[index].isBlockLevelBox())
856 return dynamicDowncast<RenderBox>(displayContent.boxes[index].layoutBox().rendererForIntegration());
857 ASSERT(displayContent.boxes[index].isInlineBox())((void)0);
858 }
859 return { };
860 };
861 if (CheckedPtr renderBox = blockLevelBox(); renderBox && !renderBox->isSelfCollapsingBlock())
862 return false;
863 }
864 }
865 return true;
866}
867
868bool LineLayout::hasContentfulInlineOrBlockLine() const
869{
870 return m_inlineContent && m_inlineContent->hasContentfulInFlowBox();
871}
872
873bool LineLayout::hasContentfulInlineLine() const
874{
875 return m_inlineContent && m_inlineContent->hasContentfulInlineLevelBox();
876}
877
878size_t LineLayout::lineCount() const
879{
880 if (!m_inlineContent)
881 return 0;
882 if (!m_inlineContent->hasContentfulInFlowBox())
883 return 0;
884
885 auto& lines = m_inlineContent->displayContent().lines;
886 if (lines.isEmpty())
887 return 0;
888 // In some cases (trailing out-of-flow, non-contentful content after <br>) we produce last line with no content but root inline box only.
889 return lines.last().hasContentfulInFlowBox() ? lines.size() : lines.size() - 1;
890}
891
892bool LineLayout::hasInkOverflow() const
893{
894 return m_inlineContent && m_inlineContent->hasInkOverflow();
895}
896
897static float baselineForEmptyContent(const RenderBlockFlow& rootRenderer)
898{
899 CheckedPtr rootLayoutBox = rootRenderer.layoutBox();
900 if (!rootLayoutBox) {
901 ASSERT_NOT_REACHED()((void)0);
902 return { };
903 }
904
905 auto& fontMetrics = rootRenderer.style().metricsOfPrimaryFont();
906 auto ascent = Layout::InlineFormattingUtils::snapToInt(fontMetrics.ascent(), *rootLayoutBox);
907 auto descent = Layout::InlineFormattingUtils::snapToInt(fontMetrics.descent(), *rootLayoutBox);
908 auto baseline = ascent + (rootLayoutBox->firstLineStyle().computedLineHeight() - (ascent + descent)) / 2;
909 return Layout::InlineFormattingUtils::snapToInt(rootRenderer.borderAndPaddingBefore() + baseline, *rootLayoutBox, Layout::InlineFormattingUtils::SnapDirection::Floor);
910}
911
912std::optional<LayoutUnit> LineLayout::firstLineBaseline() const
913{
914 if (!m_inlineContent) {
915 // FIXME: We should really ASSERT here but table code asks for baseline even before running layout.
916 // and we get here by having a LineLayout object initiated by preferred with computation.
917 return { };
918 }
919
920 auto baselineForLineOrBlock = [&](auto& line) -> std::optional<LayoutUnit> {
921 if (!line.hasContentfulInFlowBox())
922 return { };
923
924 if (auto* blockLevelBox = m_inlineContent->blockLevelBoxForLine(line)) {
925 // For block-in-inline look for the baseline of the child box.
926 CheckedRef blockRenderer = downcast<RenderBox>(*blockLevelBox->layoutBox().rendererForIntegration());
927 return blockRenderer->firstLineBaseline();
928 }
929 return LayoutUnit { Layout::InlineFormattingUtils::snapToInt(baselineForLine(line), rootLayoutBox(), Layout::InlineFormattingUtils::SnapDirection::Floor) };
930 };
931
932 for (auto& line : m_inlineContent->displayContent().lines) {
933 if (auto baseline = baselineForLineOrBlock(line))
934 return baseline;
935 }
936
937 return flow().hasLineIfEmpty() ? std::make_optional(LayoutUnit { baselineForEmptyContent(flow()) }) : std::nullopt;
938}
939
940std::optional<LayoutUnit> LineLayout::lastLineBaseline() const
941{
942 if (!m_inlineContent) {
943 // FIXME: We should really ASSERT here but table code asks for baseline even before running layout.
944 // and we get here by having a LineLayout object initiated by preferred with computation.
945 return { };
946 }
947
948 auto baselineForLineOrBlock = [&](auto& line) -> std::optional<LayoutUnit> {
949 if (!line.hasContentfulInFlowBox())
950 return { };
951
952 if (auto* blockLevelBox = m_inlineContent->blockLevelBoxForLine(line)) {
953 // For block-in-inline look for the baseline of the child box.
954 CheckedRef blockRenderer = downcast<RenderBox>(*blockLevelBox->layoutBox().rendererForIntegration());
955 return blockRenderer->lastLineBaseline();
956 }
957 return LayoutUnit { Layout::InlineFormattingUtils::snapToInt(baselineForLine(line), rootLayoutBox(), Layout::InlineFormattingUtils::SnapDirection::Floor) };
958 };
959
960 for (auto& line : m_inlineContent->displayContent().lines | std::views::reverse) {
961 if (auto baseline = baselineForLineOrBlock(line))
962 return baseline;
963 }
964
965 return flow().hasLineIfEmpty() ? std::make_optional(LayoutUnit { baselineForEmptyContent(flow()) }) : std::nullopt;
966}
967
968LayoutUnit LineLayout::baselineForLine(const InlineDisplay::Line& line) const
969{
970 auto rootWritingMode = rootLayoutBox().writingMode();
971 auto baseline = line.baseline();
972 if (rootWritingMode.isLineInverted())
973 baseline = line.lineBoxLogicalRect().height() - baseline;
974
975 switch (rootWritingMode.blockDirection()) {
976 case FlowDirection::TopToBottom:
977 case FlowDirection::BottomToTop:
978 return LayoutUnit { line.lineBoxTop() + baseline };
979 case FlowDirection::LeftToRight:
980 case FlowDirection::RightToLeft:
981 return LayoutUnit { line.lineBoxLeft() + baseline };
982 default:
983 ASSERT_NOT_REACHED()((void)0);
984 return { };
985 }
986}
987
988Vector<LineAdjustment> LineLayout::adjustContentForPagination(const Layout::BlockLayoutState& blockLayoutState, bool isPartialLayout)
989{
990 ASSERT(!m_lineDamage)((void)0);
991
992 if (!m_inlineContent)
993 return { };
994
995 auto& layoutState = *flow().view().frameView().layoutContext().layoutState();
996 if (!layoutState.isPaginated())
997 return { };
998
999 bool allowLayoutRestart = !isPartialLayout;
1000 auto [adjustments, layoutRestartLine] = computeAdjustmentsForPagination(*m_inlineContent, m_blockFormattingState.placedFloats(), allowLayoutRestart, blockLayoutState, flow());
1001
1002 if (!adjustments.isEmpty()) {
1003 adjustLinePositionsForPagination(*m_inlineContent, adjustments);
1004 m_inlineContent->setFirstLinePaginationOffset(adjustments[0].offset);
1005 }
1006
1007 if (layoutRestartLine) {
1008 auto invalidation = Layout::InlineInvalidation { ensureLineDamage(), m_inlineContentCache.inlineItems().content(), m_inlineContent->displayContent() };
1009 auto canRestart = invalidation.restartForPagination(layoutRestartLine->index, layoutRestartLine->offset);
1010 if (!canRestart)
1011 m_lineDamage = { };
1012 }
1013
1014 return adjustments;
1015}
1016
1017void LineLayout::collectOverflow()
1018{
1019 if (!m_inlineContent)
1020 return;
1021
1022 flow().addLayoutOverflow(LayoutRect { m_inlineContent->scrollableOverflow() });
1023 if (!flow().hasNonVisibleOverflow())
1024 flow().addVisualOverflow(LayoutRect { m_inlineContent->inkOverflow() });
1025}
1026
1027InlineContent& LineLayout::ensureInlineContent()
1028{
1029 if (!m_inlineContent)
1030 m_inlineContent = makeUnique<InlineContent>(flow());
1031 return *m_inlineContent;
1032}
1033
1034InlineIterator::TextBoxIterator LineLayout::textBoxesFor(const RenderText& renderText) const
1035{
1036 if (!m_inlineContent)
1037 return { };
1038
1039 CheckedRef layoutBox = *renderText.layoutBox();
1040 auto firstIndex = m_inlineContent->firstBoxIndexForLayoutBox(layoutBox);
1041 if (!firstIndex)
1042 return { };
1043
1044 return InlineIterator::textBoxFor(*m_inlineContent, *firstIndex);
1045}
1046
1047InlineIterator::LeafBoxIterator LineLayout::boxFor(const RenderElement& renderElement) const
1048{
1049 if (!m_inlineContent)
1050 return { };
1051
1052 CheckedRef layoutBox = *renderElement.layoutBox();
1053 auto firstIndex = m_inlineContent->firstBoxIndexForLayoutBox(layoutBox);
1054 if (!firstIndex)
1055 return { };
1056
1057 return InlineIterator::boxFor(*m_inlineContent, *firstIndex);
1058}
1059
1060InlineIterator::InlineBoxIterator LineLayout::firstInlineBoxFor(const RenderInline& renderInline) const
1061{
1062 if (!m_inlineContent)
1063 return { };
1064
1065 CheckedRef layoutBox = *renderInline.layoutBox();
1066 auto* box = m_inlineContent->firstBoxForLayoutBox(layoutBox);
1067 if (!box)
1068 return { };
1069
1070 return InlineIterator::inlineBoxFor(*m_inlineContent, *box);
1071}
1072
1073InlineIterator::InlineBoxIterator LineLayout::firstRootInlineBox() const
1074{
1075 if (!m_inlineContent || !m_inlineContent->hasContentfulInFlowBox())
1076 return { };
1077
1078 return InlineIterator::inlineBoxFor(*m_inlineContent, m_inlineContent->displayContent().boxes.first());
1079}
1080
1081InlineIterator::InlineBoxIterator LineLayout::lastRootInlineBox() const
1082{
1083 if (!m_inlineContent || !m_inlineContent->hasContentfulInFlowBox())
1084 return { };
1085
1086 return InlineIterator::inlineBoxFor(*m_inlineContent, m_inlineContent->displayContent().boxes.last());
1087}
1088
1089InlineIterator::LineBoxIterator LineLayout::firstLineBox() const
1090{
1091 if (!m_inlineContent || !m_inlineContent->hasContentfulInFlowBox())
1092 return { };
1093
1094 return { InlineIterator::LineBoxIteratorModernPath(*m_inlineContent, 0) };
1095}
1096
1097InlineIterator::LineBoxIterator LineLayout::lastLineBox() const
1098{
1099 if (!m_inlineContent || !m_inlineContent->hasContentfulInFlowBox())
1100 return { };
1101
1102 return { InlineIterator::LineBoxIteratorModernPath(*m_inlineContent, m_inlineContent->displayContent().lines.isEmpty() ? 0 : m_inlineContent->displayContent().lines.size() - 1) };
1103}
1104
1105LayoutRect LineLayout::firstInlineBoxRect(const RenderInline& renderInline) const
1106{
1107 if (!m_inlineContent)
1108 return { };
1109
1110 CheckedRef layoutBox = *renderInline.layoutBox();
1111 auto* firstBox = m_inlineContent->firstBoxForLayoutBox(layoutBox);
1112 if (!firstBox)
1113 return { };
1114
1115 // FIXME: We should be able to flip the display boxes soon after the root block
1116 // is finished sizing in one go.
1117 auto firstBoxRect = Layout::toLayoutRect(firstBox->visualRectIgnoringBlockDirection());
1118 switch (rootLayoutBox().writingMode().blockDirection()) {
1119 case FlowDirection::TopToBottom:
1120 case FlowDirection::BottomToTop:
1121 case FlowDirection::LeftToRight:
1122 return firstBoxRect;
1123 case FlowDirection::RightToLeft:
1124 firstBoxRect.setX(flow().width() - firstBoxRect.maxX());
1125 return firstBoxRect;
1126 default:
1127 ASSERT_NOT_REACHED()((void)0);
1128 return firstBoxRect;
1129 }
1130}
1131
1132LayoutRect LineLayout::enclosingBorderBoxRectFor(const RenderInline& renderInline) const
1133{
1134 if (!m_inlineContent)
1135 return { };
1136
1137 // FIXME: This preserves existing output.
1138 if (!m_inlineContent->hasContentfulInFlowBox())
1139 return { };
1140
1141 auto borderBoxLogicalRect = LayoutRect { Layout::BoxGeometry::borderBoxRect(layoutState().geometryForBox(*renderInline.layoutBox())) };
1142 return flow().writingMode().isHorizontal() ? borderBoxLogicalRect : borderBoxLogicalRect.transposedRect();
1143}
1144
1145LayoutRect LineLayout::inkOverflowBoundingBoxRectFor(const RenderInline& renderInline) const
1146{
1147 if (!m_inlineContent)
1148 return { };
1149
1150 CheckedRef layoutBox = *renderInline.layoutBox();
1151
1152 LayoutRect result;
1153 m_inlineContent->traverseNonRootInlineBoxes(layoutBox, [&](auto& inlineBox) {
1154 result.unite(Layout::toLayoutRect(inlineBox.inkOverflow()));
1155 });
1156 return result;
1157}
1158
1159Vector<FloatRect> LineLayout::collectInlineBoxRects(const RenderInline& renderInline) const
1160{
1161 if (!m_inlineContent)
1162 return { };
1163
1164 CheckedRef layoutBox = *renderInline.layoutBox();
1165
1166 Vector<FloatRect> result;
1167 m_inlineContent->traverseNonRootInlineBoxes(layoutBox, [&](auto& inlineBox) {
1168 result.append(inlineBox.visualRectIgnoringBlockDirection());
1169 });
1170 return result;
1171}
1172
1173static LayoutPoint flippedContentOffsetIfNeeded(const RenderBlockFlow& root, const RenderBox& childRenderer, LayoutPoint contentOffset)
1174{
1175 if (root.writingMode().isBlockFlipped())
1176 return root.flipForWritingModeForChild(childRenderer, contentOffset);
1177 return contentOffset;
1178}
1179
1180static LayoutRect flippedRectForWritingMode(const RenderBlockFlow& root, const FloatRect& rect)
1181{
1182 auto flippedRect = LayoutRect { rect };
1183 root.flipForWritingMode(flippedRect);
1184 return flippedRect;
1185}
1186
1187bool LineLayout::isContentConsideredStale() const
1188{
1189 CheckedPtr rootRenderer = m_rootLayoutBox->rendererForIntegration();
1190 if (!rootRenderer)
1191 return true;
1192 if (rootRenderer->normalChildNeedsLayout()) {
1193 // FIXME: Let's bail out on needsLayout() -see webkit.org/b/304002
1194 return true;
1195 }
1196 if (rootRenderer->style().isSkippedRootOrSkippedContent())
1197 return true;
1198 if (m_lineDamage && m_lineDamage->hasDetachedContent())
1199 return true;
1200 return false;
1201}
1202
1203void LineLayout::paint(PaintInfo& paintInfo, const LayoutPoint& paintOffset, const RenderInline* layerRenderer)
1204{
1205 if (!m_inlineContent)
1206 return;
1207
1208 if (isContentConsideredStale()) {
1209 ASSERT_NOT_REACHED()((void)0);
1210 return;
1211 }
1212
1213 auto shouldPaintForPhase = [&] {
1214 switch (paintInfo.phase) {
1215 case PaintPhase::Accessibility:
1216 case PaintPhase::Foreground:
1217 case PaintPhase::EventRegion:
1218 case PaintPhase::TextClip:
1219 case PaintPhase::Mask:
1220 case PaintPhase::Selection:
1221 case PaintPhase::Outline:
1222 case PaintPhase::ChildOutlines:
1223 case PaintPhase::SelfOutline:
1224 return true;
1225 case PaintPhase::ChildBlockBackground:
1226 case PaintPhase::ChildBlockBackgrounds:
1227 case PaintPhase::Float:
1228 // These phases are only needed in blocks-in-inline case.
1229 return m_inlineContent->hasBlockLevelBoxes();
1230 default:
1231 return false;
1232 }
1233 };
1234 if (!shouldPaintForPhase())
1235 return;
1236
1237 if (!m_inlineContent->hasPaintedInlineLevelBoxes()) {
1238 if (!m_inlineContent->hasBlockLevelBoxes())
1239 return;
1240 if (!layerRenderer) {
1241 // Shortcut block-only painting.
1242 flow().paintBlockLevelContentInInline(paintInfo, paintOffset);
1243 return;
1244 }
1245 }
1246
1247 InlineContentPainter { paintInfo, paintOffset, layerRenderer, *m_inlineContent, flow() }.paint();
1248}
1249
1250static FloatRect visibleLineRectIncludingEllipsis(const InlineDisplay::Content& displayContent, size_t lineIndex)
1251{
1252 auto& line = displayContent.lines[lineIndex];
1253 if (line.isFullyTruncatedInBlockDirection())
1254 return { };
1255 if (!line.hasEllipsis() || line.hasContentAfterEllipsisBox())
1256 return line.inkOverflow();
1257
1258 auto ellipsis = displayContent.lineEllipsis(lineIndex);
1259 auto rect = line.lineBoxRect();
1260 if (line.isLeftToRightInlineDirection()) {
1261 auto visibleLineBoxRight = std::min(rect.maxX(), ellipsis->visualRect.maxX());
1262 return { rect.location(), FloatPoint { visibleLineBoxRight, rect.maxY() } };
1263 }
1264 auto visibleLineBoxLeft = std::max(rect.x(), ellipsis->visualRect.x());
1265 return { FloatPoint { visibleLineBoxLeft, rect.y() }, FloatPoint { rect.maxX(), rect.maxY() } };
1266};
1267
1268bool LineLayout::hitTest(const HitTestRequest& request, HitTestResult& result, const HitTestLocation& locationInContainer, const LayoutPoint& accumulatedOffset, HitTestAction hitTestAction, const RenderInline* layerRenderer)
1269{
1270 if (!m_inlineContent)
1271 return false;
1272
1273 // All real inline content is foreground.
1274 if (hitTestAction != HitTestForeground && !m_inlineContent->hasBlockLevelBoxes())
1275 return false;
1276 if (hitTestAction == HitTestBlockBackground)
1277 return false;
1278
1279 if (isContentConsideredStale()) {
1280 ASSERT_NOT_REACHED()((void)0);
1281 return false;
1282 }
1283
1284 auto hitTestBoundingBox = locationInContainer.boundingBox();
1285 hitTestBoundingBox.moveBy(-accumulatedOffset);
1286 auto boxRange = m_inlineContent->boxesForRect(hitTestBoundingBox);
1287
1288 LayerPaintScope layerPaintScope(layerRenderer);
1289
1290 for (auto& box : boxRange | std::views::reverse) {
1291 if (!layerPaintScope.testIsIncludesAndUpdate(box))
1292 continue;
1293
1294 bool visibleForHitTesting = request.userTriggered() ? box.isVisible() : box.isVisibleIgnoringUsedVisibility();
1295 if (!visibleForHitTesting)
1296 continue;
1297
1298 auto shouldHitTestForPhase = [&] {
1299 switch (hitTestAction) {
1300 case HitTestForeground:
1301 // Inline boxes around block-in-inline are hit tested in block background phases.
1302 return !m_inlineContent->isInlineBoxWrapperForBlockLevelBox(box);
1303 case HitTestChildBlockBackground:
1304 case HitTestChildBlockBackgrounds:
1305 return box.isBlockLevelBox() || m_inlineContent->isInlineBoxWrapperForBlockLevelBox(box);
1306 case HitTestFloat:
1307 return box.isBlockLevelBox();
1308 case HitTestBlockBackground:
1309 break;
1310 }
1311 ASSERT_NOT_REACHED()((void)0);
1312 return false;
1313 };
1314
1315 if (!shouldHitTestForPhase())
1316 continue;
1317
1318 CheckedRef renderer = *box.layoutBox().rendererForIntegration();
1319
1320 if (box.isBlockLevelBox()) {
1321 CheckedRef renderBox = downcast<RenderBox>(renderer.get());
1322 auto childHitTest = hitTestAction == HitTestChildBlockBackgrounds ? HitTestChildBlockBackground : hitTestAction;
1323 LayoutPoint childPoint = flippedContentOffsetIfNeeded(flow(), renderBox, accumulatedOffset);
1324 if (!renderBox->hasSelfPaintingLayer() && renderBox->nodeAtPoint(request, result, locationInContainer, childPoint, childHitTest))
1325 return true;
1326 continue;
1327 }
1328
1329 if (box.isAtomicInlineBox()) {
1330 if (renderer->hitTest(request, result, locationInContainer, flippedContentOffsetIfNeeded(flow(), downcast<RenderBox>(renderer.get()), accumulatedOffset)))
1331 return true;
1332 continue;
1333 }
1334
1335 auto lineRect = visibleLineRectIncludingEllipsis(m_inlineContent->displayContent(), box.lineIndex());
1336 auto boxRect = flippedRectForWritingMode(flow(), InlineDisplay::Box::visibleRectIgnoringBlockDirection(box, lineRect));
1337 boxRect.moveBy(accumulatedOffset);
1338
1339 if (!locationInContainer.intersects(boxRect))
1340 continue;
1341
1342 CheckedPtr renderElementPtr = dynamicDowncast<RenderElement>(renderer.get());
1343 CheckedRef elementRenderer = renderElementPtr ? *renderElementPtr : *renderer->parent();
1344 if (!elementRenderer->visibleToHitTesting(request))
1345 continue;
1346
1347 renderer->updateHitTestResult(result, flow().flipForWritingMode(locationInContainer.point() - toLayoutSize(accumulatedOffset)));
1348 if (result.addNodeToListBasedTestResult(protect(renderer->nodeForHitTest()).get(), request, locationInContainer, boxRect) == HitTestProgress::Stop)
1349 return true;
1350 }
1351
1352 return false;
1353}
1354
1355void LineLayout::shiftLinesByInBlockDirection(LayoutUnit blockShift)
1356{
1357 if (!m_inlineContent)
1358 return;
1359 bool isHorizontalWritingMode = flow().writingMode().isHorizontal();
1360
1361 auto& displayContent = m_inlineContent->displayContent();
1362 for (size_t lineIndex = 0; lineIndex < displayContent.lines.size(); ++lineIndex)
1363 displayContent.moveLineInBlockDirection(lineIndex, blockShift);
1364
1365 auto deltaX = isHorizontalWritingMode ? 0_lu : blockShift;
1366 auto deltaY = isHorizontalWritingMode ? blockShift : 0_lu;
1367 for (auto& box : m_inlineContent->displayContent().boxes) {
1368 if (isHorizontalWritingMode)
1369 box.moveVertically(blockShift);
1370 else
1371 box.moveHorizontally(blockShift);
1372
1373 if (box.isAtomicInlineBox()) {
1374 CheckedRef renderer = downcast<RenderBox>(*box.layoutBox().rendererForIntegration());
1375 renderer->move(deltaX, deltaY);
1376 }
1377 }
1378
1379 for (CheckedRef layoutBox : formattingContextBoxes(rootLayoutBox())) {
1380 if (layoutBox->isOutOfFlowPositioned() && layoutBox->style().hasStaticBlockPosition(isHorizontalWritingMode)) {
1381 if (CheckedPtr layerRenderer = dynamicDowncast<RenderLayerModelObject>(layoutBox->rendererForIntegration())) {
1382 if (CheckedPtr layer = layerRenderer->layer())
1383 layer->setStaticBlockPosition(layer->staticBlockPosition() + blockShift);
1384 }
1385 if (CheckedPtr renderBox = dynamicDowncast<RenderBox>(layoutBox->rendererForIntegration()))
1386 renderBox->move(deltaX, deltaY);
1387 }
1388 }
1389}
1390
1391bool LineLayout::insertedIntoTree(const RenderElement& parent, RenderObject& child)
1392{
1393 if (flow().style().isSkippedRootOrSkippedContent())
1394 return false;
1395
1396 if (!m_inlineContent) {
1397 // This should only be called on partial layout.
1398 ASSERT_NOT_REACHED()((void)0);
1399 return false;
1400 }
1401
1402 CheckedRef childLayoutBox = BoxTreeUpdater { flow() }.insert(parent, child, child.previousSibling());
1403 if (CheckedPtr childInlineTextBox = dynamicDowncast<Layout::InlineTextBox>(childLayoutBox.get())) {
1404 auto invalidation = Layout::InlineInvalidation { ensureLineDamage(), m_inlineContentCache.inlineItems().content(), m_inlineContent->displayContent() };
1405 return invalidation.textInserted(*childInlineTextBox);
1406 }
1407
1408 if (childLayoutBox->isLineBreakBox() || childLayoutBox->isReplacedBox() || childLayoutBox->isInlineBox()) {
1409 auto invalidation = Layout::InlineInvalidation { ensureLineDamage(), m_inlineContentCache.inlineItems().content(), m_inlineContent->displayContent() };
1410 return invalidation.inlineLevelBoxInserted(childLayoutBox);
1411 }
1412
1413 ASSERT_NOT_IMPLEMENTED_YET()((void)0);
1414 return false;
1415}
1416
1417bool LineLayout::removedFromTree(const RenderElement& parent, RenderObject& child)
1418{
1419 if (flow().style().isSkippedRootOrSkippedContent())
1420 return false;
1421
1422 if (!child.everHadLayout()) {
1423 ensureLineDamage().addDetachedBox(BoxTreeUpdater { flow() }.remove(parent, child));
1424 return false;
1425 }
1426
1427 if (!m_inlineContent) {
1428 // This should only be called on partial layout.
1429 ASSERT_NOT_REACHED()((void)0);
1430 return false;
1431 }
1432
1433 CheckedRef childLayoutBox = *child.layoutBox();
1434 CheckedPtr childInlineTextBox = dynamicDowncast<Layout::InlineTextBox>(childLayoutBox.get());
1435 auto invalidation = Layout::InlineInvalidation { ensureLineDamage(), m_inlineContentCache.inlineItems().content(), m_inlineContent->displayContent() };
1436 auto boxIsInvalidated = childInlineTextBox ? invalidation.textWillBeRemoved(*childInlineTextBox) : childLayoutBox->isLineBreakBox() ? invalidation.inlineLevelBoxWillBeRemoved(childLayoutBox) : false;
1437 if (boxIsInvalidated)
1438 m_lineDamage->addDetachedBox(BoxTreeUpdater { flow() }.remove(parent, child));
1439 return boxIsInvalidated;
1440}
1441
1442bool LineLayout::updateTextContent(const RenderText& textRenderer, std::optional<size_t> offset, size_t oldLength)
1443{
1444 if (flow().style().isSkippedRootOrSkippedContent())
1445 return false;
1446
1447 if (!m_inlineContent) {
1448 // This is supposed to be only called on partial layout, but
1449 // RenderText::setText may be (force) called after min/max size computation and before layout.
1450 // We may need to invalidate anyway to clean up inline item list.
1451 return false;
1452 }
1453
1454 BoxTreeUpdater::updateContent(textRenderer);
1455
1456 auto invalidation = Layout::InlineInvalidation { ensureLineDamage(), m_inlineContentCache.inlineItems().content(), m_inlineContent->displayContent() };
1457 CheckedRef inlineTextBox = *textRenderer.layoutBox();
1458 if (!offset) {
1459 // Text content is entirely replaced.
1460 return invalidation.textInserted(inlineTextBox, { 0 });
1461 }
1462
1463 if (*offset == oldLength) {
1464 // This is essentially just an append.
1465 return invalidation.textInserted(inlineTextBox);
1466 }
1467
1468 int delta = inlineTextBox->content().length() - oldLength;
1469 return delta >= 0 ? invalidation.textInserted(inlineTextBox, offset) : invalidation.textWillBeRemoved(inlineTextBox, offset);
1470}
1471
1472void LineLayout::releaseCaches(RenderView& view)
1473{
1474 for (CheckedRef renderer : descendantsOfType<RenderBlockFlow>(view)) {
1475 if (CheckedPtr lineLayout = renderer->inlineLayout())
1476 lineLayout->releaseCachesAndResetDamage();
1477 }
1478}
1479
1480void LineLayout::releaseCachesAndResetDamage()
1481{
1482 m_inlineContentCache.inlineItems().content().clear();
1483 if (m_inlineContent)
1484 m_inlineContent->releaseCaches();
1485 if (m_lineDamage)
1486 Layout::InlineInvalidation::resetInlineDamage(*m_lineDamage);
1487}
1488
1489void LineLayout::clearInlineContent()
1490{
1491 if (!m_inlineContent)
1492 return;
1493 m_inlineContent = nullptr;
1494}
1495
1496Layout::InlineDamage& LineLayout::ensureLineDamage()
1497{
1498 if (!m_lineDamage)
1499 m_lineDamage = makeUnique<Layout::InlineDamage>();
1500 return *m_lineDamage;
1501}
1502
1503bool LineLayout::contentNeedsVisualReordering() const
1504{
1505 return m_inlineContentCache.inlineItems().requiresVisualReordering();
1506}
1507
1508bool LineLayout::hasBlocks() const
1509{
1510 return m_inlineContent && m_inlineContent->hasBlockLevelBoxes();
1511}
1512
1513#if ENABLE(TREE_DEBUGGING)(defined ENABLE_TREE_DEBUGGING && ENABLE_TREE_DEBUGGING
)
1514void LineLayout::outputLineTree(WTF::TextStream& stream, size_t depth) const
1515{
1516 if (m_inlineContent)
1517 showInlineContent(stream, *m_inlineContent, depth, isDamaged());
1518}
1519#endif
1520
1521}
1522}