| 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 |
Press '?' to see keyboard shortcuts
Keyboard shortcuts:
| 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) |
| 83 | NSAttributedStringKey const WTWritingToolsPreservedAttributeName = @"WTWritingToolsPreserved"; |
| 84 | #endif |
| 85 | |
| 86 | namespace WebCore { |
| 87 | |
| 88 | template<typename Data> |
| 89 | using ElementCache = WeakHashMap<Element, Data, WeakPtrImplWithEventTargetData>; |
| 90 | |
| 91 | static 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 | |
| 133 | static 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 | |
| 156 | static 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 | |
| 172 | static 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 | |
| 181 | static 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 | |
| 200 | static 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) |
| 207 | static 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 | |
| 232 | static 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 | |
| 249 | static 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 | |
| 274 | static 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 | |
| 281 | static 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. |
| 290 | static 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. |
| 335 | static 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. |
| 450 | enum class ReplaceAllNoBreakSpaces : bool { No, Yes }; |
| 451 | static 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 | |
| 512 | AttributedString editingAttributedString(const SimpleRange& range, OptionSet<IncludedElement> includedElements) |
| 513 | { |
| 514 | return editingAttributedStringInternal(range, { }, includedElements, ReplaceAllNoBreakSpaces::No); |
| 515 | } |
| 516 | |
| 517 | AttributedString editingAttributedStringReplacingNoBreakSpace(const SimpleRange& range, TextIteratorBehaviors behaviors, OptionSet<IncludedElement> includedElements) |
| 518 | { |
| 519 | return editingAttributedStringInternal(range, behaviors, includedElements, ReplaceAllNoBreakSpaces::Yes); |
| 520 | } |
| 521 | |
| 522 | } |