Bug Summary

File:Volumes/Data/worker/Apple-iOS-26-Safer-CPP-Checks-EWS/build/Source/WebCore/editing/cocoa/EditingHTMLConverter.mm
Warning:line 135, column 22
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 EditingHTMLConverter.mm -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/EditingHTMLConverter.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/1576255350738772533/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 OBJC_OLD_DISPATCH_PROTOTYPES=0 -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 -Wno-implicit-atomic-properties -Wno-objc-interface-ivars -Wno-arc-repeated-use-of-weak -Wimplicit-retain-self -Wnon-virtual-dtor -Wno-overloaded-virtual -Wno-exit-time-destructors -Wduplicate-method-match -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-selector -Wno-strict-selector-match -Wundeclared-selector -Wdeprecated-implementations -Wno-c++11-extensions -Wno-implicit-fallthrough -Wprotocol -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 -fobjc-runtime=ios-26.2.0 -fobjc-weak -fobjc-exceptions -fexceptions -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/EditingHTMLConverter.plist -x objective-c++ /Volumes/Data/worker/Apple-iOS-26-Safer-CPP-Checks-EWS/build/Source/WebCore/editing/cocoa/EditingHTMLConverter.mm
1/*
2 * Copyright (C) 2011-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''
14 * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
15 * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
16 * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL APPLE INC. OR ITS CONTRIBUTORS
17 * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
18 * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
19 * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
20 * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
21 * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
22 * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF
23 * THE POSSIBILITY OF SUCH DAMAGE.
24 */
25
26#import "config.h"
27#import "EditingHTMLConverter.h"
28
29#import "ArchiveResource.h"
30#import "BoundaryPointInlines.h"
31#import "CSSColorValue.h"
32#import "CSSComputedStyleDeclaration.h"
33#import "CSSPrimitiveValue.h"
34#import "CSSSerializationContext.h"
35#import "CharacterData.h"
36#import "ColorCocoa.h"
37#import "CommonAtomStrings.h"
38#import "ComposedTreeIterator.h"
39#import "ContainerNodeInlines.h"
40#import "Document.h"
41#import "DocumentLoader.h"
42#import "Editing.h"
43#import "ElementChildIteratorInlines.h"
44#import "ElementInlines.h"
45#import "ElementRareData.h"
46#import "ElementTraversal.h"
47#import "FontAttributes.h"
48#import "FontCascade.h"
49#import "FrameDestructionObserverInlines.h"
50#import "FrameLoader.h"
51#import "HTMLAttachmentElement.h"
52#import "HTMLConverter.h"
53#import "HTMLElement.h"
54#import "HTMLImageElement.h"
55#import "HTMLOListElement.h"
56#import "LoaderNSURLExtras.h"
57#import "LocalFrame.h"
58#import "LocalizedStrings.h"
59#import "NodeName.h"
60#import "RenderImage.h"
61#import "RenderObjectStyle.h"
62#import "RenderStyle+GettersInlines.h"
63#import "RenderText.h"
64#import "StyleExtractor.h"
65#import "StyleProperties.h"
66#import "StyledElement.h"
67#import "TextIterator.h"
68#import "VisibleSelection.h"
69#import "WebContentReader.h"
70#import <pal/spi/cocoa/NSAttributedStringSPI.h>
71#import <wtf/TZoneMallocInlines.h>
72
73#if ENABLE(MULTI_REPRESENTATION_HEIC)(defined ENABLE_MULTI_REPRESENTATION_HEIC && ENABLE_MULTI_REPRESENTATION_HEIC
)
74#import "PlatformNSAdaptiveImageGlyph.h"
75#endif
76
77#if PLATFORM(IOS_FAMILY)(defined 1 && 1)
78#import "UIFoundationSoftLink.h"
79#import <pal/ios/UIKitSoftLink.h>
80#endif
81
82#if ENABLE(WRITING_TOOLS)(defined 1 && 1)
83NSAttributedStringKey const WTWritingToolsPreservedAttributeName = @"WTWritingToolsPreserved";
84#endif
85
86namespace WebCore {
87
88template<typename Data>
89using ElementCache = WeakHashMap<Element, Data, WeakPtrImplWithEventTargetData>;
90
91static String preferredFilenameForElement(const HTMLImageElement& element)
92{
93 if (RefPtr attachment = element.attachmentElement()) {
94 if (auto title = attachment->attachmentTitle(); !title.isEmpty())
95 return title;
96 }
97
98 auto altText = element.altText();
99
100 auto urlString = element.imageSourceURL();
101
102 auto suggestedName = [&] -> String {
103 Ref document = element.document();
104 RetainPtr url = document->completeURL(urlString).createNSURL();
105 if (!url)
106 url = [NSURL _web_URLWithString:[urlString.createNSString() stringByTrimmingCharactersInSet:[NSCharacterSet whitespaceAndNewlineCharacterSet]] relativeToURL:nilnullptr];
107
108 RefPtr frame = document->frame();
109 if (frame->loader().frameHasLoaded()) {
110 RefPtr dataSource = frame->loader().documentLoader();
111 if (RefPtr resource = dataSource->subresource(url.get())) {
112 auto& mimeType = resource->mimeType();
113
114 if (!altText.isEmpty())
115 return suggestedFilenameWithMIMEType(url.get(), mimeType, altText);
116
117 return suggestedFilenameWithMIMEType(url.get(), mimeType);
118 }
119 }
120
121 return { };
122 }();
123
124 if (!suggestedName.isEmpty())
125 return suggestedName;
126
127 if (!altText.isEmpty())
128 return altText;
129
130 return copyImageUnknownFileLabel();
131}
132
133static RetainPtr<NSFileWrapper> fileWrapperForElement(const HTMLImageElement& element)
134{
135 if (CachedImage* cachedImage = element.cachedImage()) {
Local variable 'cachedImage' is uncounted and unsafe
136 if (RefPtr sharedBuffer = cachedImage->resourceBuffer()) {
137 RetainPtr wrapper = adoptNS([[NSFileWrapper alloc] initRegularFileWithContents:sharedBuffer->makeContiguous()->createNSData().get()]);
138 [wrapper setPreferredFilename:preferredFilenameForElement(element).createNSString().get()];
139 return wrapper;
140 }
141 }
142
143 CheckedPtr renderer = element.renderer();
144 if (CheckedPtr renderImage = dynamicDowncast<RenderImage>(renderer)) {
145 RefPtr image = renderImage->cachedImage();
146 if (image && !image->errorOccurred()) {
147 RetainPtr<NSFileWrapper> wrapper = adoptNS([[NSFileWrapper alloc] initRegularFileWithContents:(__bridge NSData *)image->imageForRenderer(renderer)->adapter().tiffRepresentation()]);
148 [wrapper setPreferredFilename:@"image.tiff"];
149 return wrapper;
150 }
151 }
152
153 return nilnullptr;
154}
155
156static RetainPtr<NSFileWrapper> fileWrapperForElement(const HTMLAttachmentElement& element)
157{
158 auto identifier = element.uniqueIdentifier();
159
160 RetainPtr data = [identifier.createNSString() dataUsingEncoding:NSUTF8StringEncoding];
161 if (!data)
162 return nilnullptr;
163
164 // Use a filename prefixed with a sentinel value to indicate that the data is corresponding
165 // to an existing HTMLAttachmentElement.
166
167 RetainPtr wrapper = adoptNS([[NSFileWrapper alloc] initRegularFileWithContents:data.get()]);
168 [wrapper setPreferredFilename:makeString(WebContentReader::placeholderAttachmentFilenamePrefix, identifier).createNSString().get()];
169 return wrapper;
170}
171
172static RetainPtr<NSAttributedString> attributedStringWithAttachmentForFileWrapper(NSFileWrapper *fileWrapper)
173{
174 if (!fileWrapper)
175 return adoptNS([[NSAttributedString alloc] initWithString:@" "]).autorelease();
176
177 RetainPtr attachment = adoptNS([[PlatformNSTextAttachmentgetNSTextAttachmentClassSingleton() alloc] initWithFileWrapper:fileWrapper]);
178 return [NSAttributedString attributedStringWithAttachment:attachment.get()];
179}
180
181static RetainPtr<NSAttributedString> attributedStringWithAttachmentForElement(const HTMLImageElement& element)
182{
183#if ENABLE(MULTI_REPRESENTATION_HEIC)(defined ENABLE_MULTI_REPRESENTATION_HEIC && ENABLE_MULTI_REPRESENTATION_HEIC
)
184 if (element.isMultiRepresentationHEIC()) {
185 if (RefPtr image = element.image()) {
186 if (NSAdaptiveImageGlyph *attachment = image->adapter().multiRepresentationHEIC()) {
187 RetainPtr attachmentString = adoptNS([[NSString alloc] initWithFormat:@"%C", static_cast<unichar>(NSAttachmentCharacter)]);
188 RetainPtr attributedString = adoptNS([[NSMutableAttributedString alloc] initWithString:attachmentString.get()]);
189 [attributedString addAttribute:NSAdaptiveImageGlyphAttributeName value:attachment range:NSMakeRange(0, 1)];
190 return attributedString;
191 }
192 }
193 }
194#endif
195
196 RetainPtr fileWrapper = fileWrapperForElement(element);
197 return attributedStringWithAttachmentForFileWrapper(fileWrapper.get());
198}
199
200static RetainPtr<NSAttributedString> attributedStringWithAttachmentForElement(const HTMLAttachmentElement& element)
201{
202 RetainPtr fileWrapper = fileWrapperForElement(element);
203 return attributedStringWithAttachmentForFileWrapper(fileWrapper.get());
204}
205
206#if ENABLE(WRITING_TOOLS)(defined 1 && 1)
207static bool elementQualifiesForWritingToolsPreservation(Element* element)
208{
209 // If the element is a mail blockquote, it should be preserved after a Writing Tools composition.
210 if (isMailBlockquote(*element))
211 return true;
212
213 // If the element is a tab span node, it is a tab character with `whitespace:pre`, and need not be preserved.
214 if (tabSpanNode(element))
215 return false;
216
217 // If the element has no renderer, it can't have `whitespace:pre` so it need not be preserved.
218 CheckedPtr renderer = element->renderer();
219 if (!renderer)
220 return false;
221
222 // If the element has `white-space:pre` (except for the aforementioned exceptions), it should be preserved.
223 // `white-space:pre` is a shorthand for white-space-collapse:preserve && text-wrap-mode::no-wrap.
224 if (renderer->style().whiteSpaceCollapse() == WhiteSpaceCollapse::Preserve
225 && renderer->style().textWrapMode() == TextWrapMode::NoWrap)
226 return true;
227
228 // Otherwise, it need not be preserved.
229 return false;
230}
231
232static bool hasAncestorQualifyingForWritingToolsPreservation(Element* ancestor, ElementCache<bool>& cache)
233{
234 if (!ancestor)
235 return false;
236
237 auto entry = cache.find(*ancestor);
238 if (entry == cache.end()) {
239 auto result = elementQualifiesForWritingToolsPreservation(ancestor) || hasAncestorQualifyingForWritingToolsPreservation(protect(ancestor->parentElement()).get(), cache);
240
241 cache.set(*ancestor, result);
242 return result;
243 }
244
245 return entry->value;
246}
247#endif // ENABLE(WRITING_TOOLS)
248
249static RefPtr<Element> enclosingElement(const Node& node, ElementCache<RefPtr<Element>>& cache, Function<bool(Element *)>&& predicate)
250{
251 Vector<Ref<Element>> ancestors;
252 RefPtr<Element> result;
253 for (RefPtr ancestor = node.parentElementInComposedTree(); ancestor; ancestor = ancestor->parentElementInComposedTree()) {
254 if (predicate(ancestor.get())) {
255 result = ancestor.get();
256 break;
257 }
258
259 auto entry = cache.find(*ancestor);
260 if (entry != cache.end()) {
261 result = entry->value;
262 break;
263 }
264
265 ancestors.append(*ancestor);
266 }
267
268 for (auto& ancestor : ancestors)
269 cache.add(ancestor.get(), result);
270
271 return result;
272}
273
274static RefPtr<Element> enclosingLinkElement(const Node& node, ElementCache<RefPtr<Element>>& cache)
275{
276 return enclosingElement(node, cache, [](auto* element) {
277 return element->isLink();
278 });
279}
280
281static RefPtr<Element> enclosingListElement(const Node& node, ElementCache<RefPtr<Element>>& cache)
282{
283 return enclosingElement(node, cache, [](auto* element) {
284 return element->hasTagName(HTMLNames::olTag) || element->hasTagName(HTMLNames::ulTag);
285 });
286}
287
288// The `enclosingListCache` associates an element with its nearest enclosing list ancestor that (ol or ul)
289// and the `textListsForListElements` associates an enclosing list element with its array of text lists.
290static void associateElementWithTextLists(Element* element, ElementCache<RefPtr<Element>>& enclosingListCache, ElementCache<RetainPtr<NSArray<NSTextList *>>>& textListsForListElements)
291{
292 // Base case #1:
293 // Eventually, an element will be null since there will be no more list elements in the ancestor hierarchy to find.
294 // Therefore, associate the element with an empty array of text lists.
295 if (!element)
296 return;
297
298 // Base case #2:
299 // If the element is already associated with an array of text lists, no work needs to be done.
300 if (textListsForListElements.contains(*element))
301 return;
302
303 // Recursive case:
304 // The NSAttributedString API contract requires that the array of NSTextList's start at the outermost list
305 // and end at the innermost list. In the TextIterator traversal, for a given element, it's "enclosing list element"
306 // will be the innermost list, by definition.
307 //
308 // For nested lists, things get a bit complicated. The antecedents of the inner most text list correspond to the
309 // associated text lists for each enclosing list element of the one before, recursively. Consequently, memoization
310 // is used to acquire all antecedents of the element's text list, and then the final array of text lists for the
311 // element is the array of the text list antecedents followed by the element's own text list itself.
312
313 RetainPtr ancestors = adoptNS([[NSArray alloc] init]);
314
315 if (RefPtr parent = enclosingListElement(*element, enclosingListCache)) {
316 associateElementWithTextLists(parent.get(), enclosingListCache, textListsForListElements);
317 ancestors = textListsForListElements.get(*parent);
318 }
319
320 TextList list;
321 list.ordered = element->hasTagName(HTMLNames::olTag);
322
323 if (CheckedPtr renderer = element->renderer())
324 list.styleType = renderer->style().listStyleType();
325
326 if (RefPtr olElement = dynamicDowncast<HTMLOListElement>(element))
327 list.startingItemNumber = olElement->start();
328
329 RetainPtr nsTextList = list.createTextList();
330 RetainPtr allLists = [ancestors arrayByAddingObject:nsTextList.get()];
331 textListsForListElements.set(*element, allLists);
332}
333
334// FIXME: Encapsulate all these parameters into a type for readability and maintainability.
335static void updateAttributes(const Node* node, const RenderStyle& style, OptionSet<IncludedElement> includedElements, ElementCache<bool>& elementQualifiesForWritingToolsPreservationCache, ElementCache<RefPtr<Element>>& enclosingLinkCache, ElementCache<RefPtr<Element>>& enclosingListCache, NSMutableDictionary<NSAttributedStringKey, id> *attributes, ElementCache<RetainPtr<NSArray<NSTextList *>>>& textListsForListElements)
336{
337#if ENABLE(WRITING_TOOLS)(defined 1 && 1)
338 if (includedElements.contains(IncludedElement::PreservedContent)) {
339 if (hasAncestorQualifyingForWritingToolsPreservation(protect(node->parentElement()).get(), elementQualifiesForWritingToolsPreservationCache))
340 [attributes setObject:@(1) forKey:WTWritingToolsPreservedAttributeName];
341 else
342 [attributes removeObjectForKey:WTWritingToolsPreservedAttributeName];
343 }
344#else
345 UNUSED_PARAM(node)(void)node;
346 UNUSED_PARAM(includedElements)(void)includedElements;
347 UNUSED_PARAM(elementQualifiesForWritingToolsPreservationCache)(void)elementQualifiesForWritingToolsPreservationCache;
348#endif
349
350 if (style.textDecorationLineInEffect().hasUnderline())
351 [attributes setObject:[NSNumber numberWithInteger:NSUnderlineStyleSingle] forKey:NSUnderlineStyleAttributeNamegetNSUnderlineStyleAttributeNameSingleton()];
352 else
353 [attributes removeObjectForKey:NSUnderlineStyleAttributeNamegetNSUnderlineStyleAttributeNameSingleton()];
354
355 if (style.textDecorationLineInEffect().hasLineThrough())
356 [attributes setObject:[NSNumber numberWithInteger:NSUnderlineStyleSingle] forKey:NSStrikethroughStyleAttributeNamegetNSStrikethroughStyleAttributeNameSingleton()];
357 else
358 [attributes removeObjectForKey:NSStrikethroughStyleAttributeNamegetNSStrikethroughStyleAttributeNameSingleton()];
359
360 CheckedRef fontCascade = style.fontCascade();
361 if (auto ctFont = fontCascade->primaryFont().ctFont())
362 [attributes setObject:(__bridge PlatformFontUIFont *)ctFont forKey:NSFontAttributeNamegetNSFontAttributeNameSingleton()];
363 else {
364 auto size = fontCascade->primaryFont().platformData().size();
365#if PLATFORM(IOS_FAMILY)(defined 1 && 1)
366 PlatformFontUIFont *platformFont = [PlatformFontClassPAL::getUIFontClassSingleton() systemFontOfSize:size];
367#else
368 PlatformFontUIFont *platformFont = [[NSFontManager sharedFontManager] convertFont:WebDefaultFont() toSize:size];
369#endif
370 [attributes setObject:platformFont forKey:NSFontAttributeNamegetNSFontAttributeNameSingleton()];
371 }
372
373 auto textAlignment = NSTextAlignmentNatural;
374 switch (style.textAlign()) {
375 case Style::TextAlign::Right:
376 case Style::TextAlign::WebKitRight:
377 textAlignment = NSTextAlignmentRight;
378 break;
379 case Style::TextAlign::Left:
380 case Style::TextAlign::WebKitLeft:
381 textAlignment = NSTextAlignmentLeft;
382 break;
383 case Style::TextAlign::Center:
384 case Style::TextAlign::WebKitCenter:
385 textAlignment = NSTextAlignmentCenter;
386 break;
387 case Style::TextAlign::Justify:
388 textAlignment = NSTextAlignmentJustified;
389 break;
390 case Style::TextAlign::Start:
391 if (style.hasExplicitlySetDirection())
392 textAlignment = style.writingMode().deprecatedIsLeftToRightDirection() ? NSTextAlignmentLeft : NSTextAlignmentRight;
393 break;
394 case Style::TextAlign::End:
395 textAlignment = style.writingMode().deprecatedIsLeftToRightDirection() ? NSTextAlignmentRight : NSTextAlignmentLeft;
396 break;
397 default:
398 ASSERT_NOT_REACHED()((void)0);
399 break;
400 }
401
402 if (textAlignment != NSTextAlignmentNatural) {
403 RetainPtr<NSMutableParagraphStyle> paragraphStyle = adoptNS([(attributes[NSParagraphStyleAttributeNamegetNSParagraphStyleAttributeNameSingleton()] ?: [PlatformNSParagraphStylePAL::getNSParagraphStyleClassSingleton() defaultParagraphStyle]) mutableCopy]);
404 [paragraphStyle setAlignment:textAlignment];
405 [attributes setObject:paragraphStyle.get() forKey:NSParagraphStyleAttributeNamegetNSParagraphStyleAttributeNameSingleton()];
406 }
407
408 auto foregroundColor = style.visitedDependentColorApplyingColorFilter();
409 if (foregroundColor.isVisible())
410 [attributes setObject:cocoaColor(foregroundColor).get() forKey:NSForegroundColorAttributeNamegetNSForegroundColorAttributeNameSingleton()];
411 else
412 [attributes removeObjectForKey:NSForegroundColorAttributeNamegetNSForegroundColorAttributeNameSingleton()];
413
414 auto backgroundColor = style.visitedDependentBackgroundColorApplyingColorFilter();
415 if (backgroundColor.isVisible())
416 [attributes setObject:cocoaColor(backgroundColor).get() forKey:NSBackgroundColorAttributeNamegetNSBackgroundColorAttributeNameSingleton()];
417 else
418 [attributes removeObjectForKey:NSBackgroundColorAttributeNamegetNSBackgroundColorAttributeNameSingleton()];
419
420 auto linkURL = [&] -> URL {
421 RefPtr enclosingLink = enclosingLinkElement(*node, enclosingLinkCache);
422 if (!enclosingLink)
423 return { };
424
425 return enclosingLink->absoluteLinkURL();
426 }();
427
428 if (RetainPtr linkNSURL = linkURL.createNSURL(); linkURL.isEmpty() || !linkNSURL)
429 [attributes removeObjectForKey:NSLinkAttributeNamegetNSLinkAttributeNameSingleton()];
430 else
431 [attributes setObject:linkNSURL.get() forKey:NSLinkAttributeNamegetNSLinkAttributeNameSingleton()];
432
433 if (includedElements.contains(IncludedElement::TextLists)) {
434 if (RefPtr enclosingList = enclosingListElement(*node, enclosingListCache)) {
435 RetainPtr<NSMutableParagraphStyle> paragraphStyle = adoptNS([(attributes[NSParagraphStyleAttributeNamegetNSParagraphStyleAttributeNameSingleton()] ?: [PlatformNSParagraphStylePAL::getNSParagraphStyleClassSingleton() defaultParagraphStyle]) mutableCopy]);
436
437 associateElementWithTextLists(enclosingList.get(), enclosingListCache, textListsForListElements);
438 if (RetainPtr lists = textListsForListElements.get(*enclosingList))
439 [paragraphStyle setTextLists:lists.get()];
440
441 attributes[NSParagraphStyleAttributeNamegetNSParagraphStyleAttributeNameSingleton()] = paragraphStyle.get();
442 } else if (RetainPtr<NSMutableParagraphStyle> paragraphStyle = adoptNS([attributes[NSParagraphStyleAttributeNamegetNSParagraphStyleAttributeNameSingleton()] mutableCopy])) {
443 [paragraphStyle setTextLists:@[ ]];
444 attributes[NSParagraphStyleAttributeNamegetNSParagraphStyleAttributeNameSingleton()] = paragraphStyle.get();
445 }
446 }
447}
448
449// This function uses TextIterator, which makes offsets in its result compatible with HTML editing.
450enum class ReplaceAllNoBreakSpaces : bool { No, Yes };
451static AttributedString editingAttributedStringInternal(const SimpleRange& range, TextIteratorBehaviors behaviors, OptionSet<IncludedElement> includedElements, ReplaceAllNoBreakSpaces replaceAllNoBreakSpaces)
452{
453 ElementCache<RefPtr<Element>> enclosingLinkCache;
454 ElementCache<RefPtr<Element>> enclosingListCache;
455 ElementCache<RetainPtr<NSArray<NSTextList *>>> textListsForListElements;
456 ElementCache<bool> elementQualifiesForWritingToolsPreservationCache;
457
458 RetainPtr string = adoptNS([[NSMutableAttributedString alloc] init]);
459 RetainPtr attributes = adoptNS([[NSMutableDictionary alloc] init]);
460 NSUInteger stringLength = 0;
461 for (TextIterator it { range, behaviors }; !it.atEnd(); it.advance()) {
462 RefPtr node = it.node();
463
464 if (RefPtr imageElement = dynamicDowncast<HTMLImageElement>(node.get()); imageElement && includedElements.contains(IncludedElement::Images)) {
465 RetainPtr attachmentAttributedString = attributedStringWithAttachmentForElement(*imageElement);
466 [string appendAttributedString:attachmentAttributedString.get()];
467 stringLength += [attachmentAttributedString length];
468 }
469
470 if (RefPtr attachmentElement = dynamicDowncast<HTMLAttachmentElement>(node.get()); attachmentElement && includedElements.contains(IncludedElement::Attachments)) {
471 RetainPtr attachmentAttributedString = attributedStringWithAttachmentForElement(*attachmentElement);
472 [string appendAttributedString:attachmentAttributedString.get()];
473 stringLength += [attachmentAttributedString length];
474 }
475
476 auto currentTextLength = it.text().length();
477 if (!currentTextLength)
478 continue;
479
480 // In some cases the text iterator emits text that is not associated with a node.
481 // In those cases, base the style on the container.
482 if (!node)
483 node = it.range().start.container.ptr();
484 CheckedPtr renderer = node->renderer();
485
486 if (renderer)
487 updateAttributes(node.get(), protect(renderer->style()), includedElements, elementQualifiesForWritingToolsPreservationCache, enclosingLinkCache, enclosingListCache, attributes.get(), textListsForListElements);
488 else if (!includedElements.contains(IncludedElement::NonRenderedContent))
489 continue;
490
491 bool replaceNoBreakSpaces = [&] {
492 if (replaceAllNoBreakSpaces == ReplaceAllNoBreakSpaces::Yes)
493 return true;
494
495 return renderer && renderer->style().nbspMode() == NBSPMode::Space;
496 }();
497
498 RetainPtr<NSString> text;
499 if (!replaceNoBreakSpaces)
500 text = it.text().createNSStringWithoutCopying();
501 else
502 text = makeStringByReplacingAll(it.text(), noBreakSpace, ' ').createNSString();
503
504 [string replaceCharactersInRange:NSMakeRange(stringLength, 0) withString:text.get()];
505 [string setAttributes:attributes.get() range:NSMakeRange(stringLength, currentTextLength)];
506 stringLength += currentTextLength;
507 }
508
509 return AttributedString::fromNSAttributedString(WTF::move(string));
510}
511
512AttributedString editingAttributedString(const SimpleRange& range, OptionSet<IncludedElement> includedElements)
513{
514 return editingAttributedStringInternal(range, { }, includedElements, ReplaceAllNoBreakSpaces::No);
515}
516
517AttributedString editingAttributedStringReplacingNoBreakSpace(const SimpleRange& range, TextIteratorBehaviors behaviors, OptionSet<IncludedElement> includedElements)
518{
519 return editingAttributedStringInternal(range, behaviors, includedElements, ReplaceAllNoBreakSpaces::Yes);
520}
521
522}