Bug Summary

File:Volumes/Data/worker/Apple-iOS-26-Safer-CPP-Checks-EWS/build/Source/WebCore/style/AnchorPositionEvaluator.cpp
Warning:line 168, column 15
Local variable 'view' is uncounted and unsafe

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 UnifiedSource439.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/UnifiedSource439.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/UnifiedSource439.plist -x c++ /Volumes/Data/worker/Apple-iOS-26-Safer-CPP-Checks-EWS/build/WebKitBuild/Release-iphonesimulator/DerivedSources/WebCore/unified-sources/UnifiedSource439.cpp
1/*
2 * Copyright (C) 2024-2025 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'' AND ANY
14 * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
15 * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
16 * DISCLAIMED. IN NO EVENT SHALL APPLE INC. OR ITS CONTRIBUTORS BE LIABLE FOR ANY
17 * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
18 * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
19 * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON
20 * ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
21 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
22 * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
23 */
24
25#include "config.h"
26#include "AnchorPositionEvaluator.h"
27
28#include "CSSPropertyNames.h"
29#include "CSSValue.h"
30#include "CSSValueKeywords.h"
31#include "ContainerNodeInlines.h"
32#include "Document.h"
33#include "Element.h"
34#include "Node.h"
35#include "NodeRenderStyle.h"
36#include "PopoverData.h"
37#include "PositionedLayoutConstraints.h"
38#include "RenderBoxInlines.h"
39#include "RenderBlock.h"
40#include "RenderBoxModelObjectInlines.h"
41#include "RenderFragmentedFlow.h"
42#include "RenderInline.h"
43#include "RenderLayer.h"
44#include "RenderLayerCompositor.h"
45#include "RenderObjectInlines.h"
46#include "RenderStyle.h"
47#include "RenderStyle+GettersInlines.h"
48#include "RenderStyle+SettersInlines.h"
49#include "RenderView.h"
50#include "StyleBuilderState.h"
51#include "StyleScope.h"
52#include "WritingMode.h"
53#include <ranges>
54
55namespace WebCore {
56
57AnchorScrollSnapshot::AnchorScrollSnapshot(const RenderBox& scroller, LayoutPoint snapshot)
58 : m_scroller(scroller)
59 , m_scrollSnapshot(snapshot)
60{ }
61
62AnchorScrollSnapshot::AnchorScrollSnapshot(LayoutPoint snapshot)
63 : m_scroller(nullptr)
64 , m_scrollSnapshot(snapshot)
65{ }
66
67inline LayoutSize AnchorScrollSnapshot::adjustmentForCurrentScrollPosition() const
68{
69 if (m_scroller)
70 return m_scrollSnapshot - m_scroller->scrollPosition();
71 return { };
72}
73
74inline bool AnchorScrollAdjuster::isEmpty() const
75{
76 return !m_scrollSnapshots.size() && !(m_hasChainedAnchor | m_hasStickyAnchor);
77}
78
79AnchorScrollAdjuster::AnchorScrollAdjuster(RenderBox& anchored, const RenderBoxModelObject& defaultAnchor)
80 : m_anchored(anchored)
81{
82 auto& style = anchored.style();
83
84 auto compensatedAxes = style.anchorFunctionScrollCompensatedAxes();
85 m_needsXAdjustment = compensatedAxes.contains(BoxAxis::Horizontal);
86 m_needsYAdjustment = compensatedAxes.contains(BoxAxis::Vertical);
87
88 auto containingWritingMode = anchored.container()->style().writingMode();
89 if (auto positionAreaValue = style.positionArea().tryValue()) {
90 if (positionAreaValue->coordMatchedTrackForAxis(BoxAxis::Horizontal, containingWritingMode, style.writingMode()) != Style::PositionAreaTrack::SpanAll)
91 m_needsXAdjustment |= true;
92 else {
93 if (containingWritingMode.isHorizontal()) {
94 auto alignment = style.justifySelf();
95 m_needsXAdjustment |= alignment.isAuto() || alignment.isNormal() || alignment.isAnchorCenter();
96 } else {
97 auto alignment = style.alignSelf();
98 m_needsXAdjustment |= alignment.isAuto() || alignment.isNormal() || alignment.isAnchorCenter();
99 }
100 }
101 if (positionAreaValue->coordMatchedTrackForAxis(BoxAxis::Vertical, containingWritingMode, style.writingMode()) != Style::PositionAreaTrack::SpanAll)
102 m_needsYAdjustment |= true;
103 else {
104 if (containingWritingMode.isHorizontal()) {
105 auto alignment = style.alignSelf();
106 m_needsYAdjustment |= alignment.isAuto() || alignment.isNormal() || alignment.isAnchorCenter();
107 } else {
108 auto alignment = style.justifySelf();
109 m_needsYAdjustment |= alignment.isAuto() || alignment.isNormal() || alignment.isAnchorCenter();
110 }
111 }
112 }
113
114 if (!m_needsXAdjustment) {
115 m_needsXAdjustment = containingWritingMode.isHorizontal()
116 ? style.justifySelf().isAnchorCenter()
117 : style.alignSelf().isAnchorCenter();
118 }
119 if (!m_needsYAdjustment) {
120 m_needsYAdjustment = containingWritingMode.isHorizontal()
121 ? style.alignSelf().isAnchorCenter()
122 : style.alignSelf().isAnchorCenter();
123 }
124
125 m_isHidden = style.isForceHidden();
126
127 if ((m_hasStickyAnchor = defaultAnchor.isStickilyPositioned()))
128 m_stickySnapshot = defaultAnchor.stickyPositionOffset();
129
130 if (auto anchorBox = dynamicDowncast<RenderBox>(defaultAnchor)) {
131 if (CheckedPtr chainedAnchor = Style::AnchorPositionEvaluator::defaultAnchorForBox(*anchorBox))
132 m_hasChainedAnchor = true;
133 }
134}
135
136RenderBox* AnchorScrollAdjuster::anchored() const
137{
138 return m_anchored.ptr();
139}
140
141bool AnchorScrollAdjuster::recaptureDiffers(const AnchorScrollAdjuster& other) const
142{
143 bool same = m_anchored.ptr() == other.m_anchored.ptr()
144 && m_scrollSnapshots == other.m_scrollSnapshots
145 && m_stickySnapshot == other.m_stickySnapshot;
146 return !same;
147}
148
149void AnchorScrollAdjuster::addSnapshot(const RenderBox& scroller)
150{
151 ASSERT(scroller.hasPotentiallyScrollableOverflow() && !is<RenderView>(scroller))((void)0);
152 m_scrollSnapshots.constructAndAppend(scroller, scroller.constrainedScrollPosition());
153}
154
155void AnchorScrollAdjuster::addViewportSnapshot(const RenderView& renderView)
156{
157 auto& view = renderView.frameView();
158 auto position = view.constrainedScrollPosition(ScrollPosition(view.scrollPositionRespectingCustomFixedPosition()));
159 m_scrollSnapshots.insert(0, AnchorScrollSnapshot { position });
160 m_adjustForViewport = true;
161}
162
163LayoutSize AnchorScrollAdjuster::adjustmentForViewport(const RenderView& renderView) const
164{
165 if (m_adjustForViewport) {
166 // Viewport snapshot is stored in the first slot.
167 ASSERT(m_scrollSnapshots.size() && !m_scrollSnapshots.first().m_scroller)((void)0);
168 auto& view = renderView.frameView();
Local variable 'view' is uncounted and unsafe
169 return m_scrollSnapshots.first().m_scrollSnapshot
170 - view.constrainedScrollPosition(IntPoint(view.scrollPositionRespectingCustomFixedPosition()));
171 }
172 return { };
173}
174
175LayoutSize AnchorScrollAdjuster::accumulateAdjustments(const RenderView& renderView, const RenderBox& anchored) const
176{
177 ASSERT(m_anchored.ptr() == &anchored)((void)0);
178 LayoutSize scrollAdjustment;
179
180 if (m_hasChainedAnchor || m_hasStickyAnchor) {
181 if (CheckedPtr defaultAnchor = Style::AnchorPositionEvaluator::defaultAnchorForBox(anchored)) {
182 auto defaultAnchorBox = dynamicDowncast<RenderBox>(defaultAnchor.get());
183 ASSERT(defaultAnchorBox)((void)0); // We shouldn't exist if there's no default anchor.
184 if (defaultAnchorBox) {
185 // The anchor may itself be scroll-compensated. Recurse if needed.
186 if (auto chainedAdjuster = renderView.layoutContext().anchorScrollAdjusterFor(*defaultAnchorBox))
187 scrollAdjustment += chainedAdjuster->accumulateAdjustments(renderView, *defaultAnchorBox);
188 // Compensate for sticky adjustments.
189 if (m_hasStickyAnchor)
190 scrollAdjustment += defaultAnchorBox->stickyPositionOffset() - m_stickySnapshot;
191 }
192 }
193 }
194
195 for (auto snapshot : m_scrollSnapshots)
196 scrollAdjustment += snapshot.adjustmentForCurrentScrollPosition();
197 scrollAdjustment += adjustmentForViewport(renderView);
198
199 if (!m_needsXAdjustment)
200 scrollAdjustment.setWidth(0);
201 if (!m_needsYAdjustment)
202 scrollAdjustment.setHeight(0);
203 return scrollAdjustment;
204}
205
206void AnchorScrollAdjuster::setFallbackLimits(const RenderBox& anchored)
207{
208 auto xConstraints = PositionedLayoutConstraints { anchored, anchored.writingMode().isHorizontal() ? LogicalBoxAxis::Inline : LogicalBoxAxis::Block };
209 auto yConstraints = PositionedLayoutConstraints { anchored, anchored.writingMode().isHorizontal() ? LogicalBoxAxis::Block : LogicalBoxAxis::Inline };
210 auto xLimits = xConstraints.originalContainingRange();
211 auto yLimits = yConstraints.originalContainingRange();
212
213 auto marginRect = anchored.marginBoxRect();
214 marginRect.moveBy(anchored.location());
215 m_fallbackLimits.m_min.setWidth(xLimits.min() - marginRect.x());
216 m_fallbackLimits.m_min.setHeight(yLimits.min() - marginRect.y());
217 m_fallbackLimits.m_max.setWidth(xLimits.max() - marginRect.maxX());
218 m_fallbackLimits.m_max.setHeight(yLimits.max() - marginRect.maxY());
219
220 m_hasFallback = true;
221}
222
223bool AnchorScrollAdjuster::invalidateForScroller(const RenderBox& scroller)
224{
225 bool anchoredNeedsInvalidation = false;
226 for (auto snapshot : m_scrollSnapshots) {
227 if (snapshot.m_scroller.get() == &scroller) {
228 anchoredNeedsInvalidation = true;
229 break;
230 }
231 }
232 if (anchoredNeedsInvalidation) {
233 m_anchored->setNeedsLayout();
234 ASSERT(m_anchored->element())((void)0);
235 if (CheckedPtr element = m_anchored->element())
236 element->invalidateForAnchorRectChange();
237 }
238 return anchoredNeedsInvalidation;
239}
240
241namespace Style {
242
243WTF_MAKE_TZONE_ALLOCATED_IMPL(AnchorPositionedState)::bmalloc::api::HeapRef AnchorPositionedState::s_heapRef; const
TZoneSpecification AnchorPositionedState::s_heapSpec = { &
AnchorPositionedState::s_heapRef, TZoneSpecification::encodeSize
<AnchorPositionedState>(), TZoneSpecification::encodeAlignment
<AnchorPositionedState>(), TZoneSpecification::encodeCategory
<AnchorPositionedState>(), ::bmalloc::api::compactAllocationMode
<AnchorPositionedState>(), TZoneSpecification::encodeDescriptor
<AnchorPositionedState>(), }; void* AnchorPositionedState
::operatorNewSlow(size_t size) { if constexpr (::bmalloc::api
::compactAllocationMode<AnchorPositionedState>() == CompactAllocationMode
::Compact) return ::bmalloc::api::tzoneAllocateCompactSlow(size
, s_heapSpec); return ::bmalloc::api::tzoneAllocateNonCompactSlow
(size, s_heapSpec); } using __makeBtzoneMallocedInlineMacroSemicolonifier
__attribute__((unused)) = int
;
244
245static inline void clearAnchorScrollSnapshots(RenderBox& anchored)
246{
247 if (!anchored.layer()->anchorScrollAdjustment())
248 return;
249 anchored.layoutContext().unregisterAnchorScrollAdjusterFor(anchored);
250}
251
252void AnchorPositionEvaluator::captureScrollSnapshots(RenderBox& anchored, bool invalidateStyleForScrollPositionChanges)
253{
254 if (!anchored.layer())
255 return;
256
257 CheckedPtr defaultAnchor = AnchorPositionEvaluator::defaultAnchorForBox(anchored);
258 if (!defaultAnchor)
259 return clearAnchorScrollSnapshots(anchored);
260
261 AnchorScrollAdjuster adjuster(anchored, *defaultAnchor);
262 if (!adjuster.mayNeedAdjustment()) // Note: We sometimes hit this path during interleaved layout because style bits aren't set yet.
263 return clearAnchorScrollSnapshots(anchored);
264
265 CheckedPtr containingBlock = anchored.containingBlock();
266 ASSERT(defaultAnchor->isDescendantOf(containingBlock.get()))((void)0);
267
268 bool isFixedAnchor = defaultAnchor->layer() && defaultAnchor->layer()->behavesAsFixed();
269 for (auto* ancestor = defaultAnchor->container(); ancestor && ancestor != containingBlock; ancestor = ancestor->container()) {
270 if (auto* box = dynamicDowncast<RenderBox>(ancestor)) {
271 if (box->hasPotentiallyScrollableOverflow())
272 adjuster.addSnapshot(*box);
273 if (box->layer() && box->layer()->behavesAsFixed())
274 isFixedAnchor = true;
275 }
276 }
277
278 if (anchored.layer() && anchored.layer()->behavesAsFixed() && !isFixedAnchor)
279 adjuster.addViewportSnapshot(anchored.view());
280
281 if (adjuster.isEmpty())
282 return clearAnchorScrollSnapshots(anchored);
283
284 if (!anchored.style().positionTryFallbacks().isNone()
285 || anchored.style().positionVisibility().contains(PositionVisibilityValue::NoOverflow))
286 adjuster.setFallbackLimits(anchored);
287
288 auto captureDiff = anchored.layoutContext().registerAnchorScrollAdjuster(WTF::move(adjuster));
289 if (invalidateStyleForScrollPositionChanges && AnchorScrollAdjuster::SnapshotsDiffer == captureDiff && anchored.style().usesAnchorFunctions()) {
290 // Scroll positions changed since the last capture, which means anchor() resolution needs updating.
291 if (CheckedPtr element = anchored.element())
292 element->invalidateForAnchorRectChange();
293 }
294 anchored.layer()->setAnchorScrollAdjustment({ });
295}
296
297void AnchorPositionEvaluator::updateScrollAdjustments(RenderView& renderView)
298{
299 // https://drafts.csswg.org/css-anchor-position-1/#scroll
300 auto& layoutContext = renderView.layoutContext();
301 bool needsRecompositing = false;
302
303 for (auto adjuster : layoutContext.anchorScrollAdjusters()) {
304 CheckedPtr anchored = adjuster.anchored();
305 if (!anchored || !anchored->layer()) {
306 ASSERT_NOT_REACHED()((void)0); // RenderBox failed to clean up.
307 continue;
308 }
309
310 LayoutSize scrollOffset = adjuster.accumulateAdjustments(renderView, *anchored);
311 if (!anchored->layer()->setAnchorScrollAdjustment(scrollOffset))
312 continue;
313
314 bool shouldBeHidden = false;
315 bool needsInvalidation = false;
316 if (adjuster.hasFallbackLimits()) {
317 if (adjuster.exceedsFallbackLimits(scrollOffset)) {
318 if (!anchored->style().positionTryFallbacks().isNone()) {
319 anchored->setNeedsLayout();
320 needsInvalidation = true;
321 } else
322 shouldBeHidden = anchored->style().positionVisibility().contains(PositionVisibilityValue::NoOverflow);
323 }
324 }
325 if (!shouldBeHidden && anchored->style().positionVisibility().contains(PositionVisibilityValue::AnchorsVisible))
326 shouldBeHidden = AnchorPositionEvaluator::isDefaultAnchorInvisibleOrClippedByInterveningBoxes(*anchored);
327
328 if (needsInvalidation || shouldBeHidden != adjuster.isHidden()) {
329 ASSERT(anchored->element())((void)0);
330 if (CheckedPtr element = anchored->element())
331 element->invalidateForAnchorRectChange(); // FIXME: Optimize this.
332 } else
333 needsRecompositing = true;
334 };
335
336 if (needsRecompositing && !layoutContext.isInLayout())
337 renderView.compositor().scheduleCompositingLayerUpdate();
338}
339
340
341static const ScopedName& implicitAnchorElementName()
342{
343 // User specified anchor names start with "--".
344 static NeverDestroyed<ScopedName> name { ScopedName { "implicit-anchor-element"_s } };
345 return name;
346}
347
348static BoxAxis NODELETE[[clang::annotate_type("webkit.nodelete")]] mapInsetPropertyToPhysicalAxis(CSSPropertyID id, const WritingMode writingMode)
349{
350 switch (id) {
351 case CSSPropertyLeft:
352 case CSSPropertyRight:
353 return BoxAxis::Horizontal;
354 case CSSPropertyTop:
355 case CSSPropertyBottom:
356 return BoxAxis::Vertical;
357 case CSSPropertyInsetInlineStart:
358 case CSSPropertyInsetInlineEnd:
359 return mapAxisLogicalToPhysical(writingMode, LogicalBoxAxis::Inline);
360 case CSSPropertyInsetBlockStart:
361 case CSSPropertyInsetBlockEnd:
362 return mapAxisLogicalToPhysical(writingMode, LogicalBoxAxis::Block);
363 default:
364 ASSERT_NOT_REACHED()((void)0);
365 return BoxAxis::Horizontal;
366 }
367}
368
369static LogicalBoxAxis NODELETE[[clang::annotate_type("webkit.nodelete")]] mapInsetPropertyToLogicalAxis(CSSPropertyID id, const WritingMode writingMode)
370{
371 switch (id) {
372 case CSSPropertyLeft:
373 case CSSPropertyRight:
374 return writingMode.isHorizontal() ? LogicalBoxAxis::Inline : LogicalBoxAxis::Block;
375 case CSSPropertyTop:
376 case CSSPropertyBottom:
377 return writingMode.isHorizontal() ? LogicalBoxAxis::Block : LogicalBoxAxis::Inline;
378 case CSSPropertyInsetInlineStart:
379 case CSSPropertyInsetInlineEnd:
380 return LogicalBoxAxis::Inline;
381 case CSSPropertyInsetBlockStart:
382 case CSSPropertyInsetBlockEnd:
383 return LogicalBoxAxis::Block;
384 default:
385 ASSERT_NOT_REACHED()((void)0);
386 return LogicalBoxAxis::Inline;
387 }
388}
389
390// Physical sides (left/right/top/bottom) can only be used in certain inset properties. "For example,
391// left is usable in left, right, or the logical inset properties that refer to the horizontal axis."
392// See: https://drafts.csswg.org/css-anchor-position-1/#typedef-anchor-side
393static bool NODELETE[[clang::annotate_type("webkit.nodelete")]] anchorSideMatchesInsetProperty(CSSValueID anchorSideID, BoxAxis physicalAxis)
394{
395 switch (anchorSideID) {
396 case CSSValueID::CSSValueInside:
397 case CSSValueID::CSSValueOutside:
398 case CSSValueID::CSSValueStart:
399 case CSSValueID::CSSValueEnd:
400 case CSSValueID::CSSValueSelfStart:
401 case CSSValueID::CSSValueSelfEnd:
402 case CSSValueID::CSSValueCenter:
403 case CSSValueID::CSSValueInvalid: // percentage
404 return true;
405 case CSSValueID::CSSValueTop:
406 case CSSValueID::CSSValueBottom:
407 return BoxAxis::Vertical == physicalAxis;
408 case CSSValueID::CSSValueLeft:
409 case CSSValueID::CSSValueRight:
410 return BoxAxis::Horizontal == physicalAxis;
411 default:
412 ASSERT_NOT_REACHED()((void)0);
413 return false;
414 }
415}
416
417static LayoutSize offsetFromAncestorContainer(const RenderElement& descendantContainer, const RenderElement& ancestorContainer)
418{
419 LayoutSize offset;
420 LayoutPoint referencePoint;
421 CheckedPtr currentContainer = &descendantContainer;
422 CheckedPtr maxContainer = &ancestorContainer;
423 if (CheckedPtr ancestorInline = dynamicDowncast<RenderInline>(&ancestorContainer))
424 maxContainer = ancestorInline->containingBlock();
425 do {
426 CheckedPtr nextContainer = currentContainer->container();
427 ASSERT(nextContainer)((void)0); // This means we reached the top without finding container.
428 if (!nextContainer)
429 break;
430 LayoutSize currentOffset = currentContainer->offsetFromContainer(*nextContainer, referencePoint);
431
432 if (CheckedPtr boxContainer = dynamicDowncast<RenderBox>(*nextContainer)) {
433 // Clamp overscroll so we don't layout into it.
434 if (boxContainer->hasPotentiallyScrollableOverflow())
435 currentOffset += boxContainer->scrollPosition() - boxContainer->constrainedScrollPosition();
436 }
437
438 offset += currentOffset;
439 referencePoint.move(currentOffset);
440 currentContainer = WTF::move(nextContainer);
441 } while (currentContainer != maxContainer);
442
443 if (CheckedPtr descendantInline = dynamicDowncast<RenderInline>(&descendantContainer)) {
444 // RenderInline objects do not automatically account for their offset above,
445 // so we incorporate this offset here.
446 offset += toLayoutSize(descendantInline->linesBoundingBox().location());
447 }
448 if (descendantContainer.containingBlock() == ancestorContainer.containingBlock()) {
449 // Account for 'position: relative' inline containing blocks by shifting back down into them.
450 if (CheckedPtr ancestorInline = dynamicDowncast<RenderInline>(&ancestorContainer))
451 offset -= toLayoutSize(ancestorInline->firstInlineBoxTopLeft()); // FIXME: Handle RTL.
452 }
453
454 if (auto ancestorBox = dynamicDowncast<RenderBox>(ancestorContainer)) // Zero out containing block scroll position.
455 offset += toLayoutSize(ancestorBox->constrainedScrollPosition());
456
457 return offset;
458}
459
460void AnchorPositionEvaluator::addAnchorFunctionScrollCompensatedAxis(RenderStyle& style, const RenderBox& anchored, const RenderBoxModelObject& anchor, BoxAxis axis)
461{
462 // https://drafts.csswg.org/css-anchor-position-1/#scroll
463 // An absolutely positioned box abspos compensates for scroll in the horizontal or vertical axis if both of the following conditions are true:
464 // - abspos has a default anchor box.
465 auto defaultAnchor = defaultAnchorForBox(anchored);
466 if (!defaultAnchor)
467 return;
468
469 // - at least one anchor() function on abspos’s used inset properties in the axis refers to a target anchor element
470 // with the same nearest scroll container ancestor as abspos’s default anchor box.
471 if (defaultAnchor != &anchor && defaultAnchor->enclosingScrollableContainer() != anchor.enclosingScrollableContainer())
472 return;
473
474 auto axes = style.anchorFunctionScrollCompensatedAxes();
475 axes.add(axis);
476 style.setAnchorFunctionScrollCompensatedAxes(axes);
477}
478
479static LayoutRect boundingRectForFragmentedAnchor(const RenderBoxModelObject& anchorBox, const RenderElement& containingBlock, const RenderFragmentedFlow& fragmentedFlow)
480{
481 // Compute the bounding box of the fragments.
482 // Location is relative to the fragmented flow.
483 CheckedPtr anchorRenderBox = dynamicDowncast<RenderBox>(&anchorBox);
484 if (!anchorRenderBox)
485 anchorRenderBox = anchorBox.containingBlock();
486 LayoutPoint offsetRelativeToFragmentedFlow = fragmentedFlow.mapFromLocalToFragmentedFlow(anchorRenderBox.get(), { }).location();
487 auto unfragmentedBorderBox = anchorBox.borderBoundingBox();
488 unfragmentedBorderBox.moveBy(offsetRelativeToFragmentedFlow);
489 fragmentedFlow.flipForWritingMode(unfragmentedBorderBox); // Convert to RenderLayer coords.
490 auto fragmentsBoundingBox = fragmentedFlow.fragmentsBoundingBox(unfragmentedBorderBox);
491 fragmentedFlow.flipForWritingMode(fragmentsBoundingBox); // Convert to RenderBox coords.
492
493 // Now convert to physical coordinates (top/left origin) and walk up.
494 // RenderFragmentedFlow doesn't have a usable frame rect, so use its container's content rect.
495 CheckedPtr fragmentedFlowContainer = fragmentedFlow.containingBlock();
496 if (!fragmentedFlowContainer) {
497 ASSERT_NOT_REACHED()((void)0);
498 return fragmentsBoundingBox;
499 }
500 auto fragmentedFlowRect = fragmentedFlowContainer->contentBoxRect();
501 if (fragmentedFlow.writingMode().isBlockFlipped()) {
502 if (fragmentedFlow.writingMode().isHorizontal())
503 fragmentsBoundingBox.setY(fragmentedFlowRect.height() - fragmentsBoundingBox.maxY());
504 else
505 fragmentsBoundingBox.setX(fragmentedFlowRect.width() - fragmentsBoundingBox.maxX());
506 }
507 fragmentsBoundingBox.moveBy(fragmentedFlowRect.location());
508
509 // Change the location to be relative to the anchor's containing block.
510 if (fragmentedFlowContainer.get() != &containingBlock)
511 fragmentsBoundingBox.move(offsetFromAncestorContainer(*fragmentedFlowContainer, containingBlock));
512
513 return fragmentsBoundingBox;
514}
515
516// This computes the top left location, physical width, and physical height of the specified
517// anchor element. The location is computed relative to the specified containing block.
518LayoutRect AnchorPositionEvaluator::computeAnchorRectRelativeToContainingBlock(CheckedRef<const RenderBoxModelObject> anchorBox, const RenderElement& containingBlock, const RenderBox& anchoredBox)
519{
520 // Fragmented flows are a little tricky to deal with. One example of a fragmented
521 // flow is a block anchor element that is "fragmented" or split across multiple columns
522 // as a result of multi-column layout. In this case, we need to compute "the axis-aligned
523 // bounding rectangle of the fragments' border boxes" and make that our anchorHeight/Width.
524 // We also need to adjust the anchor's top left location to match that of the bounding box
525 // instead of the first fragment.
526 if (CheckedPtr fragmentedFlow = anchorBox->enclosingFragmentedFlow();
527 fragmentedFlow && fragmentedFlow->isDescendantOf(&containingBlock))
528 return boundingRectForFragmentedAnchor(anchorBox, containingBlock, *fragmentedFlow);
529
530 auto anchorWidth = anchorBox->offsetWidth();
531 auto anchorHeight = anchorBox->offsetHeight();
532 auto anchorLocation = LayoutPoint { offsetFromAncestorContainer(anchorBox, containingBlock) };
533
534 if (&containingBlock == &containingBlock.view() && anchoredBox.isFixedPositioned()) {
535 // Handle fixed positioning x scrolling anchor.
536 bool isFixedAnchor = false;
537 for (const RenderElement* box = anchorBox.ptr(); box && box != &containingBlock; box = box->container()) {
538 if (box->isFixedPositioned()) {
539 isFixedAnchor = true;
540 break;
541 }
542 }
543 if (!isFixedAnchor) {
544 auto& view = anchorBox->view().frameView();
545 anchorLocation.moveBy(-view.constrainedScrollPosition(ScrollPosition(view.scrollPositionRespectingCustomFixedPosition())));
546 }
547 }
548
549 if (CheckedPtr containingBox = dynamicDowncast<RenderBox>(containingBlock)) {
550 if (containingBox->shouldPlaceVerticalScrollbarOnLeft())
551 anchorLocation.move(-containingBox->verticalScrollbarWidth(), 0);
552 }
553
554 return LayoutRect(anchorLocation, LayoutSize(anchorWidth, anchorHeight));
555}
556
557static bool inline NODELETE[[clang::annotate_type("webkit.nodelete")]] isInsetPropertyContainerStartSide(CSSPropertyID insetPropertyID, PositionedLayoutConstraints& constraints)
558{
559 switch (insetPropertyID) {
560 case CSSPropertyLeft:
561 return constraints.containingWritingMode().isAnyLeftToRight();
562 case CSSPropertyRight:
563 return !constraints.containingWritingMode().isAnyLeftToRight();
564 case CSSPropertyTop:
565 return constraints.containingWritingMode().isAnyTopToBottom();
566 case CSSPropertyBottom:
567 return !constraints.containingWritingMode().isAnyTopToBottom();
568 case CSSPropertyInsetInlineStart:
569 return !constraints.selfWritingMode().isInlineOpposing(constraints.containingWritingMode());
570 case CSSPropertyInsetInlineEnd:
571 return constraints.selfWritingMode().isInlineOpposing(constraints.containingWritingMode());
572 case CSSPropertyInsetBlockStart:
573 return !constraints.selfWritingMode().isBlockOpposing(constraints.containingWritingMode());
574 case CSSPropertyInsetBlockEnd:
575 return constraints.selfWritingMode().isBlockOpposing(constraints.containingWritingMode());
576 default:
577 ASSERT_NOT_REACHED()((void)0);
578 return true;
579 }
580}
581
582static CSSPropertyID NODELETE[[clang::annotate_type("webkit.nodelete")]] getOppositeInset(CSSPropertyID propertyID)
583{
584 switch (propertyID) {
585 case CSSPropertyLeft:
586 return CSSPropertyRight;
587 case CSSPropertyRight:
588 return CSSPropertyLeft;
589 case CSSPropertyTop:
590 return CSSPropertyBottom;
591 case CSSPropertyBottom:
592 return CSSPropertyTop;
593
594 case CSSPropertyInsetInlineStart:
595 return CSSPropertyInsetInlineEnd;
596 case CSSPropertyInsetInlineEnd:
597 return CSSPropertyInsetInlineStart;
598 case CSSPropertyInsetBlockStart:
599 return CSSPropertyInsetBlockEnd;
600 case CSSPropertyInsetBlockEnd:
601 return CSSPropertyInsetBlockStart;
602
603 default:
604 ASSERT_NOT_REACHED()((void)0);
605 return CSSPropertyInsetBlockStart;
606 }
607}
608
609static std::pair<CSSPropertyID, bool> applyTryTacticsToInset(CSSPropertyID propertyID, WritingMode writingMode, const BuilderPositionTryFallback& fallback)
610{
611 bool isFlipped = false;
612 for (auto tactic : fallback.tactics) {
613 switch (tactic) {
614 case PositionTryFallback::Tactic::FlipInline:
615 if (LogicalBoxAxis::Inline == mapInsetPropertyToLogicalAxis(propertyID, writingMode)) {
616 propertyID = getOppositeInset(propertyID);
617 isFlipped = true;
618 }
619 break;
620 case PositionTryFallback::Tactic::FlipBlock:
621 if (LogicalBoxAxis::Block == mapInsetPropertyToLogicalAxis(propertyID, writingMode)) {
622 propertyID = getOppositeInset(propertyID);
623 isFlipped = true;
624 }
625 break;
626 case PositionTryFallback::Tactic::FlipX:
627 if (BoxAxis::Horizontal == mapInsetPropertyToPhysicalAxis(propertyID, writingMode)) {
628 propertyID = getOppositeInset(propertyID);
629 isFlipped = true;
630 }
631 break;
632 case PositionTryFallback::Tactic::FlipY:
633 if (BoxAxis::Vertical == mapInsetPropertyToPhysicalAxis(propertyID, writingMode)) {
634 propertyID = getOppositeInset(propertyID);
635 isFlipped = true;
636 }
637 break;
638 case PositionTryFallback::Tactic::FlipStart:
639 propertyID = [&] {
640 switch (propertyID) {
641 case CSSPropertyInsetInlineStart:
642 return CSSPropertyInsetBlockStart;
643 case CSSPropertyInsetInlineEnd:
644 return CSSPropertyInsetBlockEnd;
645 case CSSPropertyInsetBlockStart:
646 return CSSPropertyInsetInlineStart;
647 case CSSPropertyInsetBlockEnd:
648 return CSSPropertyInsetInlineEnd;
649
650 case CSSPropertyLeft:
651 return writingMode.isAnyLeftToRight() == writingMode.isAnyTopToBottom()
652 ? CSSPropertyTop : CSSPropertyBottom;
653 case CSSPropertyRight:
654 return writingMode.isAnyLeftToRight() == writingMode.isAnyTopToBottom()
655 ? CSSPropertyBottom : CSSPropertyTop;
656 case CSSPropertyTop:
657 return writingMode.isAnyLeftToRight() == writingMode.isAnyTopToBottom()
658 ? CSSPropertyLeft : CSSPropertyRight;
659 case CSSPropertyBottom:
660 return writingMode.isAnyLeftToRight() == writingMode.isAnyTopToBottom()
661 ? CSSPropertyRight : CSSPropertyLeft;
662
663 default:
664 ASSERT_NOT_REACHED()((void)0);
665 return CSSPropertyInsetBlockStart;
666 }
667 }();
668 break;
669 }
670 }
671 return { propertyID, isFlipped };
672}
673
674// "An anchor() function representing a valid anchor function resolves...to the <length> that would
675// align the edge of the positioned elements' inset-modified containing block corresponding to the
676// property the function appears in with the specified border edge of the target anchor element..."
677// See: https://drafts.csswg.org/css-anchor-position-1/#anchor-pos
678static LayoutUnit computeInsetValue(CSSPropertyID insetPropertyID, CheckedRef<const RenderBoxModelObject> anchorBox, CheckedRef<const RenderBox> anchorPositionedRenderer, AnchorPositionEvaluator::Side anchorSide, const std::optional<BuilderPositionTryFallback>& positionTryFallback)
679{
680 auto writingMode = anchorPositionedRenderer->writingMode();
681 bool isFlipped = false;
682 if (positionTryFallback)
683 std::tie(insetPropertyID, isFlipped) = applyTryTacticsToInset(insetPropertyID, writingMode, *positionTryFallback);
684
685 auto selfLogicalAxis = mapInsetPropertyToLogicalAxis(insetPropertyID, writingMode);
686 PositionedLayoutConstraints constraints(anchorPositionedRenderer, selfLogicalAxis);
687
688 auto anchorPercentage = [&]() -> double {
689 if (std::holds_alternative<CSSValueID>(anchorSide)) {
690 auto anchorSideID = std::get<CSSValueID>(anchorSide);
691 switch (anchorSideID) {
692 case CSSValueCenter:
693 return 0.5;
694 case CSSValueStart:
695 return 0;
696 case CSSValueEnd:
697 return 1;
698 case CSSValueSelfStart:
699 return constraints.isOpposing() ? 1 : 0;
700 case CSSValueSelfEnd:
701 return constraints.isOpposing() ? 0 : 1;
702
703 case CSSValueTop:
704 return constraints.containingWritingMode().isAnyTopToBottom() ? 0 : 1;
705 case CSSValueBottom:
706 return constraints.containingWritingMode().isAnyTopToBottom() ? 1 : 0;
707 case CSSValueLeft:
708 return constraints.containingWritingMode().isAnyLeftToRight() ? 0 : 1;
709 case CSSValueRight:
710 return constraints.containingWritingMode().isAnyLeftToRight() ? 1 : 0;
711
712 case CSSValueInside:
713 return isInsetPropertyContainerStartSide(insetPropertyID, constraints) != isFlipped ? 0 : 1;
714 case CSSValueOutside:
715 return isInsetPropertyContainerStartSide(insetPropertyID, constraints) != isFlipped ? 1 : 0;
716
717 default:
718 ASSERT_NOT_REACHED()((void)0);
719 return 0;
720 }
721 } else
722 return std::get<double>(anchorSide);
723 }();
724 if (constraints.startIsBefore() == isFlipped)
725 anchorPercentage = 1 - anchorPercentage;
726
727 CheckedPtr containingBlock = anchorPositionedRenderer->container();
728 ASSERT(containingBlock)((void)0);
729 auto anchorRect = AnchorPositionEvaluator::computeAnchorRectRelativeToContainingBlock(anchorBox, *containingBlock, anchorPositionedRenderer.get());
730 auto anchorRange = constraints.extractRange(anchorRect);
731
732 auto anchorPosition = anchorRange.min() + LayoutUnit(anchorRange.size() * anchorPercentage);
733 auto insetValue = isInsetPropertyContainerStartSide(insetPropertyID, constraints) == constraints.startIsBefore()
734 ? anchorPosition - constraints.containingRange().min()
735 : constraints.containingRange().max() - anchorPosition;
736 return insetValue;
737}
738
739CheckedPtr<RenderBoxModelObject> AnchorPositionEvaluator::findAnchorForAnchorFunctionAndAttemptResolution(BuilderState& builderState, std::optional<ScopedName> anchorNameArgument)
740{
741 auto& style = builderState.renderStyle();
742 style.setUsesAnchorFunctions();
743
744 if (!builderState.anchorPositionedStates())
745 return { };
746
747 auto isValid = [&] {
748 if (!builderState.element())
749 return false;
750
751 // FIXME: Support remaining box generating pseudo-elements (like ::marker).
752 auto pseudoElement = style.pseudoElementType();
753 if (pseudoElement && pseudoElement != PseudoElementType::Before && pseudoElement != PseudoElementType::After)
754 return false;
755
756 return true;
757 };
758
759 if (!isValid())
760 return { };
761
762 Ref elementOrHost = *builderState.element();
763
764 // PseudoElement nodes are created on-demand by render tree builder so dont' work as keys here.
765 auto& anchorPositionedStates = *builderState.anchorPositionedStates();
766 auto& anchorPositionedState = *anchorPositionedStates.ensure({ elementOrHost.ptr(), style.pseudoElementIdentifier() }, [&] {
767 return WTF::makeUnique<AnchorPositionedState>();
768 }).iterator->value.get();
769
770 auto scopedAnchorName = [&] {
771 if (anchorNameArgument)
772 return *anchorNameArgument;
773 return defaultAnchorName(style);
774 };
775
776 auto resolvedAnchorName = ResolvedScopedName::createFromScopedName(elementOrHost, scopedAnchorName());
777
778 // Collect anchor names that this element refers to in anchor() or anchor-size()
779 bool isNewAnchorName = anchorPositionedState.anchorNames.add(resolvedAnchorName).isNewEntry;
780
781 // If anchor resolution has progressed past FindAnchors, and we pick up a new anchor name, set the
782 // stage back to FindAnchors. This restarts the resolution process to resolve newly added names.
783 if (isNewAnchorName)
784 anchorPositionedState.stage = AnchorPositionResolutionStage::FindAnchors;
785
786 // An anchor() instance will be ready to be resolved when all referenced anchor-names
787 // have been mapped to an actual anchor element in the DOM tree. At that point, we
788 // should also have layout information for the anchor-positioned element alongside
789 // the anchors referenced by the anchor-positioned element. Until then, we cannot
790 // resolve this anchor() instance.
791 if (anchorPositionedState.stage <= AnchorPositionResolutionStage::FindAnchors)
792 return { };
793
794 auto anchorPositionedElement = anchorPositionedElementOrPseudoElement(builderState);
795
796 CheckedPtr anchorPositionedRenderer = anchorPositionedElement ? anchorPositionedElement->renderer() : nullptr;
797 if (!anchorPositionedRenderer) {
798 // If no render tree information is present, the procedure is finished.
799 anchorPositionedState.stage = AnchorPositionResolutionStage::Resolved;
800 return { };
801 }
802
803 // Anchor value may now be resolved using layout information
804
805 RefPtr anchorElement = anchorPositionedState.anchorElements.get(resolvedAnchorName);
806 if (!anchorElement) {
807 // See: https://drafts.csswg.org/css-anchor-position-1/#valid-anchor-function
808 anchorPositionedState.stage = AnchorPositionResolutionStage::Resolved;
809
810 return { };
811 }
812
813 if (auto* state = anchorPositionedStates.get(keyForElementOrPseudoElement(*anchorElement))) {
814 // Check if the anchor is itself anchor-positioned but hasn't been positioned yet.
815 if (state->stage < AnchorPositionResolutionStage::Positioned)
816 return { };
817 }
818
819 anchorPositionedState.stage = AnchorPositionResolutionStage::Resolved;
820
821 return dynamicDowncast<RenderBoxModelObject>(anchorElement->renderer());
822}
823
824bool AnchorPositionEvaluator::propertyAllowsAnchorFunction(CSSPropertyID propertyID)
825{
826 return CSSProperty::isInsetProperty(propertyID);
827}
828
829std::optional<double> AnchorPositionEvaluator::evaluate(BuilderState& builderState, std::optional<ScopedName> elementName, Side side)
830{
831 auto& style = builderState.renderStyle();
832
833 auto propertyID = builderState.cssPropertyID();
834 auto physicalAxis = mapInsetPropertyToPhysicalAxis(propertyID, style.writingMode());
835
836 // https://drafts.csswg.org/css-anchor-position-1/#anchor-valid
837 auto isValidAnchor = [&] {
838 // It’s being used in an inset property...
839 if (!propertyAllowsAnchorFunction(propertyID))
840 return false;
841
842 // ...on an absolutely-positioned element.
843 if (!style.hasOutOfFlowPosition())
844 return false;
845
846 // If its <anchor-side> specifies a physical keyword, it’s being used in an inset property in that axis.
847 // (For example, left can only be used in left, right, or a logical inset property in the horizontal axis.)
848 if (auto* sideID = std::get_if<CSSValueID>(&side); sideID && !anchorSideMatchesInsetProperty(*sideID, physicalAxis))
849 return false;
850
851 return true;
852 };
853
854 if (!isValidAnchor())
855 return { };
856
857 auto anchorRenderer = findAnchorForAnchorFunctionAndAttemptResolution(builderState, elementName);
858 if (!anchorRenderer)
859 return { };
860
861 RefPtr anchorPositionedElement = anchorPositionedElementOrPseudoElement(builderState);
862 if (!anchorPositionedElement)
863 return { };
864
865 CheckedPtr anchorPositionedRenderer = dynamicDowncast<RenderBox>(anchorPositionedElement->renderer());
866 if (!anchorPositionedRenderer)
867 return { };
868
869 addAnchorFunctionScrollCompensatedAxis(style, *anchorPositionedRenderer, *anchorRenderer, physicalAxis);
870
871 // Proceed with computing the inset value for the specified inset property.
872 double insetValue = computeInsetValue(propertyID, *anchorRenderer, *anchorPositionedRenderer, side, builderState.positionTryFallback());
873
874 // Adjust for CSS `zoom` property and page zoom.
875 return insetValue / style.usedZoom();
876}
877
878// Returns the default anchor size dimension to use when it is not specified in
879// anchor-size(). This matches the axis of the property that anchor-size() is used in.
880static AnchorSizeDimension NODELETE[[clang::annotate_type("webkit.nodelete")]] defaultDimensionForPropertyID(CSSPropertyID propertyID)
881{
882 switch (propertyID) {
883 case CSSPropertyWidth:
884 case CSSPropertyMinWidth:
885 case CSSPropertyMaxWidth:
886 case CSSPropertyLeft:
887 case CSSPropertyRight:
888 case CSSPropertyMarginLeft:
889 case CSSPropertyMarginRight:
890 return AnchorSizeDimension::Width;
891
892 case CSSPropertyHeight:
893 case CSSPropertyMinHeight:
894 case CSSPropertyMaxHeight:
895 case CSSPropertyTop:
896 case CSSPropertyBottom:
897 case CSSPropertyMarginTop:
898 case CSSPropertyMarginBottom:
899 return AnchorSizeDimension::Height;
900
901 case CSSPropertyBlockSize:
902 case CSSPropertyMinBlockSize:
903 case CSSPropertyMaxBlockSize:
904 case CSSPropertyInsetBlockStart:
905 case CSSPropertyInsetBlockEnd:
906 case CSSPropertyMarginBlockStart:
907 case CSSPropertyMarginBlockEnd:
908 return AnchorSizeDimension::Block;
909
910 case CSSPropertyInlineSize:
911 case CSSPropertyMinInlineSize:
912 case CSSPropertyMaxInlineSize:
913 case CSSPropertyInsetInlineStart:
914 case CSSPropertyInsetInlineEnd:
915 case CSSPropertyMarginInlineStart:
916 case CSSPropertyMarginInlineEnd:
917 return AnchorSizeDimension::Inline;
918
919 default:
920 ASSERT_NOT_REACHED("anchor-size() being used in disallowed CSS property, which should not happen")((void)0);
921 return AnchorSizeDimension::Width;
922 }
923}
924
925// Convert anchor size dimension to the physical dimension (width or height).
926static BoxAxis NODELETE[[clang::annotate_type("webkit.nodelete")]] anchorSizeDimensionToPhysicalDimension(AnchorSizeDimension dimension, const RenderStyle& style, const RenderStyle& containerStyle)
927{
928 switch (dimension) {
929 case AnchorSizeDimension::Width:
930 return BoxAxis::Horizontal;
931 case AnchorSizeDimension::Height:
932 return BoxAxis::Vertical;
933 case AnchorSizeDimension::Block:
934 return mapAxisLogicalToPhysical(containerStyle.writingMode(), LogicalBoxAxis::Block);
935 case AnchorSizeDimension::Inline:
936 return mapAxisLogicalToPhysical(containerStyle.writingMode(), LogicalBoxAxis::Inline);
937 case AnchorSizeDimension::SelfBlock:
938 return mapAxisLogicalToPhysical(style.writingMode(), LogicalBoxAxis::Block);
939 case AnchorSizeDimension::SelfInline:
940 return mapAxisLogicalToPhysical(style.writingMode(), LogicalBoxAxis::Inline);
941 }
942
943 ASSERT_NOT_REACHED()((void)0);
944 return BoxAxis::Horizontal;
945}
946
947bool AnchorPositionEvaluator::propertyAllowsAnchorSizeFunction(CSSPropertyID propertyID)
948{
949 return CSSProperty::isSizingProperty(propertyID) || CSSProperty::isInsetProperty(propertyID) || CSSProperty::isMarginProperty(propertyID);
950}
951
952std::optional<double> AnchorPositionEvaluator::evaluateSize(BuilderState& builderState, std::optional<ScopedName> elementName, std::optional<AnchorSizeDimension> dimension)
953{
954 auto propertyID = builderState.cssPropertyID();
955 const auto& style = builderState.renderStyle();
956
957 auto isValidAnchorSize = [&] {
958 // It’s being used in a sizing property, an inset property, or a margin property...
959 if (!propertyAllowsAnchorSizeFunction(propertyID))
960 return false;
961
962 // ...on an absolutely-positioned element.
963 if (!style.hasOutOfFlowPosition())
964 return false;
965
966 return true;
967 };
968
969 if (!isValidAnchorSize())
970 return { };
971
972 auto anchorRenderer = findAnchorForAnchorFunctionAndAttemptResolution(builderState, elementName);
973 if (!anchorRenderer)
974 return { };
975
976 // Resolve the dimension (width or height) to return from the anchor positioned element.
977 RefPtr anchorPositionedElement = anchorPositionedElementOrPseudoElement(builderState);
978 if (!anchorPositionedElement)
979 return { };
980
981 CheckedPtr anchorPositionedRenderer = anchorPositionedElement->renderer();
982 ASSERT(anchorPositionedRenderer)((void)0);
983
984 CheckedPtr anchorPositionedContainerRenderer = anchorPositionedRenderer->container();
985 ASSERT(anchorPositionedContainerRenderer)((void)0);
986
987 auto resolvedDimension = dimension.value_or(defaultDimensionForPropertyID(propertyID));
988 auto physicalDimension = anchorSizeDimensionToPhysicalDimension(resolvedDimension, anchorPositionedRenderer->style(), anchorPositionedContainerRenderer->style());
989
990 if (builderState.positionTryFallback()) {
991 // "For sizing properties, change the specified axis in anchor-size() functions to maintain the same relative relationship to the new direction that they had to the old."
992 if (CSSProperty::isSizingProperty(propertyID)) {
993 auto swapDimensions = builderState.positionTryFallback()->tactics.contains(PositionTryFallback::Tactic::FlipStart);
994 if (swapDimensions)
995 physicalDimension = oppositeAxis(physicalDimension);
996 }
997 }
998
999 auto anchorBorderBoundingBox = anchorRenderer->borderBoundingBox();
1000 if (CheckedPtr fragmentedFlow = anchorRenderer->enclosingFragmentedFlow()) {
1001 CheckedPtr containingBlock = anchorPositionedRenderer->containingBlock();
1002 if (fragmentedFlow && containingBlock
1003 && fragmentedFlow->isDescendantOf(containingBlock.get()))
1004 anchorBorderBoundingBox = boundingRectForFragmentedAnchor(*anchorRenderer, *containingBlock, *fragmentedFlow);
1005 }
1006
1007 // Adjust for CSS `zoom` property and page zoom.
1008
1009 switch (physicalDimension) {
1010 case BoxAxis::Horizontal:
1011 return anchorBorderBoundingBox.width() / style.usedZoom();
1012 case BoxAxis::Vertical:
1013 return anchorBorderBoundingBox.height() / style.usedZoom();
1014 }
1015
1016 ASSERT_NOT_REACHED()((void)0);
1017 return { };
1018}
1019
1020static const RenderElement* penultimateContainingBlockChainElement(const RenderElement& descendant, const RenderElement* ancestor)
1021{
1022 auto* currentElement = &descendant;
1023 for (auto* nextElement = currentElement->container(); nextElement; nextElement = nextElement->container()) {
1024 if (nextElement == ancestor)
1025 return currentElement;
1026 currentElement = nextElement;
1027 }
1028 return nullptr;
1029}
1030
1031static bool firstChildPrecedesSecondChild(const RenderObject* firstChild, const RenderObject* secondChild, const RenderObject* containingBlock)
1032{
1033 HashSet<CheckedRef<const RenderObject>> firstAncestorChain;
1034
1035 for (auto* first = firstChild; first; first = first->parent()) {
1036 firstAncestorChain.add(*first);
1037 if (first == containingBlock)
1038 break;
1039 }
1040
1041 auto* second = secondChild;
1042 for (; second != containingBlock; second = second->parent()) {
1043 if (firstAncestorChain.contains(second->parent())) {
1044 for (auto* sibling = second->previousSibling(); sibling; sibling = sibling->previousSibling()) {
1045 if (firstAncestorChain.contains(sibling))
1046 return true;
1047 }
1048 return false;
1049 }
1050 }
1051 return false;
1052}
1053
1054// Given an element and its anchor name, locate the closest ancestor (*) element
1055// that establishes an anchor scope affecting this anchor name, and return the pointer
1056// to such element. If no ancestor establishes an anchor scope affecting this name,
1057// returns nullptr.
1058// (*): an anchor element can also establish an anchor scope containing itself. In this
1059// case, the return value is itself.
1060static CheckedPtr<const Element> anchorScopeForAnchorName(const RenderBoxModelObject& renderer, const ResolvedScopedName anchorName)
1061{
1062 // Traverse up the composed tree through itself and each ancestor.
1063 CheckedPtr<const Element> anchorElement = renderer.element();
1064 ASSERT(anchorElement)((void)0);
1065 for (CheckedPtr<const Element> currentAncestor = anchorElement; currentAncestor; currentAncestor = currentAncestor->parentElementInComposedTree()) {
1066 CheckedPtr currentAncestorStyle = currentAncestor->renderStyle();
1067 if (!currentAncestorStyle)
1068 continue;
1069 const auto& currentAncestorAnchorScope = currentAncestorStyle->anchorScope();
1070
1071 if (Style::NameScope::Type::None == currentAncestorAnchorScope.type)
1072 continue;
1073
1074 auto styleScope = Scope::forOrdinal(*currentAncestor, currentAncestorAnchorScope.scopeOrdinal);
1075 ASSERT(styleScope)((void)0);
1076 if (anchorName.scopeIdentifier() != styleScope->identifier())
1077 continue;
1078
1079 if (Style::NameScope::Type::All == currentAncestorAnchorScope.type
1080 || currentAncestorAnchorScope.names.contains(CustomIdentifier { anchorName.name() }))
1081 return currentAncestor;
1082 }
1083
1084 return nullptr;
1085}
1086
1087enum class TopLayerStatus : uint8_t { Same, Lower, Higher };
1088static TopLayerStatus computeTopLayerStatus(const RenderBox& anchored, const RenderBoxModelObject& anchor)
1089{
1090 // Two elements are in the same top layer if they have the same top layer root (including if both are none).
1091 // An element A is in a higher top layer than an element B if A has a top layer root, and either B has a top
1092 // layer root earlier in the top layer than A’s, or B doesn’t have a top layer root at all.
1093 // https://drafts.csswg.org/css-position-4/#top-layer
1094
1095 if (!anchored.document().hasTopLayerElement())
1096 return TopLayerStatus::Same;
1097
1098 auto topLayerRoot = [&](auto& renderer) -> const RenderLayerModelObject* {
1099 for (auto* layer = renderer.enclosingLayer(); layer; layer = layer->parent()) {
1100 if (layer->establishesTopLayer())
1101 return &layer->renderer();
1102 }
1103 return nullptr;
1104 };
1105
1106 auto* anchoredRoot = topLayerRoot(anchored);
1107 auto* anchorRoot = topLayerRoot(anchor);
1108 if (anchoredRoot == anchorRoot)
1109 return TopLayerStatus::Same;
1110 if (!anchoredRoot)
1111 return TopLayerStatus::Lower;
1112 if (!anchorRoot)
1113 return TopLayerStatus::Higher;
1114
1115 auto& topLayerElements = anchored.document().topLayerElements();
1116 for (auto& topLayerElement : topLayerElements) {
1117 if (topLayerElement.ptr() == anchoredRoot->element())
1118 return TopLayerStatus::Lower;
1119 if (topLayerElement.ptr() == anchorRoot->element())
1120 return TopLayerStatus::Higher;
1121 }
1122 return TopLayerStatus::Lower;
1123}
1124
1125// See: https://drafts.csswg.org/css-anchor-position-1/#acceptable-anchor-element
1126static bool isAcceptableAnchorElement(const RenderBoxModelObject& anchorRenderer, Ref<const Element> anchorPositionedElement, const std::optional<ResolvedScopedName> anchorName = { })
1127{
1128 // "Possible anchor is either an element or a fully styleable tree-abiding pseudo-element."
1129 // This always have an associated Element (for ::before/::after it is PseudoElement).
1130 if (!anchorRenderer.element())
1131 return false;
1132
1133 CheckedPtr anchorPositionedRenderer = dynamicDowncast<RenderBox>(anchorPositionedElement->renderer());
1134 ASSERT(anchorPositionedRenderer)((void)0);
1135
1136 if (anchorName) {
1137 // Check that anchorRenderer has the specified name.
1138 ASSERT(anchorRenderer.style().anchorNames().containsIf(((void)0)
1139 [anchorName](auto& scopedName) { return scopedName.name == anchorName->name(); }((void)0)
1140 ))((void)0);
1141
1142 // The anchor and anchor-positioned element must be in the same scope.
1143 auto anchorScopeElement = anchorScopeForAnchorName(anchorRenderer, *anchorName);
1144 auto anchorPositionedScopeElement = anchorScopeForAnchorName(*anchorPositionedRenderer, *anchorName);
1145 if (anchorScopeElement != anchorPositionedScopeElement)
1146 return false;
1147 }
1148
1149 CheckedPtr containingBlock = anchorPositionedRenderer->container();
1150 ASSERT(containingBlock)((void)0);
1151
1152 // "possible anchor is laid out strictly before positioned el, aka one of the following is true:"
1153 auto topLayerStatus = computeTopLayerStatus(*anchorPositionedRenderer, anchorRenderer);
1154 switch (topLayerStatus) {
1155 case TopLayerStatus::Higher:
1156 // "- positioned el is in a higher top layer than possible anchor"
1157 return true;
1158 case TopLayerStatus::Same: {
1159 // "- Both elements are in the same top layer..."
1160 auto* penultimateElement = penultimateContainingBlockChainElement(anchorRenderer, containingBlock.get());
1161 if (!penultimateElement)
1162 return false;
1163
1164 if (!penultimateElement->isOutOfFlowPositioned())
1165 return true;
1166
1167 return firstChildPrecedesSecondChild(penultimateElement, anchorPositionedRenderer.get(), containingBlock.get());
1168 }
1169 case TopLayerStatus::Lower:
1170 return false;
1171 }
1172 ASSERT_NOT_REACHED()((void)0);
1173 return false;
1174}
1175
1176static RefPtr<Element> findImplicitAnchor(const Element& anchorPositionedElement)
1177{
1178 auto find = [&]() -> RefPtr<Element> {
1179 // "The implicit anchor element of a pseudo-element is its originating element, unless otherwise specified."
1180 // https://drafts.csswg.org/css-anchor-position-1/#implicit
1181 if (auto pseudoElement = dynamicDowncast<PseudoElement>(anchorPositionedElement))
1182 return pseudoElement->hostElement();
1183
1184 // https://html.spec.whatwg.org/multipage/popover.html#the-popover-attribute
1185 // 24. Set element's implicit anchor element to invoker.
1186 if (auto popoverData = anchorPositionedElement.popoverData())
1187 return popoverData->invoker();
1188
1189 return nullptr;
1190 };
1191
1192 if (auto implicitAnchorElement = find()) {
1193 // "If [a spec] defines is an implicit anchor element for query el which is an acceptable anchor element for query el, return that element."
1194 // https://drafts.csswg.org/css-anchor-position-1/#target
1195 CheckedPtr anchor = dynamicDowncast<RenderBoxModelObject>(implicitAnchorElement->renderer());
1196 if (anchor && isAcceptableAnchorElement(*anchor, anchorPositionedElement))
1197 return implicitAnchorElement;
1198 }
1199
1200 return nullptr;
1201}
1202
1203static RefPtr<Element> findLastAcceptableAnchorWithName(ResolvedScopedName anchorName, const Element& anchorPositionedElement, const AnchorsForAnchorName& anchorsForAnchorName)
1204{
1205 if (anchorName.name() == implicitAnchorElementName().name)
1206 return findImplicitAnchor(anchorPositionedElement);
1207
1208 const auto& anchors = anchorsForAnchorName.get(anchorName);
1209
1210 for (auto& anchor : anchors | std::views::reverse) {
1211 if (isAcceptableAnchorElement(anchor.get(), anchorPositionedElement, anchorName))
1212 return anchor->element();
1213 }
1214
1215 return { };
1216}
1217
1218static AnchorsForAnchorName collectAnchorsForAnchorName(const Document& document)
1219{
1220 if (!document.renderView())
1221 return { };
1222
1223 AnchorsForAnchorName anchorsForAnchorName;
1224
1225 auto& anchors = document.renderView()->anchors();
1226 for (auto& anchorRenderer : anchors) {
1227 CheckedPtr anchorElement = anchorRenderer.element();
1228 ASSERT(anchorElement)((void)0);
1229
1230 for (auto& scopedName : anchorRenderer.style().anchorNames()) {
1231 auto resolvedScopedName = ResolvedScopedName::createFromScopedName(*anchorElement, scopedName);
1232
1233 anchorsForAnchorName.ensure(resolvedScopedName, [&] {
1234 return AnchorsForAnchorName::MappedType { };
1235 }).iterator->value.append(anchorRenderer);
1236 }
1237 }
1238
1239 // Sort them in tree order.
1240 for (auto& anchors : anchorsForAnchorName.values()) {
1241 std::ranges::sort(anchors, [](auto& a, auto& b) {
1242 // FIXME: Figure out anonymous pseudo-elements.
1243 if (!a->element() || !b->element())
1244 return !!b->element();
1245 return is_lt(treeOrder<ComposedTree>(*a->element(), *b->element()));
1246 });
1247 }
1248
1249 return anchorsForAnchorName;
1250}
1251
1252AnchorElements AnchorPositionEvaluator::findAnchorsForAnchorPositionedElement(const Element& anchorPositionedElement, const HashSet<ResolvedScopedName>& anchorNames, const AnchorsForAnchorName& anchorsForAnchorName)
1253{
1254 AnchorElements anchorElements;
1255
1256 for (auto& anchorName : anchorNames) {
1257 auto anchor = findLastAcceptableAnchorWithName(anchorName, anchorPositionedElement, anchorsForAnchorName);
1258 anchorElements.add(anchorName, anchor);
1259 }
1260
1261 return anchorElements;
1262}
1263
1264void AnchorPositionEvaluator::updateAnchorPositioningStatesAfterInterleavedLayout(Document& document, AnchorPositionedStates& anchorPositionedStates)
1265{
1266 if (anchorPositionedStates.isEmpty())
1267 return;
1268
1269 // FIXME: Make the code below oeprate on renderers (boxes) rather than elements.
1270 auto anchorsForAnchorName = collectAnchorsForAnchorName(document);
1271
1272 for (auto& elementAndState : anchorPositionedStates) {
1273 auto& state = *elementAndState.value;
1274
1275 switch (state.stage) {
1276 case AnchorPositionResolutionStage::FindAnchors: {
1277 RefPtr element = elementAndState.key.first;
1278 if (elementAndState.key.second)
1279 element = element->pseudoElementIfExists(*elementAndState.key.second);
1280
1281 CheckedPtr renderer = element ? element->renderer() : nullptr;
1282 if (renderer) {
1283 // FIXME: Remove the redundant anchorElements member. The mappings are available in anchorPositionedToAnchorMap.
1284 state.anchorElements = findAnchorsForAnchorPositionedElement(*element, state.anchorNames, anchorsForAnchorName);
1285 if (isLayoutTimeAnchorPositioned(renderer->style()))
1286 renderer->setNeedsLayout();
1287
1288 Vector<ResolvedAnchor> anchors;
1289 for (auto& anchorNameAndElement : state.anchorElements) {
1290 CheckedPtr anchorElement = anchorNameAndElement.value.get();
1291 anchors.append(ResolvedAnchor {
1292 .renderer = anchorElement ? dynamicDowncast<RenderBoxModelObject>(anchorElement->renderer()) : nullptr,
1293 .name = anchorNameAndElement.key
1294 });
1295 }
1296 document.styleScope().anchorPositionedToAnchorMap().set(*element, AnchorPositionedToAnchorEntry {
1297 .pseudoElementIdentifier = elementAndState.key.second,
1298 .anchors = WTF::move(anchors)
1299 });
1300 }
1301 state.stage = renderer && renderer->style().usesAnchorFunctions() ? AnchorPositionResolutionStage::ResolveAnchorFunctions : AnchorPositionResolutionStage::Resolved;
1302 break;
1303 }
1304
1305 case AnchorPositionResolutionStage::ResolveAnchorFunctions:
1306 case AnchorPositionResolutionStage::Resolved:
1307 if (CheckedPtr anchored = elementAndState.key.first->renderer()) {
1308 if (auto anchoredBox = dynamicDowncast<RenderBox>(anchored.get()))
1309 AnchorPositionEvaluator::captureScrollSnapshots(*anchoredBox, false);
1310 }
1311 state.stage = AnchorPositionResolutionStage::Positioned;
1312 break;
1313
1314 case AnchorPositionResolutionStage::Positioned:
1315 break;
1316 }
1317 }
1318}
1319
1320void AnchorPositionEvaluator::updateAnchorPositionedStateForDefaultAnchorAndPositionVisibility(Element& element, const RenderStyle& style, AnchorPositionedStates& states)
1321{
1322 auto shouldResolveDefaultAnchor = isAnchorPositioned(style);
1323
1324 // `position-visibility: no-overflow` should also work for non-anchor positioned out-of-flow boxes.
1325 // Create an empty anchor positioning state for it so we perform the required layout interleaving.
1326 auto hasPositionVisibilityNoOverflow = style.display().doesGenerateBox()
1327 && style.hasOutOfFlowPosition()
1328 && style.positionVisibility().contains(PositionVisibilityValue::NoOverflow);
1329
1330 if (!shouldResolveDefaultAnchor && !hasPositionVisibilityNoOverflow)
1331 return;
1332
1333 auto* state = states.ensure({ &element, style.pseudoElementIdentifier() }, [&] {
1334 return makeUnique<AnchorPositionedState>();
1335 }).iterator->value.get();
1336
1337 if (shouldResolveDefaultAnchor) {
1338 // Always resolve the default anchor. Even if nothing is anchored to it we need it to compute the scroll compensation.
1339 auto resolvedDefaultAnchor = ResolvedScopedName::createFromScopedName(element, defaultAnchorName(style));
1340 if (state->anchorNames.add(resolvedDefaultAnchor).isNewEntry) {
1341 // If anchor resolution has progressed past FindAnchors, and we pick up a new anchor name, set the
1342 // stage back to FindAnchors. This restarts the resolution process to resolve newly added names.
1343 state->stage = AnchorPositionResolutionStage::FindAnchors;
1344 }
1345 }
1346}
1347
1348auto AnchorPositionEvaluator::makeAnchorPositionedForAnchorMap(AnchorPositionedToAnchorMap& toAnchorMap) -> AnchorToAnchorPositionedMap
1349{
1350 AnchorToAnchorPositionedMap map;
1351
1352 for (auto elementAndAnchors : toAnchorMap) {
1353 CheckedRef anchorPositionedElement = elementAndAnchors.key;
1354 for (auto& anchor : elementAndAnchors.value.anchors) {
1355 if (!anchor.renderer)
1356 continue;
1357 map.ensure(*anchor.renderer, [&] {
1358 return Vector<Ref<Element>> { };
1359 }).iterator->value.append(anchorPositionedElement);
1360 }
1361 }
1362 return map;
1363}
1364
1365bool AnchorPositionEvaluator::isAnchorPositioned(const RenderStyle& style)
1366{
1367 return isStyleTimeAnchorPositioned(style) || isLayoutTimeAnchorPositioned(style);
1368}
1369
1370bool AnchorPositionEvaluator::isStyleTimeAnchorPositioned(const RenderStyle& style)
1371{
1372 if (!style.display().doesGenerateBox() || !style.hasOutOfFlowPosition())
1373 return false;
1374
1375 return style.usesAnchorFunctions();
1376}
1377
1378bool AnchorPositionEvaluator::isLayoutTimeAnchorPositioned(const RenderStyle& style)
1379{
1380 if (!style.display().doesGenerateBox() || !style.hasOutOfFlowPosition())
1381 return false;
1382
1383 if (!style.positionArea().isNone())
1384 return true;
1385
1386 return style.justifySelf().isAnchorCenter() || style.alignSelf().isAnchorCenter();
1387}
1388
1389static CSSPropertyID NODELETE[[clang::annotate_type("webkit.nodelete")]] flipHorizontal(CSSPropertyID propertyID)
1390{
1391 switch (propertyID) {
1392 case CSSPropertyLeft:
1393 return CSSPropertyRight;
1394 case CSSPropertyRight:
1395 return CSSPropertyLeft;
1396 case CSSPropertyMarginLeft:
1397 return CSSPropertyMarginRight;
1398 case CSSPropertyMarginRight:
1399 return CSSPropertyMarginLeft;
1400 default:
1401 return propertyID;
1402 }
1403}
1404
1405static CSSPropertyID NODELETE[[clang::annotate_type("webkit.nodelete")]] flipVertical(CSSPropertyID propertyID)
1406{
1407 switch (propertyID) {
1408 case CSSPropertyTop:
1409 return CSSPropertyBottom;
1410 case CSSPropertyBottom:
1411 return CSSPropertyTop;
1412 case CSSPropertyMarginTop:
1413 return CSSPropertyMarginBottom;
1414 case CSSPropertyMarginBottom:
1415 return CSSPropertyMarginTop;
1416 default:
1417 return propertyID;
1418 }
1419}
1420
1421static CSSPropertyID flipStart(CSSPropertyID propertyID, WritingMode writingMode)
1422{
1423 auto logicalProperty = CSSProperty::unresolvePhysicalProperty(propertyID, writingMode);
1424
1425 auto flippedLogical = [&] {
1426 switch (logicalProperty) {
1427 case CSSPropertyInsetBlockStart:
1428 return CSSPropertyInsetInlineStart;
1429 case CSSPropertyInsetBlockEnd:
1430 return CSSPropertyInsetInlineEnd;
1431 case CSSPropertyBlockSize:
1432 return CSSPropertyInlineSize;
1433 case CSSPropertyMinBlockSize:
1434 return CSSPropertyMinInlineSize;
1435 case CSSPropertyMaxBlockSize:
1436 return CSSPropertyMaxInlineSize;
1437 case CSSPropertyInsetInlineStart:
1438 return CSSPropertyInsetBlockStart;
1439 case CSSPropertyInsetInlineEnd:
1440 return CSSPropertyInsetBlockEnd;
1441 case CSSPropertyInlineSize:
1442 return CSSPropertyBlockSize;
1443 case CSSPropertyMinInlineSize:
1444 return CSSPropertyMinBlockSize;
1445 case CSSPropertyMaxInlineSize:
1446 return CSSPropertyMaxBlockSize;
1447 case CSSPropertyMarginBlockStart:
1448 return CSSPropertyMarginInlineStart;
1449 case CSSPropertyMarginBlockEnd:
1450 return CSSPropertyMarginInlineEnd;
1451 case CSSPropertyMarginInlineStart:
1452 return CSSPropertyMarginBlockStart;
1453 case CSSPropertyMarginInlineEnd:
1454 return CSSPropertyMarginBlockEnd;
1455 case CSSPropertyAlignSelf:
1456 return CSSPropertyJustifySelf;
1457 case CSSPropertyJustifySelf:
1458 return CSSPropertyAlignSelf;
1459 default:
1460 return propertyID;
1461 }
1462 };
1463 return CSSProperty::resolveDirectionAwareProperty(flippedLogical(), writingMode);
1464}
1465
1466CSSPropertyID AnchorPositionEvaluator::resolvePositionTryFallbackProperty(CSSPropertyID propertyID, WritingMode writingMode, const BuilderPositionTryFallback& fallback)
1467{
1468 ASSERT(!CSSProperty::isDirectionAwareProperty(propertyID))((void)0);
1469
1470 for (auto tactic : fallback.tactics) {
1471 switch (tactic) {
1472 case PositionTryFallback::Tactic::FlipInline:
1473 propertyID = writingMode.isHorizontal() ? flipHorizontal(propertyID) : flipVertical(propertyID);
1474 break;
1475 case PositionTryFallback::Tactic::FlipBlock:
1476 propertyID = writingMode.isHorizontal() ? flipVertical(propertyID) : flipHorizontal(propertyID);
1477 break;
1478 case PositionTryFallback::Tactic::FlipX:
1479 propertyID = flipHorizontal(propertyID);
1480 break;
1481 case PositionTryFallback::Tactic::FlipY:
1482 propertyID = flipVertical(propertyID);
1483 break;
1484 case PositionTryFallback::Tactic::FlipStart:
1485 propertyID = flipStart(propertyID, writingMode);
1486 break;
1487 }
1488 }
1489 return propertyID;
1490}
1491
1492CSSValueID AnchorPositionEvaluator::resolvePositionTryFallbackValueForSelfPosition(CSSPropertyID propertyID, CSSValueID position, WritingMode writingMode, const BuilderPositionTryFallback& fallback)
1493{
1494 // Implements the bullet starting "For the self-alignment properties" from step 4 of https://drafts.csswg.org/css-anchor-position-1/#swap-due-to-a-try-tactic.
1495
1496 ASSERT(propertyID == CSSPropertyAlignSelf || propertyID == CSSPropertyJustifySelf)((void)0);
1497
1498 auto flipSidedPosition = [](auto position) -> CSSValueID {
1499 // Swap to the "opposite" position if the current position is "sided".
1500 // If there is no opposite value, nothing is changed.
1501 switch (position) {
1502 case CSSValueStart:
1503 return CSSValueEnd;
1504 case CSSValueEnd:
1505 return CSSValueStart;
1506 case CSSValueSelfStart:
1507 return CSSValueSelfEnd;
1508 case CSSValueSelfEnd:
1509 return CSSValueSelfStart;
1510 case CSSValueFlexStart:
1511 return CSSValueFlexEnd;
1512 case CSSValueFlexEnd:
1513 return CSSValueFlexStart;
1514 case CSSValueLeft:
1515 return CSSValueRight;
1516 case CSSValueRight:
1517 return CSSValueLeft;
1518 default:
1519 return position;
1520 }
1521 };
1522
1523 auto flipStart = [](auto writingMode, auto position) -> CSSValueID {
1524 // `justify-self` additionally takes `left`/`right`, `align-self` doesn't. When
1525 // applying `flip-start`, `justify-self` gets swapped with `align-self` (see
1526 // call to `flipStart` in `AnchorPositionEvaluator::resolvePositionTryFallbackProperty`).
1527 // So if we're resolving `justify-self` (which later gets swapped with `align-self`),
1528 // and the position is `left`/`right`, resolve it to `self-start`/`self-end`.
1529 switch (position) {
1530 case CSSValueLeft:
1531 return writingMode.bidiDirection() == TextDirection::LTR ? CSSValueSelfStart : CSSValueSelfEnd;
1532 case CSSValueRight:
1533 return writingMode.bidiDirection() == TextDirection::LTR ? CSSValueSelfEnd : CSSValueSelfStart;
1534 default:
1535 return position;
1536 }
1537 };
1538
1539 for (auto tactic : fallback.tactics) {
1540 switch (tactic) {
1541 case PositionTryFallback::Tactic::FlipBlock:
1542 if (propertyID == CSSPropertyAlignSelf)
1543 position = flipSidedPosition(position);
1544 break;
1545 case PositionTryFallback::Tactic::FlipInline:
1546 if (propertyID == CSSPropertyJustifySelf)
1547 position = flipSidedPosition(position);
1548 break;
1549 case PositionTryFallback::Tactic::FlipX:
1550 if (propertyID == (writingMode.isHorizontal() ? CSSPropertyJustifySelf : CSSPropertyAlignSelf))
1551 position = flipSidedPosition(position);
1552 break;
1553 case PositionTryFallback::Tactic::FlipY:
1554 if (propertyID == (writingMode.isHorizontal() ? CSSPropertyAlignSelf : CSSPropertyJustifySelf))
1555 position = flipSidedPosition(position);
1556 break;
1557 case PositionTryFallback::Tactic::FlipStart:
1558 if (propertyID == CSSPropertyJustifySelf)
1559 position = flipStart(writingMode, position);
1560 break;
1561 }
1562 }
1563
1564 return position;
1565}
1566
1567bool AnchorPositionEvaluator::overflowsInsetModifiedContainingBlock(const RenderBox& anchoredBox)
1568{
1569 if (!anchoredBox.isOutOfFlowPositioned())
1570 return false;
1571
1572 auto inlineConstraints = PositionedLayoutConstraints { anchoredBox, LogicalBoxAxis::Inline };
1573 auto blockConstraints = PositionedLayoutConstraints { anchoredBox, LogicalBoxAxis::Block };
1574 inlineConstraints.computeInsets();
1575 blockConstraints.computeInsets();
1576
1577 auto anchorInlineSize = anchoredBox.logicalWidth() + anchoredBox.marginStart() + anchoredBox.marginEnd();
1578 auto anchorBlockSize = anchoredBox.logicalHeight() + anchoredBox.marginBefore() + anchoredBox.marginAfter();
1579
1580 return inlineConstraints.insetModifiedContainingSize() < anchorInlineSize
1581 || blockConstraints.insetModifiedContainingSize() < anchorBlockSize;
1582}
1583
1584bool AnchorPositionEvaluator::isDefaultAnchorInvisibleOrClippedByInterveningBoxes(const RenderBox& anchoredBox)
1585{
1586 CheckedPtr defaultAnchor = defaultAnchorForBox(anchoredBox);
1587 if (!defaultAnchor)
1588 return false;
1589
1590 if (defaultAnchor->style().usedVisibility() == Visibility::Hidden)
1591 return true;
1592
1593 CheckedPtr anchorBox = dynamicDowncast<RenderBox>(*defaultAnchor);
1594
1595 // https://drafts.csswg.org/css-anchor-position-1/#position-visibility
1596 // "An anchor box anchor is clipped by intervening boxes relative to a positioned box abspos relying on it if anchor’s ink overflow
1597 // rectangle is fully clipped by a box which is an ancestor of anchor but a descendant of abspos’s containing block."
1598
1599 auto localAnchorRect = [&] {
1600 if (anchorBox)
1601 return anchorBox->visualOverflowRect();
1602 return downcast<RenderInline>(*defaultAnchor).linesVisualOverflowBoundingBox();
1603 }();
1604 auto* anchoredContainingBlock = anchoredBox.container();
1605
1606 auto anchorRect = defaultAnchor->localToAbsoluteQuad(FloatQuad { localAnchorRect }).boundingBox();
1607
1608 for (auto* anchorAncestor = defaultAnchor->container(); anchorAncestor && anchorAncestor != anchoredContainingBlock; anchorAncestor = anchorAncestor->container()) {
1609 if (!anchorAncestor->hasNonVisibleOverflow())
1610 continue;
1611 auto* clipAncestor = dynamicDowncast<RenderBox>(*anchorAncestor);
1612 if (!clipAncestor)
1613 continue;
1614 auto localClipRect = clipAncestor->overflowClipRect({ });
1615 auto clipRect = clipAncestor->localToAbsoluteQuad(FloatQuad { localClipRect }).boundingBox();
1616 if (!clipRect.intersects(anchorRect))
1617 return true;
1618 }
1619
1620 if (anchorBox) {
1621 // Test for chained anchors.
1622 if (isDefaultAnchorInvisibleOrClippedByInterveningBoxes(*anchorBox))
1623 return true;
1624 }
1625
1626 return false;
1627}
1628
1629// FIXME: The code should operate fully on host/pseudoElementIdentifier pairs and not use PseudoElements to
1630// support pseudo-elements other than ::before/::after.
1631RefPtr<const Element> AnchorPositionEvaluator::anchorPositionedElementOrPseudoElement(BuilderState& builderState)
1632{
1633 RefPtr element = builderState.element();
1634 if (auto identifier = builderState.style().pseudoElementIdentifier())
1635 return element->pseudoElementIfExists(*identifier);
1636 return element;
1637}
1638
1639AnchorPositionedKey AnchorPositionEvaluator::keyForElementOrPseudoElement(const Element& element)
1640{
1641 if (auto* pseudoElement = dynamicDowncast<PseudoElement>(element))
1642 return { pseudoElement->hostElement(), PseudoElementIdentifier { pseudoElement->pseudoElementType() } };
1643 return { &element, { } };
1644}
1645
1646bool AnchorPositionEvaluator::isAnchor(const RenderStyle& style)
1647{
1648 if (!style.anchorNames().isNone())
1649 return true;
1650
1651 return isImplicitAnchor(style);
1652}
1653
1654bool AnchorPositionEvaluator::isImplicitAnchor(const RenderStyle& style)
1655{
1656 // The invoker is an implicit anchor for the popover.
1657 // https://drafts.csswg.org/css-anchor-position-1/#implicit
1658 if (style.isPopoverInvoker())
1659 return true;
1660
1661 // "The implicit anchor element of a pseudo-element is its originating element, unless otherwise specified."
1662 // https://drafts.csswg.org/css-anchor-position-1/#implicit
1663 auto isImplicitAnchorForPseudoElement = [&](PseudoElementType pseudoElementType) {
1664 const RenderStyle* pseudoElementStyle = style.getCachedPseudoStyle({ pseudoElementType });
1665 if (!pseudoElementStyle)
1666 return false;
1667 // If we have an explicit anchor name then there is no need for an implicit anchor.
1668 if (!pseudoElementStyle->positionAnchor().isAuto())
1669 return false;
1670
1671 return pseudoElementStyle->usesAnchorFunctions() || isLayoutTimeAnchorPositioned(*pseudoElementStyle);
1672 };
1673 return isImplicitAnchorForPseudoElement(PseudoElementType::Before) || isImplicitAnchorForPseudoElement(PseudoElementType::After);
1674}
1675
1676ScopedName AnchorPositionEvaluator::defaultAnchorName(const RenderStyle& style)
1677{
1678 if (auto name = style.positionAnchor().tryName())
1679 return *name;
1680 return implicitAnchorElementName();
1681}
1682
1683CheckedPtr<RenderBoxModelObject> AnchorPositionEvaluator::defaultAnchorForBox(const RenderBox& box)
1684{
1685 if (!box.element())
1686 return nullptr;
1687
1688 CheckedRef element = *box.element();
1689
1690 auto& anchorPositionedMap = box.document().styleScope().anchorPositionedToAnchorMap();
1691 auto it = anchorPositionedMap.find(element);
1692 if (it == anchorPositionedMap.end())
1693 return nullptr;
1694
1695 auto anchorName = ResolvedScopedName::createFromScopedName(element, defaultAnchorName(box.style()));
1696
1697 for (auto& anchor : it->value.anchors) {
1698 if (anchorName == anchor.name)
1699 return anchor.renderer.get();
1700 }
1701 return nullptr;
1702}
1703
1704HashMap<AnchorPositionedKey, size_t> AnchorPositionEvaluator::recordLastSuccessfulPositionOptions(const SingleThreadWeakHashSet<const RenderBox>& positionTryBoxes)
1705{
1706 HashMap<Style::AnchorPositionedKey, size_t> lastSuccessfulPositionOptionMap;
1707
1708 for (const auto& positionTryBox : positionTryBoxes) {
1709 auto styleable = Styleable::fromRenderer(positionTryBox);
1710 ASSERT(styleable)((void)0);
1711
1712 if (auto usedPositionOptionIndex = positionTryBox.style().usedPositionOptionIndex())
1713 lastSuccessfulPositionOptionMap.add({ styleable->element, styleable->pseudoElementIdentifier }, *usedPositionOptionIndex);
1714 }
1715
1716 return lastSuccessfulPositionOptionMap;
1717}
1718
1719} // namespace Style
1720
1721} // namespace WebCore