Bug Summary

File:Volumes/Data/worker/Apple-iOS-26-Safer-CPP-Checks-EWS/build/Source/WebCore/editing/Editing.cpp
Warning:line 1225, column 15
Local variable 'cachedImage' 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 UnifiedSource204.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/UnifiedSource204.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/UnifiedSource204.plist -x c++ /Volumes/Data/worker/Apple-iOS-26-Safer-CPP-Checks-EWS/build/WebKitBuild/Release-iphonesimulator/DerivedSources/WebCore/unified-sources/UnifiedSource204.cpp
1/*
2 * Copyright (C) 2004-2025 Apple Inc. All rights reserved.
3 * Copyright (C) 2015 Google Inc. All rights reserved.
4 *
5 * Redistribution and use in source and binary forms, with or without
6 * modification, are permitted provided that the following conditions
7 * are met:
8 * 1. Redistributions of source code must retain the above copyright
9 * notice, this list of conditions and the following disclaimer.
10 * 2. Redistributions in binary form must reproduce the above copyright
11 * notice, this list of conditions and the following disclaimer in the
12 * documentation and/or other materials provided with the distribution.
13 *
14 * THIS SOFTWARE IS PROVIDED BY APPLE INC. ``AS IS'' AND ANY
15 * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
16 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
17 * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL APPLE INC. OR
18 * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
19 * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
20 * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
21 * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY
22 * OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
23 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
24 * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
25 */
26
27#include "config.h"
28#include "Editing.h"
29
30#include "AXObjectCache.h"
31#include "CachedImage.h"
32#include "ContainerNodeInlines.h"
33#include "EditingInlines.h"
34#include "Editor.h"
35#include "ElementChildIteratorInlines.h"
36#include "ElementInlines.h"
37#include "ElementRareData.h"
38#include "FrameDestructionObserverInlines.h"
39#include "GraphicsLayer.h"
40#include "HTMLBodyElement.h"
41#include "HTMLDListElement.h"
42#include "HTMLDivElement.h"
43#include "HTMLElementFactory.h"
44#include "HTMLImageElement.h"
45#include "HTMLInterchange.h"
46#include "HTMLLIElement.h"
47#include "HTMLNames.h"
48#include "HTMLOListElement.h"
49#include "HTMLParagraphElement.h"
50#include "HTMLPictureElement.h"
51#include "HTMLSpanElement.h"
52#include "HTMLTableElement.h"
53#include "HTMLTextFormControlElement.h"
54#include "HTMLUListElement.h"
55#include "HitTestSource.h"
56#include "ImageOverlay.h"
57#include "LocalFrame.h"
58#include "NodeTraversal.h"
59#include "PositionIterator.h"
60#include "Range.h"
61#include "RenderBlock.h"
62#include "RenderElement.h"
63#include "RenderLayer.h"
64#include "RenderLayerBacking.h"
65#include "RenderObjectInlines.h"
66#include "RenderStyle+GettersInlines.h"
67#include "RenderTableCell.h"
68#include "RenderTextControlSingleLine.h"
69#include "RenderedPosition.h"
70#include "ShadowRoot.h"
71#include "Text.h"
72#include "TextControlInnerElements.h"
73#include "TextIterator.h"
74#include "VisibleUnits.h"
75#include "WritingMode.h"
76#include <wtf/Assertions.h>
77#include <wtf/IterationStatus.h>
78#include <wtf/StdLibExtras.h>
79#include <wtf/text/StringBuilder.h>
80#include <wtf/unicode/CharacterNames.h>
81
82namespace WebCore {
83
84using namespace HTMLNames;
85
86static bool isVisiblyAdjacent(const Position&, const Position&);
87
88bool canHaveChildrenForEditing(const Node& node)
89{
90 return !is<Text>(node) && !is<HTMLPictureElement>(node) && node.canContainRangeEndPoint();
91}
92
93// Atomic means that the node has no children, or has children which are ignored for the purposes of editing.
94bool isAtomicNode(const Node* node)
95{
96 return node && (!node->hasChildNodes() || editingIgnoresContent(*node));
97}
98
99RefPtr<ContainerNode> highestEditableRoot(const Position& position, EditableType editableType)
100{
101 RefPtr<ContainerNode> highestEditableRoot = editableRootForPosition(position, editableType);
102 if (!highestEditableRoot)
103 return nullptr;
104
105 for (RefPtr<ContainerNode> node = highestEditableRoot; !is<HTMLBodyElement>(*node); ) {
106 node = node->parentNode();
107 if (!node)
108 break;
109 // FIXME: Can this ever be a Document or DocumentFragment? If not, this should return Element* instead.
110 if (hasEditableStyle(*node, editableType))
111 highestEditableRoot = node;
112 }
113
114 return highestEditableRoot;
115}
116
117Element* lowestEditableAncestor(Node* node)
118{
119 for (RefPtr currentNode = node; currentNode; currentNode = currentNode->parentNode()) {
120 if (currentNode->hasEditableStyle())
121 return currentNode->rootEditableElement();
122 if (is<HTMLBodyElement>(*currentNode))
123 break;
124 }
125 return nullptr;
126}
127
128static bool isEditableToAccessibility(const Node& node)
129{
130 ASSERT(AXObjectCache::accessibilityEnabled())((void)0);
131 ASSERT(node.document().existingAXObjectCache())((void)0);
132
133 if (CheckedPtr cache = node.document().existingAXObjectCache())
134 return cache->rootAXEditableElement(&node);
135
136 return false;
137}
138
139static bool computeEditability(const Node& node, EditableType editableType, Node::ShouldUpdateStyle shouldUpdateStyle)
140{
141 if (node.computeEditability(Node::UserSelectAllTreatment::NotEditable, shouldUpdateStyle) != Node::Editability::ReadOnly)
142 return true;
143
144 switch (editableType) {
145 case ContentIsEditable:
146 return false;
147 case HasEditableAXRole:
148 return isEditableToAccessibility(node);
149 }
150 ASSERT_NOT_REACHED()((void)0);
151 return false;
152}
153
154bool hasEditableStyle(const Node& node, EditableType editableType)
155{
156 return computeEditability(node, editableType, Node::ShouldUpdateStyle::DoNotUpdate);
157}
158
159bool isEditableNode(const Node& node)
160{
161 return computeEditability(node, ContentIsEditable, Node::ShouldUpdateStyle::Update);
162}
163
164bool isEditablePosition(const Position& position, EditableType editableType)
165{
166 RefPtr node = position.containerNode();
167 return node && computeEditability(*node, editableType, Node::ShouldUpdateStyle::Update);
168}
169
170bool isAtUnsplittableElement(const Position& position)
171{
172 RefPtr node = position.containerNode();
173 return node == editableRootForPosition(position) || node == enclosingNodeOfType(position, isTableCell);
174}
175
176bool isRichlyEditablePosition(const Position& position)
177{
178 RefPtr node = position.containerNode();
179 return node && node->hasRichlyEditableStyle();
180}
181
182Element* editableRootForPosition(const Position& position, EditableType editableType)
183{
184 RefPtr node = position.containerNode();
185 if (!node)
186 return nullptr;
187
188 switch (editableType) {
189 case HasEditableAXRole:
190 if (CheckedPtr cache = node->document().existingAXObjectCache())
191 return const_cast<Element*>(cache->rootAXEditableElement(node.get()));
192 [[fallthrough]];
193 case ContentIsEditable:
194 return node->rootEditableElement();
195 }
196 return nullptr;
197}
198
199// Finds the enclosing element until which the tree can be split.
200// When a user hits ENTER, he/she won't expect this element to be split into two.
201// You may pass it as the second argument of splitTreeToNode.
202RefPtr<Element> unsplittableElementForPosition(const Position& position)
203{
204 // Since enclosingNodeOfType won't search beyond the highest root editable node,
205 // this code works even if the closest table cell was outside of the root editable node.
206 if (RefPtr enclosingCell = downcast<Element>(enclosingNodeOfType(position, &isTableCell)))
207 return enclosingCell;
208 return editableRootForPosition(position);
209}
210
211Position nextCandidate(const Position& position)
212{
213 for (PositionIterator nextPosition = position; !nextPosition.atEnd(); ) {
214 nextPosition.increment();
215 if (nextPosition.isCandidate())
216 return nextPosition;
217 }
218 return { };
219}
220
221Position nextVisuallyDistinctCandidate(const Position& position, SkipDisplayContents skipDisplayContents)
222{
223 // FIXME: Use PositionIterator instead.
224 Position nextPosition = position;
225 Position downstreamStart = nextPosition.downstream();
226 while (!nextPosition.atEndOfTree()) {
227 nextPosition = nextPosition.next(Character);
228 if (nextPosition.isCandidate() && nextPosition.downstream() != downstreamStart)
229 return nextPosition;
230 if (RefPtr node = nextPosition.containerNode()) {
231 if (!node->renderer()) {
232 if (skipDisplayContents == SkipDisplayContents::No) {
233 if (auto element = dynamicDowncast<Element>(node); element && element->hasDisplayContents())
234 continue;
235 }
236 nextPosition = lastPositionInOrAfterNode(node.get());
237 }
238 }
239 }
240 return { };
241}
242
243Position previousCandidate(const Position& position)
244{
245 PositionIterator previousPosition = position;
246 while (!previousPosition.atStart()) {
247 previousPosition.decrement();
248 if (previousPosition.isCandidate())
249 return previousPosition;
250 }
251 return { };
252}
253
254Position previousVisuallyDistinctCandidate(const Position& position)
255{
256 // FIXME: Use PositionIterator instead.
257 Position previousPosition = position;
258 Position downstreamStart = previousPosition.downstream();
259 while (!previousPosition.atStartOfTree()) {
260 previousPosition = previousPosition.previous(Character);
261 if (previousPosition.isCandidate() && previousPosition.downstream() != downstreamStart)
262 return previousPosition;
263 if (RefPtr node = previousPosition.containerNode()) {
264 if (!node->renderer())
265 previousPosition = firstPositionInOrBeforeNode(node.get());
266 }
267 }
268 return { };
269}
270
271Position firstEditablePositionAfterPositionInRoot(const Position& position, ContainerNode* highestRoot)
272{
273 if (!highestRoot)
274 return { };
275
276 // position falls before highestRoot.
277 if (auto result = firstPositionInNode(*highestRoot); position < result && highestRoot->hasEditableStyle())
278 return result;
279
280 Position candidate = position;
281
282 if (&position.deprecatedNode()->treeScope() != &highestRoot->treeScope()) {
283 RefPtr shadowAncestor = highestRoot->treeScope().ancestorNodeInThisScope(position.deprecatedNode());
284 if (!shadowAncestor)
285 return { };
286
287 candidate = positionAfterNode(*shadowAncestor);
288 }
289
290 while (candidate.deprecatedNode() && !isEditablePosition(candidate) && protect(candidate.deprecatedNode())->isDescendantOf(*highestRoot))
291 candidate = isAtomicNode(candidate.deprecatedNode()) ? positionInParentAfterNode(protect(*candidate.deprecatedNode())) : nextVisuallyDistinctCandidate(candidate);
292
293 if (candidate.deprecatedNode() && !protect(candidate.deprecatedNode())->isInclusiveDescendantOf(*highestRoot))
294 return { };
295
296 return candidate;
297}
298
299Position lastEditablePositionBeforePositionInRoot(const Position& position, ContainerNode* highestRoot)
300{
301 if (!highestRoot)
302 return { };
303
304 // When position falls after highestRoot, the result is easy to compute.
305 if (auto result = lastPositionInNode(*highestRoot); position > result)
306 return result;
307
308 Position candidate = position;
309
310 if (&position.deprecatedNode()->treeScope() != &highestRoot->treeScope()) {
311 RefPtr shadowAncestor = highestRoot->treeScope().ancestorNodeInThisScope(position.deprecatedNode());
312 if (!shadowAncestor)
313 return { };
314
315 candidate = firstPositionInOrBeforeNode(shadowAncestor.get());
316 }
317
318 while (candidate.deprecatedNode() && !isEditablePosition(candidate) && protect(candidate.deprecatedNode())->isDescendantOf(*highestRoot))
319 candidate = isAtomicNode(candidate.deprecatedNode()) ? positionInParentBeforeNode(protect(*candidate.deprecatedNode())) : previousVisuallyDistinctCandidate(candidate);
320
321 if (candidate.deprecatedNode() && !protect(candidate.deprecatedNode())->isInclusiveDescendantOf(*highestRoot))
322 return { };
323
324 return candidate;
325}
326
327// FIXME: The function name, comment, and code say three different things here!
328// Whether or not content before and after this node will collapse onto the same line as it.
329bool isBlock(const Node& node)
330{
331 return node.renderer() && !node.renderer()->isInline();
332}
333
334bool isInline(const Node& node)
335{
336 return node.renderer() && node.renderer()->isInline();
337}
338
339// FIXME: Deploy this in all of the places where enclosingBlockFlow/enclosingBlockFlowOrTableElement are used.
340// FIXME: Pass a position to this function. The enclosing block of [table, x] for example, should be the
341// block that contains the table and not the table, and this function should be the only one responsible for
342// knowing about these kinds of special cases.
343RefPtr<Element> enclosingBlock(RefPtr<Node> node, EditingBoundaryCrossingRule rule)
344{
345 return dynamicDowncast<Element>(enclosingNodeOfType(firstPositionInOrBeforeNode(node.get()), isBlock, rule));
346}
347
348TextDirection directionOfEnclosingBlock(const Position& position)
349{
350 auto block = enclosingBlock(protect(position.containerNode()));
351 if (!block)
352 return TextDirection::LTR;
353 CheckedPtr renderer = block->renderer();
354 if (!renderer)
355 return TextDirection::LTR;
356 return renderer->writingMode().bidiDirection();
357}
358
359// This method is used to create positions in the DOM. It returns the maximum valid offset
360// in a node. It returns 1 for some elements even though they do not have children, which
361// creates technically invalid DOM Positions. Be sure to call parentAnchoredEquivalent
362// on a Position before using it to create a DOM Range, or an exception will be thrown.
363int lastOffsetForEditing(const Node& node)
364{
365 if (node.isCharacterDataNode() || node.hasChildNodes())
366 return node.length();
367
368 // FIXME: Might be more helpful to return 1 for any node where editingIgnoresContent is true, even one that happens to have child nodes, like a select element with option node children.
369 return editingIgnoresContent(node) ? 1 : 0;
370}
371
372bool isAmbiguousBoundaryCharacter(char16_t character)
373{
374 // These are characters that can behave as word boundaries, but can appear within words.
375 // If they are just typed, i.e. if they are immediately followed by a caret, we want to delay text checking until the next character has been typed.
376 // FIXME: this is required until <rdar://problem/6853027> is fixed and text checking can do this for us.
377 return character == '\'' || character == '@' || character == rightSingleQuotationMark || character == hebrewPunctuationGershayim;
378}
379
380String stringWithRebalancedWhitespace(const String& string, bool startIsStartOfParagraph, bool shouldEmitNBSPbeforeEnd)
381{
382 StringBuilder rebalancedString;
383
384 bool previousCharacterWasSpace = false;
385 unsigned length = string.length();
386 for (unsigned i = 0; i < length; ++i) {
387 auto character = string[i];
388 if (!deprecatedIsEditingWhitespace(character)) {
389 previousCharacterWasSpace = false;
390 continue;
391 }
392 Latin1Character selectedWhitespaceCharacter;
393 // We need to ensure there is no next sibling text node. See https://bugs.webkit.org/show_bug.cgi?id=123163
394 if (previousCharacterWasSpace || (!i && startIsStartOfParagraph) || (i == length - 1 && shouldEmitNBSPbeforeEnd)) {
395 selectedWhitespaceCharacter = noBreakSpace;
396 previousCharacterWasSpace = false;
397 } else {
398 selectedWhitespaceCharacter = ' ';
399 previousCharacterWasSpace = true;
400 }
401 if (character == selectedWhitespaceCharacter)
402 continue;
403 rebalancedString.reserveCapacity(length);
404 rebalancedString.appendSubstring(string, rebalancedString.length(), i - rebalancedString.length());
405 rebalancedString.append(selectedWhitespaceCharacter);
406 }
407
408 if (rebalancedString.isEmpty())
409 return string;
410
411 rebalancedString.reserveCapacity(length);
412 rebalancedString.appendSubstring(string, rebalancedString.length(), length - rebalancedString.length());
413 return rebalancedString.toString();
414}
415
416bool isTableStructureNode(const Node& node)
417{
418 auto* renderer = node.renderer();
419 return renderer && (renderer->isRenderTableCell() || renderer->isRenderTableRow() || renderer->isRenderTableSection() || renderer->isRenderTableCol());
420}
421
422const String& nonBreakingSpaceString()
423{
424 static NeverDestroyed<String> nonBreakingSpaceString(span(noBreakSpace));
425 return nonBreakingSpaceString;
426}
427
428RefPtr<Element> isFirstPositionAfterTable(const VisiblePosition& position)
429{
430 Position upstream(position.deepEquivalent().upstream());
431 RefPtr node = upstream.deprecatedNode();
432 if (!node)
433 return nullptr;
434 CheckedPtr renderer = node->renderer();
435 if (!renderer || !renderer->isRenderTable() || !upstream.atLastEditingPositionForNode())
436 return nullptr;
437 return downcast<Element>(node.releaseNonNull());
438}
439
440RefPtr<Element> isLastPositionBeforeTable(const VisiblePosition& position)
441{
442 Position downstream(position.deepEquivalent().downstream());
443 RefPtr node = downstream.deprecatedNode();
444 if (!node)
445 return nullptr;
446 CheckedPtr renderer = node->renderer();
447 if (!renderer || !renderer->isRenderTable() || !downstream.atFirstEditingPositionForNode())
448 return nullptr;
449 return downcast<Element>(node.releaseNonNull());
450}
451
452// Returns the visible position at the beginning of a node
453VisiblePosition visiblePositionBeforeNode(Node& node)
454{
455 if (node.hasChildNodes())
456 return VisiblePosition(firstPositionInOrBeforeNode(&node));
457 ASSERT(node.parentNode())((void)0);
458 ASSERT(!node.parentNode()->isShadowRoot())((void)0);
459 return positionInParentBeforeNode(node);
460}
461
462// Returns the visible position at the ending of a node
463VisiblePosition visiblePositionAfterNode(Node& node)
464{
465 if (node.hasChildNodes())
466 return VisiblePosition(lastPositionInOrAfterNode(&node));
467 ASSERT(node.parentNode())((void)0);
468 ASSERT(!node.parentNode()->isShadowRoot())((void)0);
469 return positionInParentAfterNode(node);
470}
471
472VisiblePosition closestEditablePositionInElementForAbsolutePoint(const Element& element, const IntPoint& point)
473{
474 if (!element.isConnected() || !element.document().frame())
475 return { };
476
477 Ref<const Element> protectedElement { element };
478 protect(element.document())->updateLayoutIgnorePendingStylesheets();
479
480 CheckedPtr renderer = element.renderer();
481 // Look at the inner element of a form control, not the control itself, as it is the editable part.
482 if (RefPtr formControlElement = dynamicDowncast<HTMLTextFormControlElement>(element)) {
483 if (!formControlElement->isInnerTextElementEditable())
484 return { };
485 if (RefPtr innerTextElement = formControlElement->innerTextElement())
486 renderer = innerTextElement->renderer();
487 }
488 if (!renderer)
489 return { };
490 auto absoluteBoundingBox = renderer->absoluteBoundingBoxRect();
491 auto constrainedAbsolutePoint = point.constrainedBetween(absoluteBoundingBox.minXMinYCorner(), absoluteBoundingBox.maxXMaxYCorner());
492 auto localPoint = renderer->absoluteToLocal(constrainedAbsolutePoint, UseTransforms);
493 auto visiblePosition = renderer->visiblePositionForPoint(flooredLayoutPoint(localPoint), HitTestSource::User);
494 return isEditablePosition(visiblePosition.deepEquivalent()) ? visiblePosition : VisiblePosition { };
495}
496
497bool isListHTMLElement(Node* node)
498{
499 return isAnyOf<HTMLUListElement, HTMLOListElement, HTMLDListElement>(node);
500}
501
502bool isListItem(const Node& node)
503{
504 return isListHTMLElement(node.parentNode()) || (node.renderer() && node.renderer()->isRenderListItem());
505}
506
507Element* enclosingElementWithTag(const Position& position, const QualifiedName& tagName)
508{
509 auto root = highestEditableRoot(position);
510 for (RefPtr node = position.deprecatedNode(); node; node = node->parentNode()) {
511 if (root && !node->hasEditableStyle())
512 continue;
513 auto* element = dynamicDowncast<Element>(*node);
514 if (!element)
515 continue;
516 if (element->hasTagName(tagName))
517 return element;
518 if (node == root)
519 return nullptr;
520 }
521 return nullptr;
522}
523
524RefPtr<Node> enclosingNodeOfType(const Position& position, bool (*nodeIsOfType)(const Node&), EditingBoundaryCrossingRule rule)
525{
526 // FIXME: support CanSkipCrossEditingBoundary
527 ASSERT(rule == CanCrossEditingBoundary || rule == CannotCrossEditingBoundary)((void)0);
528 auto root = rule == CannotCrossEditingBoundary ? highestEditableRoot(position) : nullptr;
529 for (RefPtr n = position.deprecatedNode(); n; n = n->parentNode()) {
530 // Don't return a non-editable node if the input position was editable, since
531 // the callers from editing will no doubt want to perform editing inside the returned node.
532 if (root && !n->hasEditableStyle())
533 continue;
534 if (nodeIsOfType(*n))
535 return n;
536 if (n == root)
537 return nullptr;
538 }
539 return nullptr;
540}
541
542RefPtr<Node> highestEnclosingNodeOfType(const Position& position, bool (*nodeIsOfType)(const Node&), EditingBoundaryCrossingRule rule, Node* stayWithin)
543{
544 RefPtr<Node> highest;
545 RefPtr root = rule == CannotCrossEditingBoundary ? highestEditableRoot(position) : nullptr;
546 for (RefPtr<Node> n = position.containerNode(); n && n != stayWithin; n = n->parentNode()) {
547 if (root && !n->hasEditableStyle())
548 continue;
549 if (nodeIsOfType(*n))
550 highest = n.get();
551 if (n == root)
552 break;
553 }
554 return highest;
555}
556
557static bool hasARenderedDescendant(Node* node, Node* excludedNode)
558{
559 for (RefPtr n = node->firstChild(); n;) {
560 if (n == excludedNode) {
561 n = NodeTraversal::nextSkippingChildren(*n, node);
562 continue;
563 }
564 if (n->renderer())
565 return true;
566 n = NodeTraversal::next(*n, node);
567 }
568 return false;
569}
570
571RefPtr<Node> highestNodeToRemoveInPruning(Node* node)
572{
573 RefPtr<Node> previousNode;
574 RefPtr rootEditableElement = node ? node->rootEditableElement() : nullptr;
575 for (RefPtr currentNode = node; currentNode; currentNode = currentNode->parentNode()) {
576 if (CheckedPtr renderer = currentNode->renderer()) {
577 if (!renderer->canHaveChildren() || hasARenderedDescendant(currentNode.get(), previousNode.get()) || rootEditableElement == currentNode.get())
578 return previousNode;
579 }
580 previousNode = currentNode.get();
581 }
582 return nullptr;
583}
584
585RefPtr<Element> enclosingTableCell(const Position& position)
586{
587 return downcast<Element>(enclosingNodeOfType(position, isTableCell));
588}
589
590RefPtr<Element> enclosingAnchorElement(const Position& p)
591{
592 for (RefPtr node = p.deprecatedNode(); node; node = node->parentNode()) {
593 if (RefPtr element = dynamicDowncast<Element>(*node); element && element->isLink())
594 return element;
595 }
596 return nullptr;
597}
598
599RefPtr<HTMLElement> enclosingList(Node* node)
600{
601 if (!node)
602 return nullptr;
603
604 RefPtr root = highestEditableRoot(firstPositionInOrBeforeNode(node));
605
606 for (RefPtr ancestor = node->parentNode(); ancestor; ancestor = ancestor->parentNode()) {
607 auto* htmlElement = dynamicDowncast<HTMLElement>(*ancestor);
608 if (htmlElement && (isAnyOf<HTMLUListElement, HTMLOListElement>(*htmlElement)))
609 return htmlElement;
610 if (ancestor == root)
611 return nullptr;
612 }
613
614 return nullptr;
615}
616
617RefPtr<Node> enclosingListChild(Node* node)
618{
619 if (!node)
620 return nullptr;
621
622 // Check for a list item element, or for a node whose parent is a list element. Such a node
623 // will appear visually as a list item (but without a list marker)
624 RefPtr root = highestEditableRoot(firstPositionInOrBeforeNode(node));
625
626 // FIXME: This function is inappropriately named since it starts with node instead of node->parentNode()
627 for (RefPtr n = node; n && n->parentNode(); n = n->parentNode()) {
628 if (is<HTMLLIElement>(*n) || (isListHTMLElement(n->parentNode()) && n != root))
629 return n;
630 if (n == root || isTableCell(*n))
631 return nullptr;
632 }
633
634 return nullptr;
635}
636
637// FIXME: This function should not need to call isStartOfParagraph/isEndOfParagraph.
638RefPtr<Node> enclosingEmptyListItem(const VisiblePosition& position)
639{
640 // Check that position is on a line by itself inside a list item
641 RefPtr listChildNode = enclosingListChild(protect(position.deepEquivalent().deprecatedNode()).get());
642 if (!listChildNode || !isStartOfParagraph(position) || !isEndOfParagraph(position))
643 return nullptr;
644
645 VisiblePosition firstInListChild(firstPositionInOrBeforeNode(listChildNode.get()));
646 VisiblePosition lastInListChild(lastPositionInOrAfterNode(listChildNode.get()));
647
648 if (firstInListChild != position || lastInListChild != position)
649 return nullptr;
650
651 return listChildNode;
652}
653
654RefPtr<HTMLElement> outermostEnclosingList(Node* node, Node* rootList)
655{
656 RefPtr list = enclosingList(node);
657 if (!list)
658 return nullptr;
659
660 while (RefPtr nextList = enclosingList(list.get())) {
661 if (nextList == rootList)
662 break;
663 list = WTF::move(nextList);
664 }
665
666 return list;
667}
668
669bool canMergeLists(Element* firstList, Element* secondList)
670{
671 auto* first = dynamicDowncast<HTMLElement>(firstList);
672 auto* second = dynamicDowncast<HTMLElement>(secondList);
673 if (!first || !second)
674 return false;
675
676 return first->localName() == second->localName() // make sure the list types match (ol vs. ul)
677 && first->hasEditableStyle() && second->hasEditableStyle() // both lists are editable
678 && first->rootEditableElement() == second->rootEditableElement() // don't cross editing boundaries
679 // Make sure there is no visible content between this li and the previous list.
680 && isVisiblyAdjacent(positionInParentAfterNode(*first), positionInParentBeforeNode(*second));
681}
682
683static Node* previousNodeConsideringAtomicNodes(const Node* node)
684{
685 if (node->previousSibling()) {
686 SUPPRESS_UNCOUNTED_LOCAL[[clang::suppress]] auto* n = node->previousSibling();
687 while (!isAtomicNode(n) && n->lastChild())
688 n = n->lastChild();
689 return n;
690 }
691
692 return node->parentNode();
693}
694
695static Node* nextNodeConsideringAtomicNodes(const Node* node)
696{
697 if (!isAtomicNode(node) && node->firstChild())
698 return node->firstChild();
699 if (node->nextSibling())
700 return node->nextSibling();
701 RefPtr<const Node> n = node;
702 while (n && !n->nextSibling())
703 n = n->parentNode();
704 if (n)
705 return n->nextSibling();
706 return nullptr;
707}
708
709Node* previousLeafNode(const Node* nodeArg)
710{
711 SUPPRESS_UNCOUNTED_LOCAL[[clang::suppress]] auto* node = nodeArg;
712 while ((node = previousNodeConsideringAtomicNodes(node))) {
713 if (isAtomicNode(node))
714 return const_cast<Node*>(node);
715 }
716 return nullptr;
717}
718
719Node* nextLeafNode(const Node* nodeArg)
720{
721 SUPPRESS_UNCOUNTED_LOCAL[[clang::suppress]] auto* node = nodeArg;
722 while ((node = nextNodeConsideringAtomicNodes(node))) {
723 if (isAtomicNode(node))
724 return const_cast<Node*>(node);
725 }
726 return nullptr;
727}
728
729// FIXME: Do not require renderer, so that this can be used within fragments.
730bool isRenderedTable(const Node* node)
731{
732 RefPtr element = dynamicDowncast<HTMLElement>(node);
733 if (!element)
734 return false;
735 auto* renderer = element->renderer();
736 return renderer && renderer->isRenderTable();
737}
738
739bool isTableCell(const Node& node)
740{
741 auto* renderer = node.renderer();
742 if (!renderer)
743 return node.hasTagName(tdTag) || node.hasTagName(thTag);
744 return renderer->isRenderTableCell();
745}
746
747bool isEmptyTableCell(const Node* node)
748{
749 // Returns true IFF the passed in node is one of:
750 // .) a table cell with no children,
751 // .) a table cell with a single BR child, and which has no other child renderers, including :before and :after renderers
752 // .) the BR child of such a table cell
753
754 // Find rendered node
755 while (node && !node->renderer())
756 node = node->parentNode();
757 if (!node)
758 return false;
759
760 // Make sure the rendered node is a table cell or <br>.
761 // If it's a <br>, then the parent node has to be a table cell.
762 CheckedPtr renderer = node->renderer();
763 if (renderer->isBR()) {
764 renderer = renderer->parent();
765 if (!renderer)
766 return false;
767 }
768 CheckedPtr renderTableCell = dynamicDowncast<RenderTableCell>(*renderer);
769 if (!renderTableCell)
770 return false;
771
772 // Check that the table cell contains no child renderers except for perhaps a single <br>.
773 CheckedPtr childRenderer = renderTableCell->firstChild();
774 if (!childRenderer)
775 return true;
776 if (!childRenderer->isBR())
777 return false;
778 return !childRenderer->nextSibling();
779}
780
781Ref<HTMLElement> createDefaultParagraphElement(Document& document)
782{
783 switch (document.editor().defaultParagraphSeparator()) {
784 case EditorParagraphSeparator::div:
785 return HTMLDivElement::create(document);
786 case EditorParagraphSeparator::p:
787 break;
788 }
789 return HTMLParagraphElement::create(document);
790}
791
792Ref<HTMLElement> createHTMLElement(Document& document, const QualifiedName& name)
793{
794 return HTMLElementFactory::createElement(name, document);
795}
796
797Ref<HTMLElement> createHTMLElement(Document& document, const AtomString& tagName)
798{
799 return createHTMLElement(document, QualifiedName(nullAtom(), tagName, xhtmlNamespaceURI));
800}
801
802HTMLSpanElement* tabSpanNode(Node* node)
803{
804 if (auto* span = dynamicDowncast<HTMLSpanElement>(node); span && span->attributeWithoutSynchronization(classAttr) == AppleTabSpanClass)
805 return span;
806 return nullptr;
807}
808
809HTMLSpanElement* parentTabSpanNode(Node* node)
810{
811 return is<Text>(node) ? tabSpanNode(node->parentNode()) : nullptr;
812}
813
814static Ref<Element> createTabSpanElement(Document& document, Text& tabTextNode)
815{
816 auto spanElement = HTMLSpanElement::create(document);
817
818 spanElement->setAttributeWithoutSynchronization(classAttr, AppleTabSpanClass);
819 spanElement->setAttribute(styleAttr, "white-space:pre"_s);
820
821 spanElement->appendChild(tabTextNode);
822
823 return spanElement;
824}
825
826Ref<Element> createTabSpanElement(Document& document, String&& tabText)
827{
828 return createTabSpanElement(document, document.createTextNode(WTF::move(tabText)));
829}
830
831Ref<Element> createTabSpanElement(Document& document)
832{
833 return createTabSpanElement(document, document.createEditingTextNode("\t"_s));
834}
835
836unsigned numEnclosingMailBlockquotes(const Position& position)
837{
838 unsigned count = 0;
839 for (RefPtr node = position.deprecatedNode(); node; node = node->parentNode()) {
840 if (isMailBlockquote(*node))
841 ++count;
842 }
843 return count;
844}
845
846void updatePositionForNodeRemoval(Position& position, Node& node)
847{
848 if (position.isNull())
849 return;
850 switch (position.anchorType()) {
851 case Position::PositionIsBeforeChildren:
852 if (node.isShadowIncludingInclusiveAncestorOf(position.containerNode()))
853 position = positionInParentBeforeNode(node);
854 break;
855 case Position::PositionIsAfterChildren:
856 if (node.isShadowIncludingInclusiveAncestorOf(position.containerNode()))
857 position = positionInParentBeforeNode(node);
858 break;
859 case Position::PositionIsOffsetInAnchor:
860 if (position.containerNode() == node.parentNode() && static_cast<unsigned>(position.offsetInContainerNode()) > node.computeNodeIndex())
861 position.moveToOffset(position.offsetInContainerNode() - 1);
862 else if (node.isShadowIncludingInclusiveAncestorOf(position.containerNode()))
863 position = positionInParentBeforeNode(node);
864 break;
865 case Position::PositionIsAfterAnchor:
866 if (node.isShadowIncludingInclusiveAncestorOf(position.anchorNode()))
867 position = positionInParentAfterNode(node);
868 break;
869 case Position::PositionIsBeforeAnchor:
870 if (node.isShadowIncludingInclusiveAncestorOf(position.anchorNode()))
871 position = positionInParentBeforeNode(node);
872 break;
873 }
874}
875
876bool isMailBlockquote(const Node& node)
877{
878 auto* htmlElement = dynamicDowncast<HTMLElement>(node);
879 if (!htmlElement || !htmlElement->hasTagName(blockquoteTag))
880 return false;
881 return htmlElement->attributeWithoutSynchronization(typeAttr) == "cite"_s;
882}
883
884int caretMinOffset(const Node& node)
885{
886 CheckedPtr renderer = node.renderer();
887 ASSERT(!node.isCharacterDataNode() || !renderer || renderer->isRenderText())((void)0);
888
889 if (renderer && renderer->isRenderText())
890 return renderer->caretMinOffset();
891
892 if (RefPtr pictureElement = dynamicDowncast<HTMLPictureElement>(node)) {
893 if (RefPtr firstImage = childrenOfType<HTMLImageElement>(*pictureElement).first())
894 return firstImage->computeNodeIndex();
895 }
896
897 return 0;
898}
899
900// If a node can contain candidates for VisiblePositions, return the offset of the last candidate, otherwise
901// return the number of children for container nodes and the length for unrendered text nodes.
902int caretMaxOffset(const Node& node)
903{
904 // For rendered text nodes, return the last position that a caret could occupy.
905 if (auto* text = dynamicDowncast<Text>(node)) {
906 if (CheckedPtr renderer = text->renderer())
907 return renderer->caretMaxOffset();
908 }
909 return lastOffsetForEditing(node);
910}
911
912bool lineBreakExistsAtVisiblePosition(const VisiblePosition& position)
913{
914 return lineBreakExistsAtPosition(position.deepEquivalent().downstream());
915}
916
917bool lineBreakExistsAtPosition(const Position& position)
918{
919 if (position.isNull())
920 return false;
921
922 if (position.anchorNode()->hasTagName(brTag) && position.atFirstEditingPositionForNode())
923 return true;
924
925 if (!position.anchorNode()->renderer())
926 return false;
927
928 RefPtr textNode = dynamicDowncast<Text>(*position.anchorNode());
929 if (!textNode || !textNode->renderer()->style().preserveNewline())
930 return false;
931
932 unsigned offset = position.offsetInContainerNode();
933 return offset < textNode->length() && textNode->data()[offset] == '\n';
934}
935
936// Modifies selections that have an end point at the edge of a table
937// that contains the other endpoint so that they don't confuse
938// code that iterates over selected paragraphs.
939VisibleSelection selectionForParagraphIteration(const VisibleSelection& original)
940{
941 VisibleSelection newSelection(original);
942 VisiblePosition startOfSelection(newSelection.visibleStart());
943 VisiblePosition endOfSelection(newSelection.visibleEnd());
944
945 // If the end of the selection to modify is just after a table, and
946 // if the start of the selection is inside that table, then the last paragraph
947 // that we'll want modify is the last one inside the table, not the table itself
948 // (a table is itself a paragraph).
949 if (RefPtr table = isFirstPositionAfterTable(endOfSelection)) {
950 if (startOfSelection.deepEquivalent().deprecatedNode()->isDescendantOf(*table))
951 newSelection = VisibleSelection(startOfSelection, endOfSelection.previous(CannotCrossEditingBoundary));
952 }
953
954 // If the start of the selection to modify is just before a table,
955 // and if the end of the selection is inside that table, then the first paragraph
956 // we'll want to modify is the first one inside the table, not the paragraph
957 // containing the table itself.
958 if (RefPtr table = isLastPositionBeforeTable(startOfSelection)) {
959 if (endOfSelection.deepEquivalent().deprecatedNode()->isDescendantOf(*table))
960 newSelection = VisibleSelection(startOfSelection.next(CannotCrossEditingBoundary), endOfSelection);
961 }
962
963 return newSelection;
964}
965
966// FIXME: indexForVisiblePosition and visiblePositionForIndex use TextIterators to convert between
967// VisiblePositions and indices. But TextIterator iteration using TextIteratorBehavior::EmitsCharactersBetweenAllVisiblePositions
968// does not exactly match VisiblePosition iteration, so using them to preserve a selection during an editing
969// opertion is unreliable. TextIterator's TextIteratorBehavior::EmitsCharactersBetweenAllVisiblePositions mode needs to be fixed,
970// or these functions need to be changed to iterate using actual VisiblePositions.
971// FIXME: Deploy these functions everywhere that TextIterators are used to convert between VisiblePositions and indices.
972int indexForVisiblePosition(const VisiblePosition& visiblePosition, RefPtr<ContainerNode>& scope)
973{
974 if (visiblePosition.isNull())
975 return 0;
976
977 auto position = visiblePosition.deepEquivalent();
978 Ref document = *position.document();
979
980 auto editableRoot = highestEditableRoot(position, AXObjectCache::accessibilityEnabled() ? HasEditableAXRole : ContentIsEditable);
981 if (editableRoot && !document->inDesignMode())
982 scope = editableRoot;
983 else {
984 if (position.containerNode()->isInShadowTree())
985 scope = position.containerNode()->containingShadowRoot();
986 else
987 scope = WTF::move(document);
988 }
989
990 auto range = *makeSimpleRange(makeBoundaryPointBeforeNodeContents(*scope), position);
991 return characterCount(range, TextIteratorBehavior::EmitsCharactersBetweenAllVisiblePositions);
992}
993
994// FIXME: Merge this function with the one above.
995int indexForVisiblePosition(Node& node, const VisiblePosition& visiblePosition, TextIteratorBehaviors behaviors)
996{
997 if (visiblePosition.isNull())
998 return 0;
999
1000 auto range = makeSimpleRange(makeBoundaryPointBeforeNodeContents(node), visiblePosition);
1001 return range ? characterCount(*range, behaviors) : 0;
1002}
1003
1004VisiblePosition visiblePositionForPositionWithOffset(const VisiblePosition& position, int offset)
1005{
1006 RefPtr<ContainerNode> root;
1007 unsigned startIndex = indexForVisiblePosition(position, root);
1008 if (!root)
1009 return { };
1010
1011 return visiblePositionForIndex(startIndex + offset, root.get());
1012}
1013
1014VisiblePosition visiblePositionForIndex(int index, Node* scope, TextIteratorBehaviors behaviors)
1015{
1016 if (!scope)
1017 return { };
1018 return { makeDeprecatedLegacyPosition(resolveCharacterLocation(makeRangeSelectingNodeContents(*scope), index, behaviors)) };
1019}
1020
1021VisiblePosition visiblePositionForIndexUsingCharacterIterator(Node& node, int index)
1022{
1023 if (index <= 0)
1024 return { firstPositionInOrBeforeNode(&node) };
1025
1026 auto range = makeRangeSelectingNodeContents(node);
1027 CharacterIterator it(range);
1028 if (!it.atEnd())
1029 it.advance(index - 1);
1030
1031 if (!it.atEnd() && it.text().length() == 1 && it.text()[0] == '\n') {
1032 // FIXME: workaround for collapsed range (where only start position is correct) emitted for some emitted newlines.
1033 it.advance(1);
1034 if (!it.atEnd())
1035 return { makeDeprecatedLegacyPosition(it.range().start) };
1036 }
1037
1038 return { makeDeprecatedLegacyPosition((it.atEnd() ? range : it.range()).end), Affinity::Upstream };
1039}
1040
1041// Determines whether two positions are visibly next to each other (first then second)
1042// while ignoring whitespaces and unrendered nodes
1043static bool isVisiblyAdjacent(const Position& first, const Position& second)
1044{
1045 return VisiblePosition(first) == VisiblePosition(second.upstream());
1046}
1047
1048// Determines whether a node is inside a range or visibly starts and ends at the boundaries of the range.
1049// Call this function to determine whether a node is visibly fit inside selectedRange
1050bool isNodeVisiblyContainedWithin(Node& node, const SimpleRange& range)
1051{
1052 if (contains<ComposedTree>(range, node))
1053 return true;
1054
1055 auto startPosition = makeDeprecatedLegacyPosition(range.start);
1056 auto endPosition = makeDeprecatedLegacyPosition(range.end);
1057
1058 bool startIsVisuallySame = visiblePositionBeforeNode(node) == startPosition;
1059 if (startIsVisuallySame && positionInParentAfterNode(node) < endPosition)
1060 return true;
1061
1062 bool endIsVisuallySame = visiblePositionAfterNode(node) == endPosition;
1063 if (endIsVisuallySame && startPosition < positionInParentBeforeNode(node))
1064 return true;
1065
1066 return startIsVisuallySame && endIsVisuallySame;
1067}
1068
1069bool isRenderedAsNonInlineTableImageOrHR(const Node* node)
1070{
1071 if (!node)
1072 return false;
1073 CheckedPtr renderer = node->renderer();
1074 return renderer && !renderer->isInline() && (renderer->isRenderTable() || renderer->isImage() || renderer->isHR());
1075}
1076
1077Element* elementIfEquivalent(const Element& first, Node& second)
1078{
1079 auto* secondElement = dynamicDowncast<Element>(second);
1080 if (secondElement && first.hasTagName(secondElement->tagQName()) && first.hasEquivalentAttributes(*secondElement))
1081 return secondElement;
1082 return nullptr;
1083}
1084
1085bool isNonTableCellHTMLBlockElement(const Node* node)
1086{
1087 return node->hasTagName(listingTag)
1088 || node->hasTagName(olTag)
1089 || node->hasTagName(preTag)
1090 || is<HTMLTableElement>(*node)
1091 || node->hasTagName(ulTag)
1092 || node->hasTagName(xmpTag)
1093 || node->hasTagName(h1Tag)
1094 || node->hasTagName(h2Tag)
1095 || node->hasTagName(h3Tag)
1096 || node->hasTagName(h4Tag)
1097 || node->hasTagName(h5Tag);
1098}
1099
1100Position adjustedSelectionStartForStyleComputation(const VisibleSelection& selection)
1101{
1102 // This function is used by range style computations to avoid bugs like:
1103 // <rdar://problem/4017641> REGRESSION (Mail): you can only bold/unbold a selection starting from end of line once
1104 // It is important to skip certain irrelevant content at the start of the selection, so we do not wind up
1105 // with a spurious "mixed" style.
1106
1107 auto visiblePosition = selection.visibleStart();
1108 if (visiblePosition.isNull())
1109 return { };
1110
1111 // if the selection is a caret, just return the position, since the style
1112 // behind us is relevant
1113 if (selection.isCaret())
1114 return visiblePosition.deepEquivalent();
1115
1116 // if the selection starts just before a paragraph break, skip over it
1117 if (isEndOfParagraph(visiblePosition))
1118 return visiblePosition.next().deepEquivalent().downstream();
1119
1120 // otherwise, make sure to be at the start of the first selected node,
1121 // instead of possibly at the end of the last node before the selection
1122 return visiblePosition.deepEquivalent().downstream();
1123}
1124
1125// FIXME: Should this be deprecated like deprecatedEnclosingBlockFlowElement is?
1126static Element* elementIfBlockFlow(Node& node)
1127{
1128 auto* element = dynamicDowncast<Element>(node);
1129 if (!element)
1130 return nullptr;
1131 auto* renderer = element->renderer();
1132 return renderer && renderer->isRenderBlockFlow() ? element : nullptr;
1133}
1134
1135bool isBlockFlowElement(const Node& node)
1136{
1137 return elementIfBlockFlow(const_cast<Node&>(node));
1138}
1139
1140Element* deprecatedEnclosingBlockFlowElement(Node* node)
1141{
1142 if (!node)
1143 return nullptr;
1144 if (auto* element = elementIfBlockFlow(*node))
1145 return element;
1146 while ((node = node->parentNode())) {
1147 if (auto* element = elementIfBlockFlow(*node))
1148 return element;
1149 if (auto* body = dynamicDowncast<HTMLBodyElement>(*node))
1150 return body;
1151 }
1152 return nullptr;
1153}
1154
1155static inline bool caretRendersInsideNode(const Node& node)
1156{
1157 return !isRenderedTable(&node) && !editingIgnoresContent(node);
1158}
1159
1160RenderBlock* rendererForCaretPainting(const Node* node)
1161{
1162 if (!node)
1163 return nullptr;
1164
1165 SUPPRESS_UNCHECKED_LOCAL[[clang::suppress]] auto* renderer = node->renderer();
1166 if (!renderer)
1167 return nullptr;
1168
1169 // If caretNode is a block and caret is inside it, then caret should be painted by that block.
1170 if (SUPPRESS_UNCHECKED_LOCAL[[clang::suppress]] auto* blockFlow = dynamicDowncast<RenderBlockFlow>(*renderer)) {
1171 if (caretRendersInsideNode(*node))
1172 return blockFlow;
1173 }
1174 return renderer->containingBlock();
1175}
1176
1177LayoutRect localCaretRectInRendererForCaretPainting(const VisiblePosition& caretPosition, RenderBlock*& caretPainter)
1178{
1179 if (caretPosition.isNull())
1180 return LayoutRect();
1181 ASSERT(caretPosition.deepEquivalent().deprecatedNode()->renderer())((void)0);
1182 auto [localRect, renderer] = caretPosition.localCaretRect();
1183 return localCaretRectInRendererForRect(localRect, caretPosition.deepEquivalent().deprecatedNode(), renderer.get(), caretPainter);
1184}
1185
1186LayoutRect localCaretRectInRendererForRect(LayoutRect& localRect, Node* node, RenderObject* renderer, RenderBlock*& caretPainter)
1187{
1188 // Get the renderer that will be responsible for painting the caret
1189 // (which is either the renderer we just found, or one of its containers).
1190 caretPainter = rendererForCaretPainting(node);
1191
1192 // Compute an offset between the renderer and the caretPainter.
1193 while (renderer != caretPainter) {
1194 CheckedPtr containerObject = renderer->container();
1195 if (!containerObject)
1196 return LayoutRect();
1197 localRect.move(renderer->offsetFromContainer(*containerObject, localRect.location()));
1198 renderer = containerObject.get();
1199 }
1200
1201 return localRect;
1202}
1203
1204IntRect absoluteBoundsForLocalCaretRect(RenderBlock* rendererForCaretPainting, const LayoutRect& rect, bool* insideFixed)
1205{
1206 if (insideFixed)
1207 *insideFixed = false;
1208
1209 if (!rendererForCaretPainting || rect.isEmpty())
1210 return IntRect();
1211
1212 LayoutRect localRect(rect);
1213 rendererForCaretPainting->flipForWritingMode(localRect);
1214 return rendererForCaretPainting->localToAbsoluteQuad(FloatRect(localRect), UseTransforms, insideFixed).enclosingBoundingBox();
1215}
1216
1217HashSet<Ref<HTMLImageElement>> visibleImageElementsInRangeWithNonLoadedImages(const SimpleRange& range)
1218{
1219 HashSet<Ref<HTMLImageElement>> result;
1220 for (TextIterator iterator(range); !iterator.atEnd(); iterator.advance()) {
1221 RefPtr imageElement = dynamicDowncast<HTMLImageElement>(iterator.node());
1222 if (!imageElement)
1223 continue;
1224
1225 auto* cachedImage = imageElement->cachedImage();
Local variable 'cachedImage' is uncounted and unsafe
1226 if (cachedImage && cachedImage->isLoading())
1227 result.add(imageElement.releaseNonNull());
1228 }
1229 return result;
1230}
1231
1232enum class RangeEndpointsToAdjust : uint8_t {
1233 Start = 1 << 0,
1234 End = 1 << 1,
1235};
1236
1237static std::optional<unsigned> visualDistanceOnSameLine(const RenderedPosition& first, const RenderedPosition& second)
1238{
1239 if (first.isNull() || second.isNull())
1240 return std::nullopt;
1241
1242 if (first.box() == second.box())
1243 return std::max(first.offset(), second.offset()) - std::min(first.offset(), second.offset());
1244
1245 enum class VisualBoundary : bool { Left, Right };
1246 auto distanceFromOffsetToVisualBoundary = [](const RenderedPosition& position, VisualBoundary boundary) {
1247 auto box = position.box();
1248 auto offset = position.offset();
1249 return (boundary == VisualBoundary::Left) == (box->direction() == TextDirection::LTR)
1250 ? std::max(box->minimumCaretOffset(), offset) - box->minimumCaretOffset()
1251 : std::max(box->maximumCaretOffset(), offset) - offset;
1252 };
1253
1254 unsigned distance = 0;
1255 bool foundFirst = false;
1256 bool foundSecond = false;
1257 for (auto box = first.lineBox()->lineLeftmostLeafBox(); box; box = box->nextLineRightwardOnLine()) {
1258 if (box == first.box()) {
1259 distance += distanceFromOffsetToVisualBoundary(first, foundSecond ? VisualBoundary::Left : VisualBoundary::Right);
1260 foundFirst = true;
1261 } else if (box == second.box()) {
1262 distance += distanceFromOffsetToVisualBoundary(second, foundFirst ? VisualBoundary::Left : VisualBoundary::Right);
1263 foundSecond = true;
1264 } else if (foundFirst || foundSecond)
1265 distance += box->maximumCaretOffset() - box->minimumCaretOffset();
1266
1267 if (foundFirst && foundSecond)
1268 return distance;
1269 }
1270
1271 return std::nullopt;
1272}
1273
1274static std::optional<BoundaryPoint> findBidiBoundary(const RenderedPosition& position, unsigned bidiLevel, SelectionExtentMovement movement, TextDirection selectionDirection)
1275{
1276 auto leftBoundary = position.leftBoundaryOfBidiRun(bidiLevel);
1277 auto rightBoundary = position.rightBoundaryOfBidiRun(bidiLevel);
1278
1279 bool moveLeft = [&] {
1280 switch (movement) {
1281 case SelectionExtentMovement::Left:
1282 return true;
1283 case SelectionExtentMovement::Right:
1284 return false;
1285 case SelectionExtentMovement::Closest: {
1286 auto distanceToLeft = visualDistanceOnSameLine(position, leftBoundary);
1287 if (!distanceToLeft)
1288 return false;
1289
1290 auto distanceToRight = visualDistanceOnSameLine(position, rightBoundary);
1291 if (!distanceToRight)
1292 return true;
1293
1294 return *distanceToLeft < *distanceToRight;
1295 }
1296 }
1297 ASSERT_NOT_REACHED()((void)0);
1298 return false;
1299 }();
1300 // This looks unintuitive, but is necessary to ensure that the boundary is moved
1301 // (visually) to the left or right, respectively, in both LTR and RTL paragraphs.
1302 return (position.box()->direction() == selectionDirection) == moveLeft ? leftBoundary.boundaryPoint() : rightBoundary.boundaryPoint();
1303}
1304
1305enum class BoxIterationDirection : bool { SameAsLine, OppositeOfLine };
1306static InlineIterator::LeafBoxIterator advanceInDirection(InlineIterator::LeafBoxIterator box, TextDirection direction, BoxIterationDirection iterationDirection)
1307{
1308 bool shouldMoveRight = (iterationDirection == BoxIterationDirection::SameAsLine) == (direction == TextDirection::LTR);
1309 return shouldMoveRight ? box->nextLineRightwardOnLine() : box->nextLineLeftwardOnLine();
1310}
1311
1312static void forEachRenderedBoxBetween(const RenderedPosition& first, const RenderedPosition& second, NOESCAPE[[clang::noescape]] const Function<IterationStatus(InlineIterator::LeafBoxIterator)>& callback)
1313{
1314 if (first.isNull()) {
1315 ASSERT_NOT_REACHED()((void)0);
1316 return;
1317 }
1318
1319 if (second.isNull()) {
1320 ASSERT_NOT_REACHED()((void)0);
1321 return;
1322 }
1323
1324 if (first.lineBox().atEnd()) {
1325 ASSERT_NOT_REACHED()((void)0);
1326 return;
1327 }
1328
1329 if (first.box() == second.box()) {
1330 callback(first.box());
1331 return;
1332 }
1333
1334 bool foundOneEndpoint = false;
1335 for (auto box = first.lineBox()->lineLeftmostLeafBox(); box; box = box->nextLineRightwardOnLine()) {
1336 bool atFirstEndpoint = box == first.box();
1337 bool atSecondEndpoint = box == second.box();
1338 bool atEndpoint = atFirstEndpoint || atSecondEndpoint;
1339 bool atLastEndpoint = atEndpoint && foundOneEndpoint;
1340 if (!atEndpoint && !foundOneEndpoint)
1341 continue;
1342
1343 foundOneEndpoint = true;
1344
1345 bool shouldSkipBox = [&] {
1346 if (!atEndpoint)
1347 return false;
1348
1349 auto& position = (atFirstEndpoint ? first : second);
1350 return atLastEndpoint ? position.atLeftmostOffsetInBox() : position.atRightmostOffsetInBox();
1351 }();
1352
1353 if (shouldSkipBox)
1354 continue;
1355
1356 if (callback(box) == IterationStatus::Done)
1357 return;
1358
1359 if (atLastEndpoint)
1360 return;
1361 }
1362}
1363
1364PositionRange positionsForRange(const SimpleRange& range)
1365{
1366 return {
1367 makeContainerOffsetPosition(range.start).downstream(),
1368 makeContainerOffsetPosition(range.end).upstream()
1369 };
1370}
1371
1372static InlineIterator::LeafBoxIterator boxWithMinimumBidiLevelBetween(const RenderedPosition& start, const RenderedPosition& end)
1373{
1374 InlineIterator::LeafBoxIterator result;
1375 forEachRenderedBoxBetween(start, end, [&](auto box) {
1376 if (!result || box->bidiLevel() < result->bidiLevel())
1377 result = box;
1378 return IterationStatus::Continue;
1379 });
1380 return result;
1381}
1382
1383TextDirection primaryDirectionForSingleLineRange(const Position& start, const Position& end)
1384{
1385 ASSERT(inSameLine(start, end))((void)0);
1386
1387 RenderedPosition renderedStart { start, Affinity::Downstream };
1388 RenderedPosition renderedEnd { end, Affinity::Upstream };
1389
1390 auto direction = start.primaryDirection();
1391 if (renderedStart.isNull() || renderedEnd.isNull())
1392 return direction;
1393
1394 if (auto box = boxWithMinimumBidiLevelBetween(renderedStart, renderedEnd))
1395 return box->direction();
1396
1397 return direction;
1398}
1399
1400static std::optional<SimpleRange> makeVisuallyContiguousIfNeeded(const SimpleRange& range, OptionSet<RangeEndpointsToAdjust> endpoints, SelectionExtentMovement movement)
1401{
1402 if (range.collapsed())
1403 return std::nullopt;
1404
1405 auto [start, end] = positionsForRange(range);
1406 auto firstLineDirection = TextDirection::LTR;
1407 RenderedPosition renderedStart { start };
1408 if (renderedStart.isNull() || renderedStart.lineBox().atEnd())
1409 return std::nullopt;
1410
1411 auto lastLineDirection = TextDirection::LTR;
1412 RenderedPosition renderedEnd { end };
1413 if (renderedEnd.isNull() || renderedEnd.lineBox().atEnd())
1414 return std::nullopt;
1415
1416 if (renderedStart.box() == renderedEnd.box())
1417 return std::nullopt;
1418
1419 auto bidiLevelAtStart = renderedStart.box()->bidiLevel();
1420 auto bidiLevelAtEnd = renderedEnd.box()->bidiLevel();
1421 auto targetBidiLevelAtStart = bidiLevelAtStart;
1422 auto targetBidiLevelAtEnd = bidiLevelAtEnd;
1423 std::optional<BoundaryPoint> adjustedStart;
1424 std::optional<BoundaryPoint> adjustedEnd;
1425 if (inSameLine(start, end)) {
1426 if (auto box = boxWithMinimumBidiLevelBetween(renderedStart, renderedEnd)) {
1427 targetBidiLevelAtStart = box->bidiLevel();
1428 targetBidiLevelAtEnd = targetBidiLevelAtStart;
1429 firstLineDirection = box->direction();
1430 lastLineDirection = firstLineDirection;
1431 }
1432 } else {
1433 bool firstLineOnlyContainsSelectedTextInOppositeDirection = true;
1434 firstLineDirection = start.primaryDirection();
1435 std::optional<BoundaryPoint> firstPositionForSelectedTextInOppositeDirectionOnFirstLine;
1436 for (auto box = renderedStart.box(); box; box = advanceInDirection(box, firstLineDirection, BoxIterationDirection::SameAsLine)) {
1437 targetBidiLevelAtStart = std::min(targetBidiLevelAtStart, box->bidiLevel());
1438
1439 if (box->direction() == firstLineDirection)
1440 firstLineOnlyContainsSelectedTextInOppositeDirection = false;
1441
1442 if (!firstLineOnlyContainsSelectedTextInOppositeDirection)
1443 continue;
1444
1445 if (RefPtr node = box->renderer().node()) {
1446 if (box->isText())
1447 firstPositionForSelectedTextInOppositeDirectionOnFirstLine.emplace(node.releaseNonNull(), box->minimumCaretOffset());
1448 else
1449 firstPositionForSelectedTextInOppositeDirectionOnFirstLine = makeBoundaryPointBeforeNode(node.releaseNonNull());
1450 }
1451 }
1452
1453 if (firstLineOnlyContainsSelectedTextInOppositeDirection)
1454 adjustedStart = WTF::move(firstPositionForSelectedTextInOppositeDirectionOnFirstLine);
1455
1456 bool lastLineOnlyContainsSelectedTextInOppositeDirection = true;
1457 lastLineDirection = end.primaryDirection();
1458 std::optional<BoundaryPoint> lastPositionForSelectedTextInOppositeDirectionOnLastLine;
1459 for (auto box = renderedEnd.box(); box; box = advanceInDirection(box, lastLineDirection, BoxIterationDirection::OppositeOfLine)) {
1460 targetBidiLevelAtEnd = std::min(targetBidiLevelAtEnd, box->bidiLevel());
1461
1462 if (box->direction() == lastLineDirection)
1463 lastLineOnlyContainsSelectedTextInOppositeDirection = false;
1464
1465 if (!lastLineOnlyContainsSelectedTextInOppositeDirection)
1466 continue;
1467
1468 if (RefPtr node = box->renderer().node()) {
1469 if (box->isText())
1470 lastPositionForSelectedTextInOppositeDirectionOnLastLine.emplace(node.releaseNonNull(), box->maximumCaretOffset());
1471 else
1472 lastPositionForSelectedTextInOppositeDirectionOnLastLine = makeBoundaryPointAfterNode(node.releaseNonNull());
1473 }
1474 }
1475
1476 if (lastLineOnlyContainsSelectedTextInOppositeDirection)
1477 adjustedEnd = WTF::move(lastPositionForSelectedTextInOppositeDirectionOnLastLine);
1478 }
1479
1480 if (!adjustedStart && bidiLevelAtStart > targetBidiLevelAtStart && start != logicalStartOfLine(start) && endpoints.contains(RangeEndpointsToAdjust::Start))
1481 adjustedStart = findBidiBoundary(renderedStart, targetBidiLevelAtStart + 1, movement, firstLineDirection);
1482
1483 if (!adjustedEnd && bidiLevelAtEnd > targetBidiLevelAtEnd && end != logicalEndOfLine(end) && endpoints.contains(RangeEndpointsToAdjust::End))
1484 adjustedEnd = findBidiBoundary(renderedEnd, targetBidiLevelAtEnd + 1, movement, lastLineDirection);
1485
1486 if (!adjustedStart && !adjustedEnd)
1487 return std::nullopt;
1488
1489 auto adjustedRange = range;
1490 if (adjustedStart)
1491 adjustedRange.start = WTF::move(*adjustedStart);
1492
1493 if (adjustedEnd)
1494 adjustedRange.end = WTF::move(*adjustedEnd);
1495
1496 if (!is_lt(treeOrder(adjustedRange.start, adjustedRange.end)))
1497 return std::nullopt;
1498
1499 return adjustedRange;
1500}
1501
1502SimpleRange adjustToVisuallyContiguousRange(const SimpleRange& range)
1503{
1504 return makeVisuallyContiguousIfNeeded(range, {
1505 RangeEndpointsToAdjust::Start,
1506 RangeEndpointsToAdjust::End
1507 }, SelectionExtentMovement::Closest).value_or(range);
1508}
1509
1510EnclosingLayerInfomation computeEnclosingLayer(const SimpleRange& range)
1511{
1512 auto [start, end] = positionsForRange(range);
1513
1514 if (start.isOrphan() || end.isOrphan())
1515 return { };
1516
1517 if (!isEditablePosition(start) && range.collapsed())
1518 return { };
1519
1520 auto findEnclosingLayer = [](const Position& position) -> RenderLayer* {
1521 RefPtr container = position.containerNode();
1522 if (!container)
1523 return nullptr;
1524
1525 CheckedPtr renderer = container->renderer();
1526 if (!renderer)
1527 return nullptr;
1528
1529 return renderer->enclosingLayer();
1530 };
1531
1532 auto [startLayer, endLayer] = [&] -> std::pair<CheckedPtr<RenderLayer>, CheckedPtr<RenderLayer>> {
1533 if (RefPtr container = start.containerNode(); container && ImageOverlay::isInsideOverlay(*container)) {
1534 RefPtr host = container->shadowHost();
1535 if (!host) {
1536 ASSERT_NOT_REACHED()((void)0);
1537 return { };
1538 }
1539
1540 CheckedPtr renderer = host->renderer();
1541 if (!renderer)
1542 return { };
1543
1544 CheckedPtr enclosingLayer = renderer->enclosingLayer();
1545 return { enclosingLayer, enclosingLayer };
1546 }
1547
1548 return { findEnclosingLayer(start), findEnclosingLayer(end) };
1549 }();
1550
1551 if (!startLayer)
1552 return { };
1553
1554 if (!endLayer)
1555 return { };
1556
1557 for (CheckedPtr layer = startLayer->commonAncestorWithLayer(*endLayer); layer; layer = layer->enclosingContainingBlockLayer(CrossFrameBoundaries::Yes)) {
1558 if (!layer->isComposited())
1559 continue;
1560
1561 RefPtr graphicsLayer = [layer] -> RefPtr<GraphicsLayer> {
1562 auto* backing = layer->backing();
1563 if (RefPtr scrolledContentsLayer = backing->scrolledContentsLayer())
1564 return scrolledContentsLayer;
1565
1566 if (RefPtr foregroundLayer = backing->foregroundLayer())
1567 return foregroundLayer;
1568
1569 if (backing->isFrameLayerWithTiledBacking())
1570 return backing->parentForSublayers();
1571
1572 return backing->graphicsLayer();
1573 }();
1574
1575 if (!graphicsLayer)
1576 continue;
1577
1578 auto identifier = graphicsLayer->layerIDIgnoringStructuralLayer();
1579 if (!identifier)
1580 continue;
1581
1582 return { WTF::move(startLayer), WTF::move(endLayer), WTF::move(layer), WTF::move(graphicsLayer), WTF::move(identifier) };
1583 }
1584 return { };
1585}
1586
1587void adjustVisibleExtentPreservingVisualContiguity(const VisiblePosition& base, VisiblePosition& extent, SelectionExtentMovement movement)
1588{
1589 auto start = makeBoundaryPoint(base.deepEquivalent());
1590 auto end = makeBoundaryPoint(extent.deepEquivalent());
1591 if (!start || !end)
1592 return;
1593
1594 OptionSet<RangeEndpointsToAdjust> endpoints;
1595 auto baseExtentOrder = treeOrder<ComposedTree>(*start, *end);
1596 bool startIsMoving = is_gt(baseExtentOrder);
1597 bool endIsMoving = is_lt(baseExtentOrder);
1598 if (startIsMoving) {
1599 std::swap(start, end);
1600 endpoints.add(RangeEndpointsToAdjust::Start);
1601 } else if (endIsMoving)
1602 endpoints.add(RangeEndpointsToAdjust::End);
1603 else
1604 return;
1605
1606 auto adjustedRange = makeVisuallyContiguousIfNeeded({ WTF::move(*start), WTF::move(*end) }, endpoints, movement);
1607 if (!adjustedRange)
1608 return;
1609
1610 extent = { makeContainerOffsetPosition(startIsMoving ? adjustedRange->start : adjustedRange->end) };
1611}
1612
1613bool crossesBidiTextBoundaryInSameLine(const VisiblePosition& position, const VisiblePosition& other)
1614{
1615 if (!inSameLine(position, other))
1616 return false;
1617
1618 std::optional<unsigned char> currentLevel;
1619 bool foundDifferentBidiLevel = false;
1620 forEachRenderedBoxBetween(RenderedPosition { position }, RenderedPosition { other }, [&](auto box) {
1621 auto bidiLevel = box->bidiLevel();
1622 if (!currentLevel) {
1623 currentLevel = bidiLevel;
1624 return IterationStatus::Continue;
1625 }
1626
1627 if (currentLevel == bidiLevel)
1628 return IterationStatus::Continue;
1629
1630 foundDifferentBidiLevel = true;
1631 return IterationStatus::Done;
1632 });
1633
1634 return foundDifferentBidiLevel;
1635}
1636
1637} // namespace WebCore