Bug Summary

File:Volumes/Data/worker/Apple-iOS-26-Safer-CPP-Checks-EWS/build/Source/WebCore/editing/Editor.cpp
Warning:line 4815, 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 UnifiedSource205.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/UnifiedSource205.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/UnifiedSource205.plist -x c++ /Volumes/Data/worker/Apple-iOS-26-Safer-CPP-Checks-EWS/build/WebKitBuild/Release-iphonesimulator/DerivedSources/WebCore/unified-sources/UnifiedSource205.cpp
1/*
2 * Copyright (C) 2006-2025 Apple Inc. All rights reserved.
3 * Copyright (C) 2008 Nokia Corporation and/or its subsidiary(-ies)
4 * Copyright (C) 2025 Samuel Weinig <sam@webkit.org>
5 *
6 * Redistribution and use in source and binary forms, with or without
7 * modification, are permitted provided that the following conditions
8 * are met:
9 * 1. Redistributions of source code must retain the above copyright
10 * notice, this list of conditions and the following disclaimer.
11 * 2. Redistributions in binary form must reproduce the above copyright
12 * notice, this list of conditions and the following disclaimer in the
13 * documentation and/or other materials provided with the distribution.
14 *
15 * THIS SOFTWARE IS PROVIDED BY APPLE INC. ``AS IS'' AND ANY
16 * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
17 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
18 * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL APPLE INC. OR
19 * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
20 * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
21 * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
22 * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY
23 * OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
24 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
25 * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
26 */
27
28#include "config.h"
29#include "Editor.h"
30
31#include "AXObjectCache.h"
32#include "AlternativeTextController.h"
33#include "ApplyStyleCommand.h"
34#include "AttachmentAssociatedElement.h"
35#include "BoundaryPointInlines.h"
36#include "CSSComputedStyleDeclaration.h"
37#include "CSSPropertyNames.h"
38#include "CSSSerializationContext.h"
39#include "CSSValueList.h"
40#include "CSSValuePool.h"
41#include "CaretRectComputation.h"
42#include "ChangeListTypeCommand.h"
43#include "Chrome.h"
44#include "ChromeClient.h"
45#include "ClipboardEvent.h"
46#include "CommonAtomStrings.h"
47#include "CompositionEvent.h"
48#include "CompositionHighlight.h"
49#include "ContainerNodeInlines.h"
50#include "CreateLinkCommand.h"
51#include "CustomUndoStep.h"
52#include "DataTransfer.h"
53#include "DeleteSelectionCommand.h"
54#include "DeprecatedGlobalSettings.h"
55#include "DictationAlternative.h"
56#include "DictationCommand.h"
57#include "DocumentFragment.h"
58#include "DocumentInlines.h"
59#include "DocumentMarkerController.h"
60#include "DocumentMarkers.h"
61#include "DocumentPage.h"
62#include "DocumentQuirks.h"
63#include "DocumentResourceLoader.h"
64#include "DocumentView.h"
65#include "Editing.h"
66#include "EditorClient.h"
67#include "ElementAncestorIteratorInlines.h"
68#include "EventHandler.h"
69#include "EventNames.h"
70#include "File.h"
71#include "FocusController.h"
72#include "FontAttributes.h"
73#include "FrameDestructionObserverInlines.h"
74#include "FrameInlines.h"
75#include "FrameLoader.h"
76#include "FrameTree.h"
77#include "GraphicsContext.h"
78#include "HTMLAttachmentElement.h"
79#include "HTMLBRElement.h"
80#include "HTMLBodyElement.h"
81#include "HTMLFormControlElement.h"
82#include "HTMLFrameOwnerElement.h"
83#include "HTMLImageElement.h"
84#include "HTMLInputElement.h"
85#include "HTMLNames.h"
86#include "HTMLOListElement.h"
87#include "HTMLQuoteElement.h"
88#include "HTMLSpanElement.h"
89#include "HTMLTextAreaElement.h"
90#include "HTMLUListElement.h"
91#include "HitTestResult.h"
92#include "ImageOverlay.h"
93#include "IndentOutdentCommand.h"
94#include "InputEvent.h"
95#include "InsertListCommand.h"
96#include "InsertTextCommand.h"
97#include "KeyboardEvent.h"
98#include "LocalFrameInlines.h"
99#include "LocalFrameView.h"
100#include "Logging.h"
101#include "ModifySelectionListLevel.h"
102#include "NodeList.h"
103#include "NodeTraversal.h"
104#include "PagePasteboardContext.h"
105#include "Pasteboard.h"
106#include "Range.h"
107#include "RemoveFormatCommand.h"
108#include "RenderAncestorIterator.h"
109#include "RenderBlock.h"
110#include "RenderBlockFlow.h"
111#include "RenderImage.h"
112#include "RenderInline.h"
113#include "RenderLayer.h"
114#include "RenderObjectStyle.h"
115#include "RenderStyle+GettersInlines.h"
116#include "RenderTextControl.h"
117#include "RenderedDocumentMarker.h"
118#include "RenderedPosition.h"
119#include "ReplaceRangeWithTextCommand.h"
120#include "ReplaceSelectionCommand.h"
121#include "ScriptDisallowedScope.h"
122#include "SerializedAttachmentData.h"
123#include "Settings.h"
124#include "ShadowRoot.h"
125#include "SharedBuffer.h"
126#include "SimplifyMarkupCommand.h"
127#include "SpellChecker.h"
128#include "SpellingCorrectionCommand.h"
129#include "StaticPasteboard.h"
130#include "StylePropertiesInlines.h"
131#include "StyleTextShadow.h"
132#include "StyleTreeResolver.h"
133#include "StyleVerticalAlign.h"
134#include "SystemSoundManager.h"
135#include "TelephoneNumberDetector.h"
136#include "Text.h"
137#include "TextCheckerClient.h"
138#include "TextCheckingHelper.h"
139#include "TextEvent.h"
140#include "TextIterator.h"
141#include "TextNodeTraversal.h"
142#include "TextPlaceholderElement.h"
143#include "TypingCommand.h"
144#include "UserTypingGestureIndicator.h"
145#include "VisibleUnits.h"
146#include "WritingSuggestionData.h"
147#include "markup.h"
148#include <pal/FileSizeFormatter.h>
149#include <pal/text/KillRing.h>
150#include <wtf/Scope.h>
151#include <wtf/SetForScope.h>
152#include <wtf/TZoneMallocInlines.h>
153#include <wtf/text/CharacterProperties.h>
154#include <wtf/text/MakeString.h>
155#include <wtf/text/ParsingUtilities.h>
156#include <wtf/unicode/CharacterNames.h>
157
158#if PLATFORM(MAC)(defined WTF_PLATFORM_MAC && WTF_PLATFORM_MAC)
159#include "ServicesOverlayController.h"
160#endif
161
162#if ENABLE(ATTACHMENT_ELEMENT)(defined 1 && 1)
163#include "PromisedAttachmentInfo.h"
164#endif
165
166namespace WebCore {
167
168WTF_MAKE_TZONE_ALLOCATED_IMPL(TemporarySelectionChange)::bmalloc::api::HeapRef TemporarySelectionChange::s_heapRef; const
TZoneSpecification TemporarySelectionChange::s_heapSpec = { &
TemporarySelectionChange::s_heapRef, TZoneSpecification::encodeSize
<TemporarySelectionChange>(), TZoneSpecification::encodeAlignment
<TemporarySelectionChange>(), TZoneSpecification::encodeCategory
<TemporarySelectionChange>(), ::bmalloc::api::compactAllocationMode
<TemporarySelectionChange>(), TZoneSpecification::encodeDescriptor
<TemporarySelectionChange>(), }; void* TemporarySelectionChange
::operatorNewSlow(size_t size) { if constexpr (::bmalloc::api
::compactAllocationMode<TemporarySelectionChange>() == CompactAllocationMode
::Compact) return ::bmalloc::api::tzoneAllocateCompactSlow(size
, s_heapSpec); return ::bmalloc::api::tzoneAllocateNonCompactSlow
(size, s_heapSpec); } using __makeBtzoneMallocedInlineMacroSemicolonifier
__attribute__((unused)) = int
;
169WTF_MAKE_TZONE_ALLOCATED_IMPL(IgnoreSelectionChangeForScope)::bmalloc::api::HeapRef IgnoreSelectionChangeForScope::s_heapRef
; const TZoneSpecification IgnoreSelectionChangeForScope::s_heapSpec
= { &IgnoreSelectionChangeForScope::s_heapRef, TZoneSpecification
::encodeSize<IgnoreSelectionChangeForScope>(), TZoneSpecification
::encodeAlignment<IgnoreSelectionChangeForScope>(), TZoneSpecification
::encodeCategory<IgnoreSelectionChangeForScope>(), ::bmalloc
::api::compactAllocationMode<IgnoreSelectionChangeForScope
>(), TZoneSpecification::encodeDescriptor<IgnoreSelectionChangeForScope
>(), }; void* IgnoreSelectionChangeForScope::operatorNewSlow
(size_t size) { if constexpr (::bmalloc::api::compactAllocationMode
<IgnoreSelectionChangeForScope>() == CompactAllocationMode
::Compact) return ::bmalloc::api::tzoneAllocateCompactSlow(size
, s_heapSpec); return ::bmalloc::api::tzoneAllocateNonCompactSlow
(size, s_heapSpec); } using __makeBtzoneMallocedInlineMacroSemicolonifier
__attribute__((unused)) = int
;
170WTF_MAKE_TZONE_ALLOCATED_IMPL(Editor)::bmalloc::api::HeapRef Editor::s_heapRef; const TZoneSpecification
Editor::s_heapSpec = { &Editor::s_heapRef, TZoneSpecification
::encodeSize<Editor>(), TZoneSpecification::encodeAlignment
<Editor>(), TZoneSpecification::encodeCategory<Editor
>(), ::bmalloc::api::compactAllocationMode<Editor>()
, TZoneSpecification::encodeDescriptor<Editor>(), }; void
* Editor::operatorNewSlow(size_t size) { if constexpr (::bmalloc
::api::compactAllocationMode<Editor>() == CompactAllocationMode
::Compact) return ::bmalloc::api::tzoneAllocateCompactSlow(size
, s_heapSpec); return ::bmalloc::api::tzoneAllocateNonCompactSlow
(size, s_heapSpec); } using __makeBtzoneMallocedInlineMacroSemicolonifier
__attribute__((unused)) = int
;
171
172static bool dispatchBeforeInputEvent(Element& element, const AtomString& inputType, IsInputMethodComposing isInputMethodComposing, const String& data = { },
173 RefPtr<DataTransfer>&& dataTransfer = nullptr, const Vector<Ref<StaticRange>>& targetRanges = { }, Event::IsCancelable cancelable = Event::IsCancelable::Yes)
174{
175 auto event = InputEvent::create(eventNames().beforeinputEvent, inputType, cancelable, element.document().windowProxy(), data,
176 WTF::move(dataTransfer), targetRanges, 0, isInputMethodComposing);
177 element.dispatchEvent(event);
178 return !event->defaultPrevented();
179}
180
181static void dispatchInputEvent(Element& element, const AtomString& inputType, IsInputMethodComposing isInputMethodComposing, const String& data = { },
182 RefPtr<DataTransfer>&& dataTransfer = nullptr, const Vector<Ref<StaticRange>>& targetRanges = { })
183{
184 // FIXME: We should not be dispatching to the scoped queue here. Normally, input events are dispatched in CompositeEditCommand::apply after the end of the scope,
185 // but TypingCommands are special in that existing TypingCommands that are applied again fire input events *from within* the scope by calling typingAddedToOpenCommand.
186 // Instead, TypingCommands should always dispatch events synchronously after the end of the scoped queue in CompositeEditCommand::apply. To work around this for the
187 // time being, just revert back to calling dispatchScopedEvent.
188 element.dispatchScopedEvent(InputEvent::create(eventNames().inputEvent, inputType, Event::IsCancelable::No, element.document().windowProxy(), data,
189 WTF::move(dataTransfer), targetRanges, 0, isInputMethodComposing));
190}
191
192static String inputEventDataForEditingStyleAndAction(const StyleProperties* style, EditAction action)
193{
194 if (!style)
195 return { };
196
197 switch (action) {
198 case EditAction::SetColor:
199 return style->getPropertyValue(CSSPropertyColor);
200 case EditAction::SetInlineWritingDirection:
201 case EditAction::SetBlockWritingDirection:
202 return style->getPropertyValue(CSSPropertyDirection);
203 default:
204 return { };
205 }
206}
207
208static String inputEventDataForEditingStyleAndAction(EditingStyle& style, EditAction action)
209{
210 return inputEventDataForEditingStyleAndAction(style.style(), action);
211}
212
213class ClearTextCommand : public DeleteSelectionCommand {
214public:
215 ClearTextCommand(Ref<Document>&&);
216 static void CreateAndApply(Ref<Document>&&);
217
218private:
219 EditAction editingAction() const override;
220};
221
222ClearTextCommand::ClearTextCommand(Ref<Document>&& document)
223 : DeleteSelectionCommand(WTF::move(document), false, true, false, false, true)
224{
225}
226
227EditAction ClearTextCommand::editingAction() const
228{
229 return EditAction::Delete;
230}
231
232void ClearTextCommand::CreateAndApply(Ref<Document>&& document)
233{
234 if (document->selection().isNone())
235 return;
236
237 // Don't leave around stale composition state.
238 document->editor().clear();
239
240 const VisibleSelection oldSelection = document->selection().selection();
241 document->selection().selectAll();
242 auto clearCommand = adoptRef(*new ClearTextCommand(WTF::move(document)));
243 clearCommand->setStartingSelection(oldSelection);
244 clearCommand->apply();
245}
246
247using namespace HTMLNames;
248using namespace WTF::Unicode;
249
250TemporarySelectionChange::TemporarySelectionChange(Document& document, std::optional<VisibleSelection> temporarySelection, OptionSet<TemporarySelectionOption> options)
251 : m_document(&document)
252 , m_options(options)
253 , m_wasIgnoringSelectionChanges(document.editor().ignoreSelectionChanges())
254#if PLATFORM(IOS_FAMILY)(defined 1 && 1)
255 , m_appearanceUpdatesWereEnabled(document.selection().isUpdateAppearanceEnabled())
256#endif
257{
258#if PLATFORM(IOS_FAMILY)(defined 1 && 1)
259 if (options & TemporarySelectionOption::EnableAppearanceUpdates)
260 document.selection().setUpdateAppearanceEnabled(true);
261#endif
262
263 if (options & TemporarySelectionOption::IgnoreSelectionChanges)
264 document.editor().setIgnoreSelectionChanges(true);
265
266 if (temporarySelection) {
267 m_selectionToRestore = document.selection().selection();
268#if PLATFORM(IOS_FAMILY)(defined 1 && 1)
269 if (document.selection().isUpdateAppearanceEnabled())
270 document.selection().setNeedsSelectionUpdate();
271#endif
272 setSelection(temporarySelection.value(), IsTemporarySelection::Yes);
273 }
274}
275
276void TemporarySelectionChange::invalidate()
277{
278 if (RefPtr document = std::exchange(m_document, nullptr))
279 document->editor().setIgnoreSelectionChanges(false, Editor::RevealSelection::No);
280}
281
282TemporarySelectionChange::~TemporarySelectionChange()
283{
284 if (!m_document)
285 return;
286
287 if (m_selectionToRestore)
288 setSelection(m_selectionToRestore.value(), IsTemporarySelection::No);
289
290 if (m_options & TemporarySelectionOption::IgnoreSelectionChanges) {
291 auto revealSelection = m_options & TemporarySelectionOption::RevealSelection ? Editor::RevealSelection::Yes : Editor::RevealSelection::No;
292 m_document->editor().setIgnoreSelectionChanges(m_wasIgnoringSelectionChanges, revealSelection);
293 }
294
295#if PLATFORM(IOS_FAMILY)(defined 1 && 1)
296 if (m_options & TemporarySelectionOption::EnableAppearanceUpdates)
297 m_document->selection().setUpdateAppearanceEnabled(m_appearanceUpdatesWereEnabled);
298#endif
299}
300
301void TemporarySelectionChange::setSelection(const VisibleSelection& selection, IsTemporarySelection isTemporarySelection)
302{
303 auto options = FrameSelection::defaultSetSelectionOptions();
304
305 if (m_options & TemporarySelectionOption::UserTriggered)
306 options.add(FrameSelection::SetSelectionOption::IsUserTriggered);
307
308 if (m_options & TemporarySelectionOption::DoNotSetFocus)
309 options.add(FrameSelection::SetSelectionOption::DoNotSetFocus);
310
311 if (isTemporarySelection == IsTemporarySelection::Yes) {
312 if (m_options & TemporarySelectionOption::RevealSelection)
313 options.add(FrameSelection::SetSelectionOption::RevealSelection);
314 if (m_options & TemporarySelectionOption::DelegateMainFrameScroll)
315 options.add(FrameSelection::SetSelectionOption::DelegateMainFrameScroll);
316 if (m_options & TemporarySelectionOption::SmoothScroll)
317 options.add(FrameSelection::SetSelectionOption::SmoothScroll);
318 if (m_options & TemporarySelectionOption::RevealSelectionBounds)
319 options.add(FrameSelection::SetSelectionOption::RevealSelectionBounds);
320 if (m_options & TemporarySelectionOption::ForceCenterScroll)
321 options.add(FrameSelection::SetSelectionOption::ForceCenterScroll);
322 if (m_options & TemporarySelectionOption::OnlyAllowForwardScrolling)
323 options.add(FrameSelection::SetSelectionOption::OnlyAllowForwardScrolling);
324 }
325
326 m_document->selection().setSelection(selection, options);
327}
328
329IgnoreSelectionChangeForScope::IgnoreSelectionChangeForScope(LocalFrame& frame)
330 : m_selectionChange(*frame.document(), std::nullopt, TemporarySelectionOption::IgnoreSelectionChanges)
331{
332}
333
334IgnoreSelectionChangeForScope::~IgnoreSelectionChangeForScope() = default;
335
336// When an event handler has moved the selection outside of a text control
337// we should use the target control's selection for this editing operation.
338VisibleSelection Editor::selectionForCommand(Event* event)
339{
340 auto selection = document().selection().selection();
341 if (!event)
342 return selection;
343 // If the target is a text control, and the current selection is outside of its shadow tree,
344 // then use the saved selection for that text control.
345 if (RefPtr target = dynamicDowncast<HTMLTextFormControlElement>(event->target()); target && target->isTextField()) {
346 auto start = selection.start();
347 if (start.isNull() || event->target() != enclosingTextFormControl(start)) {
348 if (auto range = target->selection())
349 return { *range, Affinity::Downstream, selection.directionality() };
350 }
351 }
352 return selection;
353}
354
355// Function considers Mac editing behavior a fallback when Page or Settings is not available.
356EditingBehavior Editor::behavior() const
357{
358 return document().editingBehavior();
359}
360
361EditorClient* Editor::client() const
362{
363 ASSERT(!m_client || !document().page() || m_client == &document().page()->editorClient())((void)0);
364 return m_client.get();
365}
366
367TextCheckerClient* Editor::textChecker() const
368{
369 if (CheckedPtr owner = client())
370 return owner->textChecker();
371 return 0;
372}
373
374void Editor::handleKeyboardEvent(KeyboardEvent& event)
375{
376 if (CheckedPtr client = this->client())
377 client->handleKeyboardEvent(event);
378}
379
380void Editor::handleInputMethodKeydown(KeyboardEvent& event)
381{
382 if (CheckedPtr client = this->client())
383 client->handleInputMethodKeydown(event);
384}
385
386void Editor::didDispatchInputMethodKeydown(KeyboardEvent& event)
387{
388 if (CheckedPtr client = this->client())
389 client->didDispatchInputMethodKeydown(event);
390}
391
392bool Editor::handleTextEvent(TextEvent& event)
393{
394 LOG(Editing, "Editor %p handleTextEvent (data %s)", this, event.data().utf8().data())((void)0);
395
396 // Default event handling for Drag and Drop will be handled by DragController
397 // so we leave the event for it.
398 if (event.isDrop())
399 return false;
400
401 if (event.isPaste() || event.isRemoveBackground()) {
402 auto action = event.isRemoveBackground() ? EditAction::RemoveBackground : EditAction::Paste;
403 if (event.pastingFragment()) {
404#if PLATFORM(IOS_FAMILY)(defined 1 && 1)
405 if (client()->performsTwoStepPaste(event.pastingFragment()))
406 return true;
407#endif
408 replaceSelectionWithFragment(*event.pastingFragment(), SelectReplacement::No, event.shouldSmartReplace() ? SmartReplace::Yes : SmartReplace::No, event.shouldMatchStyle() ? MatchStyle::Yes : MatchStyle::No, action, event.mailBlockquoteHandling());
409 } else
410 replaceSelectionWithText(event.data(), SelectReplacement::No, event.shouldSmartReplace() ? SmartReplace::Yes : SmartReplace::No, action);
411 return true;
412 }
413
414 String data = event.data();
415 if (data == "\n"_s) {
416 if (event.isLineBreak())
417 return insertLineBreak();
418 return insertParagraphSeparator();
419 }
420
421 return insertTextWithoutSendingTextEvent(data, false, &event);
422}
423
424bool Editor::canEdit() const
425{
426 return document().selection().selection().rootEditableElement();
427}
428
429bool Editor::canEditRichly() const
430{
431 return document().selection().selection().isContentRichlyEditable();
432}
433
434static AtomString eventNameForClipboardEvent(ClipboardEventKind kind)
435{
436 switch (kind) {
437 case ClipboardEventKind::Copy:
438 case ClipboardEventKind::CopyFont:
439 return eventNames().copyEvent;
440 case ClipboardEventKind::Cut:
441 return eventNames().cutEvent;
442 case ClipboardEventKind::Paste:
443 case ClipboardEventKind::PasteAsPlainText:
444 case ClipboardEventKind::PasteAsQuotation:
445 case ClipboardEventKind::PasteFont:
446 return eventNames().pasteEvent;
447 case ClipboardEventKind::BeforeCopy:
448 return eventNames().beforecopyEvent;
449 case ClipboardEventKind::BeforeCut:
450 return eventNames().beforecutEvent;
451 case ClipboardEventKind::BeforePaste:
452 return eventNames().beforepasteEvent;
453 }
454 ASSERT_NOT_REACHED()((void)0);
455 return { };
456}
457
458static Ref<DataTransfer> createDataTransferForClipboardEvent(Document& document, ClipboardEventKind kind)
459{
460 switch (kind) {
461 case ClipboardEventKind::Copy:
462 case ClipboardEventKind::CopyFont:
463 case ClipboardEventKind::Cut:
464 return DataTransfer::createForCopyAndPaste(document, DataTransfer::StoreMode::ReadWrite, makeUnique<StaticPasteboard>());
465 case ClipboardEventKind::PasteAsPlainText:
466 if (DeprecatedGlobalSettings::customPasteboardDataEnabled()) {
467 auto plainTextType = textPlainContentTypeAtom();
468 auto plainText = Pasteboard::createForCopyAndPaste(PagePasteboardContext::create(document.pageID()))->readString(plainTextType);
469 auto pasteboard = makeUnique<StaticPasteboard>();
470 pasteboard->writeString(plainTextType, plainText);
471 return DataTransfer::createForCopyAndPaste(document, DataTransfer::StoreMode::Readonly, WTF::move(pasteboard));
472 }
473 [[fallthrough]];
474 case ClipboardEventKind::Paste:
475 case ClipboardEventKind::PasteAsQuotation:
476 case ClipboardEventKind::PasteFont:
477 return DataTransfer::createForCopyAndPaste(document, DataTransfer::StoreMode::Readonly, Pasteboard::createForCopyAndPaste(PagePasteboardContext::create(document.pageID())));
478 case ClipboardEventKind::BeforeCopy:
479 case ClipboardEventKind::BeforeCut:
480 case ClipboardEventKind::BeforePaste:
481 return DataTransfer::createForCopyAndPaste(document, DataTransfer::StoreMode::Invalid, makeUnique<StaticPasteboard>());
482 }
483 ASSERT_NOT_REACHED()((void)0);
484 return DataTransfer::createForCopyAndPaste(document, DataTransfer::StoreMode::Invalid, makeUnique<StaticPasteboard>());
485}
486
487// Returns whether caller should continue with "the default processing", which is the same as
488// the event handler NOT setting the return value to false
489// https://w3c.github.io/clipboard-apis/#fire-a-clipboard-event
490bool Editor::dispatchClipboardEvent(RefPtr<Element>&& target, ClipboardEventKind kind, Ref<DataTransfer>&& dataTransfer)
491{
492 // FIXME: Move the target selection code here.
493 if (!target)
494 return true;
495
496 auto event = ClipboardEvent::create(eventNameForClipboardEvent(kind), dataTransfer.copyRef());
497
498 target->dispatchEvent(event);
499 bool noDefaultProcessing = event->defaultPrevented();
500 if (noDefaultProcessing && (kind == ClipboardEventKind::Copy || kind == ClipboardEventKind::Cut) && dataTransfer->pasteboard().hasData())
501 dataTransfer->commitToPasteboard(*Pasteboard::createForCopyAndPaste(PagePasteboardContext::create(target->document().pageID())));
502
503 dataTransfer->makeInvalidForSecurity();
504
505 return !noDefaultProcessing;
506}
507
508bool Editor::dispatchClipboardEvent(RefPtr<Element>&& target, ClipboardEventKind kind)
509{
510 if (!target)
511 return true;
512
513 return Editor::dispatchClipboardEvent(WTF::move(target), kind, createDataTransferForClipboardEvent(target->document(), kind));
514}
515
516// WinIE uses onbeforecut and onbeforepaste to enables the cut and paste menu items. They
517// also send onbeforecopy, apparently for symmetry, but it doesn't affect the menu items.
518// We need to use onbeforecopy as a real menu enabler because we allow elements that are not
519// normally selectable to implement copy/paste (like divs, or a document body).
520
521bool Editor::canDHTMLCut()
522{
523 if (document().selection().selection().isInPasswordField())
524 return false;
525
526 return !dispatchClipboardEvent(findEventTargetFromSelection(), ClipboardEventKind::BeforeCut);
527}
528
529bool Editor::canDHTMLCopy()
530{
531 if (document().selection().selection().isInPasswordField())
532 return false;
533 return !dispatchClipboardEvent(findEventTargetFromSelection(), ClipboardEventKind::BeforeCopy);
534}
535
536bool Editor::canDHTMLPaste()
537{
538 return !dispatchClipboardEvent(findEventTargetFromSelection(), ClipboardEventKind::BeforePaste);
539}
540
541bool Editor::canCut() const
542{
543 return canCopy() && canDelete();
544}
545
546static HTMLImageElement* imageElementFromImageDocument(Document& document)
547{
548 if (!document.isImageDocument())
549 return nullptr;
550
551 RefPtr body = document.bodyOrFrameset();
552 return body ? dynamicDowncast<HTMLImageElement>(body->firstChild()) : nullptr;
553}
554
555bool Editor::canCopy() const
556{
557 if (imageElementFromImageDocument(document()))
558 return true;
559 const VisibleSelection& selection = document().selection().selection();
560 return (selection.isRange() || !isEditablePosition(selection.start())) && (!selection.isInPasswordField() || selection.isInAutoFilledAndViewableField());
561}
562
563bool Editor::canDelete() const
564{
565 const VisibleSelection& selection = document().selection().selection();
566 return selection.isRange() && selection.rootEditableElement();
567}
568
569bool Editor::canDeleteRange(const SimpleRange& range) const
570{
571 if (!range.start.container->hasEditableStyle() || !range.end.container->hasEditableStyle())
572 return false;
573
574 if (range.collapsed()) {
575 // FIXME: We sometimes allow deletions at the start of editable roots, like when the caret is in an empty list item.
576 auto previous = VisiblePosition { makeDeprecatedLegacyPosition(range.start) }.previous();
577 if (previous.isNull() || previous.deepEquivalent().deprecatedNode()->rootEditableElement() != range.start.container->rootEditableElement())
578 return false;
579 }
580
581 return true;
582}
583
584bool Editor::shouldSmartDelete()
585{
586 if (behavior().shouldAlwaysSmartDelete())
587 return true;
588 return document().selection().granularity() == TextGranularity::WordGranularity;
589}
590
591bool Editor::smartInsertDeleteEnabled()
592{
593 return client() && client()->smartInsertDeleteEnabled();
594}
595
596bool Editor::canSmartCopyOrDelete()
597{
598 return client() && client()->smartInsertDeleteEnabled() && shouldSmartDelete();
599}
600
601bool Editor::isSelectTrailingWhitespaceEnabled() const
602{
603 return client() && client()->isSelectTrailingWhitespaceEnabled();
604}
605
606bool Editor::deleteWithDirection(SelectionDirection direction, TextGranularity granularity, bool shouldAddToKillRing, bool isTypingAction)
607{
608 if (!canEdit())
609 return false;
610
611 Ref document = this->document();
612 if (document->selection().isRange()) {
613 if (isTypingAction) {
614 TypingCommand::deleteKeyPressed(WTF::move(document), canSmartCopyOrDelete() ? TypingCommand::Option::SmartDelete : OptionSet<TypingCommand::Option> { }, granularity);
615 revealSelectionAfterEditingOperation();
616 } else {
617 if (shouldAddToKillRing)
618 addRangeToKillRing(*selectedRange(), KillRingInsertionMode::AppendText);
619 deleteSelectionWithSmartDelete(canSmartCopyOrDelete());
620 // Implicitly calls revealSelectionAfterEditingOperation().
621 }
622 } else {
623 OptionSet<TypingCommand::Option> options;
624 if (canSmartCopyOrDelete())
625 options.add(TypingCommand::Option::SmartDelete);
626 if (shouldAddToKillRing)
627 options.add(TypingCommand::Option::AddsToKillRing);
628 switch (direction) {
629 case SelectionDirection::Forward:
630 case SelectionDirection::Right:
631 TypingCommand::forwardDeleteKeyPressed(WTF::move(document), options, granularity);
632 break;
633 case SelectionDirection::Backward:
634 case SelectionDirection::Left:
635 TypingCommand::deleteKeyPressed(WTF::move(document), options, granularity);
636 break;
637 }
638 revealSelectionAfterEditingOperation();
639 }
640
641 // FIXME: We should to move this down into deleteKeyPressed.
642 // clear the "start new kill ring sequence" setting, because it was set to true
643 // when the selection was updated by deleting the range
644 if (shouldAddToKillRing)
645 setStartNewKillRingSequence(false);
646
647 return true;
648}
649
650void Editor::deleteSelectionWithSmartDelete(bool smartDelete, EditAction editingAction)
651{
652 Ref document = this->document();
653 if (document->selection().isNone())
654 return;
655
656 DeleteSelectionCommand::create(WTF::move(document), smartDelete, true, false, false, true, editingAction)->apply();
657}
658
659void Editor::clearText()
660{
661 ClearTextCommand::CreateAndApply(protect(document()));
662}
663
664void Editor::pasteAsPlainText(const String& pastingText, bool smartReplace)
665{
666 auto target = findEventTargetFromSelection();
667 if (!target)
668 return;
669 auto sanitizedText = pastingText;
670 Ref document = this->document();
671 if (RefPtr page = document->page())
672 sanitizedText = page->applyLinkDecorationFiltering(sanitizedText, LinkDecorationFilteringTrigger::Paste);
673 target->dispatchEvent(TextEvent::createForPlainTextPaste(document->windowProxy(), WTF::move(sanitizedText), smartReplace));
674}
675
676void Editor::pasteAsFragment(Ref<DocumentFragment>&& pastingFragment, bool smartReplace, bool matchStyle, MailBlockquoteHandling respectsMailBlockquote, EditAction action)
677{
678 auto target = findEventTargetFromSelection();
679 if (!target)
680 return;
681
682 ASSERT(action == EditAction::RemoveBackground || action == EditAction::Paste)((void)0);
683 auto type = action == EditAction::RemoveBackground ? TextEventInputRemoveBackground : TextEventInputPaste;
684 target->dispatchEvent(TextEvent::createForFragmentPaste(document().windowProxy(), WTF::move(pastingFragment), type, smartReplace, matchStyle, respectsMailBlockquote));
685}
686
687void Editor::pasteAsPlainTextBypassingDHTML()
688{
689 pasteAsPlainTextWithPasteboard(*Pasteboard::createForCopyAndPaste(PagePasteboardContext::create(document().pageID())));
690}
691
692void Editor::pasteAsPlainTextWithPasteboard(Pasteboard& pasteboard)
693{
694 String text = readPlainTextFromPasteboard(pasteboard);
695 if (client() && client()->shouldInsertText(text, selectedRange(), EditorInsertAction::Pasted))
696 pasteAsPlainText(text, canSmartReplaceWithPasteboard(pasteboard));
697}
698
699String Editor::readPlainTextFromPasteboard(Pasteboard& pasteboard)
700{
701 PasteboardPlainText text;
702 pasteboard.read(text);
703 return plainTextFromPasteboard(text);
704}
705
706#if !PLATFORM(MAC)(defined WTF_PLATFORM_MAC && WTF_PLATFORM_MAC)
707
708String Editor::plainTextFromPasteboard(const PasteboardPlainText& text)
709{
710 return text.text;
711}
712
713#endif
714
715bool Editor::canSmartReplaceWithPasteboard(Pasteboard& pasteboard)
716{
717 return client() && client()->smartInsertDeleteEnabled() && pasteboard.canSmartReplace();
718}
719
720bool Editor::shouldInsertFragment(DocumentFragment& fragment, const std::optional<SimpleRange>& replacingDOMRange, EditorInsertAction givenAction)
721{
722 if (!client())
723 return false;
724
725 if (RefPtr child = dynamicDowncast<CharacterData>(fragment.firstChild()); child && fragment.lastChild() == fragment.firstChild())
726 return client()->shouldInsertText(child->data(), replacingDOMRange, givenAction);
727
728 return client()->shouldInsertNode(fragment, replacingDOMRange, givenAction);
729}
730
731void Editor::replaceSelectionWithFragment(DocumentFragment& fragment, SelectReplacement selectReplacement, SmartReplace smartReplace, MatchStyle matchStyle, EditAction editingAction, MailBlockquoteHandling mailBlockquoteHandling)
732{
733 Ref document = this->document();
734 VisibleSelection selection = document->selection().selection();
735 if (selection.isNone() || !selection.isContentEditable())
736 return;
737
738 AccessibilityReplacedText replacedText;
739 if (AXObjectCache::accessibilityEnabled() && (editingAction == EditAction::Paste || editingAction == EditAction::Insert || editingAction == EditAction::RemoveBackground))
740 replacedText = AccessibilityReplacedText(selection);
741
742 OptionSet<ReplaceSelectionCommand::CommandOption> options { ReplaceSelectionCommand::PreventNesting, ReplaceSelectionCommand::SanitizeFragment };
743 if (selectReplacement == SelectReplacement::Yes)
744 options.add(ReplaceSelectionCommand::SelectReplacement);
745 if (smartReplace == SmartReplace::Yes)
746 options.add(ReplaceSelectionCommand::SmartReplace);
747 if (matchStyle == MatchStyle::Yes)
748 options.add(ReplaceSelectionCommand::MatchStyle);
749 if (mailBlockquoteHandling == MailBlockquoteHandling::IgnoreBlockquote)
750 options.add(ReplaceSelectionCommand::IgnoreMailBlockquote);
751
752 auto command = ReplaceSelectionCommand::create(document.copyRef(), &fragment, options, editingAction);
753 command->apply();
754
755 m_imageElementsToLoadBeforeRevealingSelection.clear();
756 if (auto insertionRange = command->insertedContentRange())
757 m_imageElementsToLoadBeforeRevealingSelection = visibleImageElementsInRangeWithNonLoadedImages(*insertionRange);
758
759 if (m_imageElementsToLoadBeforeRevealingSelection.isEmpty())
760 revealSelectionAfterEditingOperation();
761
762 selection = document->selection().selection();
763 if (selection.isInPasswordField())
764 return;
765
766 if (AXObjectCache::accessibilityEnabled() && editingAction == EditAction::Paste) {
767 String text = AccessibilityObject::stringForVisiblePositionRange(command->visibleSelectionForInsertedText());
768 replacedText.postTextStateChangeNotification(document->existingAXObjectCache(), AXTextEditType::Paste, text, document->selection().selection());
769 command->composition()->setRangeDeletedByUnapply(replacedText.replacedRange());
770 }
771
772 if (AXObjectCache::accessibilityEnabled() && editingAction == EditAction::Insert) {
773 String text = command->documentFragmentPlainText();
774 replacedText.postTextStateChangeNotification(document->existingAXObjectCache(), AXTextEditType::Insert, text, document->selection().selection());
775 command->composition()->setRangeDeletedByUnapply(replacedText.replacedRange());
776 }
777
778 if (!isContinuousSpellCheckingEnabled())
779 return;
780
781 RefPtr nodeToCheck { selection.rootEditableElement() };
782 if (!nodeToCheck)
783 return;
784
785 auto rangeToCheck = makeRangeSelectingNodeContents(*nodeToCheck);
786 if (auto request = SpellCheckRequest::create(resolveTextCheckingTypeMask(*nodeToCheck, { TextCheckingType::Spelling, TextCheckingType::Grammar }), TextCheckingProcessType::TextCheckingProcessBatch, rangeToCheck, rangeToCheck, rangeToCheck))
787 m_spellChecker->requestCheckingFor(request.releaseNonNull());
788}
789
790void Editor::replaceSelectionWithText(const String& text, SelectReplacement selectReplacement, SmartReplace smartReplace, EditAction editingAction)
791{
792 auto range = selectedRange();
793 if (!range)
794 return;
795
796 replaceSelectionWithFragment(createFragmentFromText(*range, text), selectReplacement, smartReplace, MatchStyle::Yes, editingAction);
797}
798
799std::optional<SimpleRange> Editor::selectedRange()
800{
801 return document().selection().selection().toNormalizedRange();
802}
803
804bool Editor::shouldDeleteRange(const std::optional<SimpleRange>& range) const
805{
806 return range && !range->collapsed() && canDeleteRange(*range) && client() && client()->shouldDeleteRange(*range);
807}
808
809bool Editor::tryDHTMLCopy()
810{
811 if (document().selection().selection().isInPasswordField())
812 return false;
813
814 return !dispatchClipboardEvent(findEventTargetFromSelection(), ClipboardEventKind::Copy);
815}
816
817bool Editor::tryDHTMLCut()
818{
819 if (document().selection().selection().isInPasswordField())
820 return false;
821
822 return !dispatchClipboardEvent(findEventTargetFromSelection(), ClipboardEventKind::Cut);
823}
824
825bool Editor::shouldInsertText(const String& text, const std::optional<SimpleRange>& range, EditorInsertAction action) const
826{
827 // FIXME(273431): shouldSuppressTextInputFromEditing does not work with site isolation.
828 RefPtr localFrame = document().localMainFrame();
829 if (localFrame && localFrame->loader().shouldSuppressTextInputFromEditing() && action == EditorInsertAction::Typed)
830 return false;
831
832 return client() && client()->shouldInsertText(text, range, action);
833}
834
835void Editor::respondToChangedContents(const VisibleSelection& endingSelection)
836{
837 if (AXObjectCache::accessibilityEnabled()) {
838 if (RefPtr node = endingSelection.start().deprecatedNode()) {
839 if (CheckedPtr cache = document().existingAXObjectCache())
840 cache->onEditableTextValueChanged(*node.get());
841 }
842 }
843
844 updateMarkersForWordsAffectedByEditing(true);
845
846 if (client())
847 client()->respondToChangedContents();
848}
849
850bool Editor::hasBidiSelection() const
851{
852 Ref document = this->document();
853 if (document->selection().isNone())
854 return false;
855
856 RefPtr<Node> startNode;
857 if (document->selection().isRange()) {
858 startNode = document->selection().selection().start().downstream().deprecatedNode();
859 RefPtr endNode = document->selection().selection().end().upstream().deprecatedNode();
860 if (enclosingBlock(startNode.get()) != enclosingBlock(endNode.get()))
861 return false;
862 } else
863 startNode = document->selection().selection().visibleStart().deepEquivalent().deprecatedNode();
864
865 if (!startNode || !startNode->renderer())
866 return false;
867
868 ScriptDisallowedScope::InMainThread scriptDisallowedScope;
869
870 if (CheckedPtr renderBlockFlow = ancestorsOfType<RenderBlockFlow>(*startNode->renderer()).first())
871 return !renderBlockFlow->style().writingMode().deprecatedIsLeftToRightDirection() || renderBlockFlow->containsNonZeroBidiLevel();
872 return false;
873}
874
875TriState Editor::selectionUnorderedListState() const
876{
877 Ref document = this->document();
878 if (document->selection().isCaret()) {
879 if (enclosingElementWithTag(document->selection().selection().start(), ulTag))
880 return TriState::True;
881 } else if (document->selection().isRange()) {
882 RefPtr startNode { enclosingElementWithTag(document->selection().selection().start(), ulTag) };
883 RefPtr endNode { enclosingElementWithTag(document->selection().selection().end(), ulTag) };
884 if (startNode && endNode && startNode == endNode)
885 return TriState::True;
886 }
887
888 return TriState::False;
889}
890
891TriState Editor::selectionOrderedListState() const
892{
893 Ref document = this->document();
894 if (document->selection().isCaret()) {
895 if (enclosingElementWithTag(document->selection().selection().start(), olTag))
896 return TriState::True;
897 } else if (document->selection().isRange()) {
898 RefPtr startNode { enclosingElementWithTag(document->selection().selection().start(), olTag) };
899 RefPtr endNode { enclosingElementWithTag(document->selection().selection().end(), olTag) };
900 if (startNode && endNode && startNode == endNode)
901 return TriState::True;
902 }
903
904 return TriState::False;
905}
906
907RefPtr<Node> Editor::insertOrderedList()
908{
909 if (!canEditRichly())
910 return nullptr;
911
912 auto newList = InsertListCommand::insertList(protect(document()), InsertListCommand::Type::OrderedList);
913 revealSelectionAfterEditingOperation();
914 return newList;
915}
916
917RefPtr<Node> Editor::insertUnorderedList()
918{
919 if (!canEditRichly())
920 return nullptr;
921
922 auto newList = InsertListCommand::insertList(protect(document()), InsertListCommand::Type::UnorderedList);
923 revealSelectionAfterEditingOperation();
924 return newList;
925}
926
927bool Editor::canIncreaseSelectionListLevel()
928{
929 return canEditRichly() && IncreaseSelectionListLevelCommand::canIncreaseSelectionListLevel(&document());
930}
931
932bool Editor::canDecreaseSelectionListLevel()
933{
934 return canEditRichly() && DecreaseSelectionListLevelCommand::canDecreaseSelectionListLevel(&document());
935}
936
937RefPtr<Node> Editor::increaseSelectionListLevel()
938{
939 Ref document = this->document();
940 if (!canEditRichly() || document->selection().isNone())
941 return nullptr;
942
943 RefPtr<Node> newList = IncreaseSelectionListLevelCommand::increaseSelectionListLevel(document.ptr());
944 revealSelectionAfterEditingOperation();
945 return newList;
946}
947
948RefPtr<Node> Editor::increaseSelectionListLevelOrdered()
949{
950 Ref document = this->document();
951 if (!canEditRichly() || document->selection().isNone())
952 return nullptr;
953
954 RefPtr<Node> newList = IncreaseSelectionListLevelCommand::increaseSelectionListLevelOrdered(document.ptr());
955 revealSelectionAfterEditingOperation();
956 return newList;
957}
958
959RefPtr<Node> Editor::increaseSelectionListLevelUnordered()
960{
961 Ref document = this->document();
962 if (!canEditRichly() || document->selection().isNone())
963 return nullptr;
964
965 RefPtr<Node> newList = IncreaseSelectionListLevelCommand::increaseSelectionListLevelUnordered(document.ptr());
966 revealSelectionAfterEditingOperation();
967 return newList;
968}
969
970void Editor::decreaseSelectionListLevel()
971{
972 Ref document = this->document();
973 if (!canEditRichly() || document->selection().isNone())
974 return;
975
976 DecreaseSelectionListLevelCommand::decreaseSelectionListLevel(document.ptr());
977 revealSelectionAfterEditingOperation();
978}
979
980void Editor::removeFormattingAndStyle()
981{
982 RemoveFormatCommand::create(protect(document()))->apply();
983}
984
985void Editor::clearLastEditCommand()
986{
987 m_lastEditCommand = nullptr;
988}
989
990RefPtr<Element> Editor::findEventTargetFrom(const VisibleSelection& selection) const
991{
992 RefPtr target { selection.start().anchorElementAncestor() };
993 if (!target)
994 target = document().bodyOrFrameset();
995
996 return target;
997}
998
999RefPtr<Element> Editor::findEventTargetFromSelection() const
1000{
1001 // https://www.w3.org/TR/clipboard-apis/#fire-a-clipboard-event says:
1002 // If the context is editable, then
1003 // Set target to be the element that contains the start of the visible selection or cursor in document order, or the body element if there is no visible selection or cursor.
1004 const VisibleSelection& selection = document().selection().selection();
1005 if (selection.isRange() || isEditablePosition(selection.start()))
1006 return findEventTargetFrom(selection);
1007 // Else, if the context is not editable, then
1008 // Set target to the focused node, or the body element if no node has focus.
1009 return document().activeElement();
1010}
1011
1012void Editor::applyStyle(StyleProperties* style, EditAction editingAction)
1013{
1014 if (style)
1015 applyStyle(EditingStyle::create(style), editingAction, ColorFilterMode::UseOriginalColor);
1016}
1017
1018void Editor::applyStyle(RefPtr<EditingStyle>&& style, EditAction editingAction, ColorFilterMode colorFilterMode)
1019{
1020 if (!style)
1021 return;
1022
1023 Ref document = this->document();
1024 if (document->selection().isNone())
1025 return;
1026
1027 AtomString inputTypeName = inputTypeNameForEditingAction(editingAction);
1028 auto isInputMethodComposing = isInputMethodComposingForEditingAction(editingAction) ? IsInputMethodComposing::Yes : IsInputMethodComposing::No;
1029 String inputEventData = inputEventDataForEditingStyleAndAction(*style, editingAction);
1030 RefPtr element { document->selection().selection().rootEditableElement() };
1031
1032 if (element && !dispatchBeforeInputEvent(*element, inputTypeName, isInputMethodComposing, inputEventData))
1033 return;
1034
1035 if (document->selection().isNone())
1036 return;
1037
1038 auto styleToApply = colorFilterMode == ColorFilterMode::InvertColor ? style->inverseTransformColorIfNeeded(*element) : style.releaseNonNull();
1039 if (document->selection().isCaret())
1040 computeAndSetTypingStyle(styleToApply, editingAction);
1041 else
1042 ApplyStyleCommand::create(WTF::move(document), styleToApply.ptr(), editingAction)->apply();
1043
1044 if (client())
1045 client()->didApplyStyle();
1046 if (element)
1047 dispatchInputEvent(*element, inputTypeName, isInputMethodComposing, inputEventData);
1048}
1049
1050bool Editor::shouldApplyStyle(const StyleProperties& style, const SimpleRange& range)
1051{
1052 return client()->shouldApplyStyle(style, range);
1053}
1054
1055void Editor::applyParagraphStyle(StyleProperties* style, EditAction editingAction)
1056{
1057 if (!style)
1058 return;
1059
1060 Ref document = this->document();
1061 if (document->selection().isNone())
1062 return;
1063
1064 AtomString inputTypeName = inputTypeNameForEditingAction(editingAction);
1065 auto isInputMethodComposing = isInputMethodComposingForEditingAction(editingAction) ? IsInputMethodComposing::Yes : IsInputMethodComposing::No;
1066 String inputEventData = inputEventDataForEditingStyleAndAction(style, editingAction);
1067 RefPtr element { document->selection().selection().rootEditableElement() };
1068 if (element && !dispatchBeforeInputEvent(*element, inputTypeName, isInputMethodComposing, inputEventData))
1069 return;
1070 if (document->selection().isNone())
1071 return;
1072
1073 ApplyStyleCommand::create(WTF::move(document), EditingStyle::create(style).ptr(), editingAction, ApplyStylePropertyLevel::ForceBlock)->apply();
1074
1075 if (client())
1076 client()->didApplyStyle();
1077 if (element)
1078 dispatchInputEvent(*element, inputTypeName, isInputMethodComposing, inputEventData);
1079}
1080
1081void Editor::applyStyleToSelection(StyleProperties* style, EditAction editingAction)
1082{
1083 if (!style || style->isEmpty() || !canEditRichly())
1084 return;
1085
1086 if (!client() || !client()->shouldApplyStyle(*style, document().selection().selection().toNormalizedRange()))
1087 return;
1088 applyStyle(style, editingAction);
1089}
1090
1091void Editor::applyStyleToSelection(Ref<EditingStyle>&& style, EditAction editingAction, ColorFilterMode colorFilterMode)
1092{
1093 if (style->isEmpty() || !canEditRichly())
1094 return;
1095
1096 // FIXME: This is wrong for text decorations since m_mutableStyle is empty.
1097 if (!client() || !client()->shouldApplyStyle(style->styleWithResolvedTextDecorations(), document().selection().selection().toNormalizedRange()))
1098 return;
1099
1100 applyStyle(WTF::move(style), editingAction, colorFilterMode);
1101}
1102
1103void Editor::applyParagraphStyleToSelection(StyleProperties* style, EditAction editingAction)
1104{
1105 if (!style || style->isEmpty() || !canEditRichly())
1106 return;
1107
1108 if (client() && client()->shouldApplyStyle(*style, document().selection().selection().toNormalizedRange()))
1109 applyParagraphStyle(style, editingAction);
1110}
1111
1112bool Editor::selectionStartHasStyle(CSSPropertyID propertyID, const String& value) const
1113{
1114 if (auto editingStyle = EditingStyle::styleAtSelectionStart(document().selection().selection(), propertyID == CSSPropertyBackgroundColor))
1115 return editingStyle->hasStyle(propertyID, value);
1116 return false;
1117}
1118
1119TriState Editor::selectionHasStyle(CSSPropertyID propertyID, const String& value) const
1120{
1121 return EditingStyle::create(propertyID, value)->triStateOfStyle(document().selection().selection());
1122}
1123
1124String Editor::selectionStartCSSPropertyValue(CSSPropertyID propertyID)
1125{
1126 Ref document = this->document();
1127 RefPtr<EditingStyle> selectionStyle = EditingStyle::styleAtSelectionStart(document->selection().selection(),
1128 propertyID == CSSPropertyBackgroundColor);
1129 if (!selectionStyle || !selectionStyle->style())
1130 return String();
1131
1132 if (propertyID == CSSPropertyFontSize)
1133 return String::number(selectionStyle->legacyFontSize(document));
1134 return selectionStyle->style()->getPropertyValue(propertyID);
1135}
1136
1137static void notifyTextFromControls(Element* startRoot, Element* endRoot, bool wasUserEdit = true)
1138{
1139 RefPtr startingTextControl { enclosingTextFormControl(firstPositionInOrBeforeNode(startRoot)) };
1140 RefPtr endingTextControl { enclosingTextFormControl(firstPositionInOrBeforeNode(endRoot)) };
1141 if (startingTextControl)
1142 startingTextControl->didEditInnerTextValue(wasUserEdit);
1143 if (endingTextControl && startingTextControl != endingTextControl)
1144 endingTextControl->didEditInnerTextValue(wasUserEdit);
1145}
1146
1147static inline bool shouldRemoveAutocorrectionIndicator(bool shouldConsiderApplyingAutocorrection, bool autocorrectionWasApplied, bool isAutocompletion)
1148{
1149#if HAVE(AUTOCORRECTION_ENHANCEMENTS)(defined 1 && 1)
1150 bool shouldRemoveIndicator = shouldConsiderApplyingAutocorrection && !autocorrectionWasApplied;
1151#if PLATFORM(IOS_FAMILY)(defined 1 && 1)
1152 // On iOS, unlike macOS, autocorrection is applied as two separate text insertions. The first insertion is
1153 // an autocompletion.
1154 return shouldRemoveIndicator && !isAutocompletion;
1155#else // !PLATFORM(IOS_FAMILY)
1156 UNUSED_PARAM(isAutocompletion)(void)isAutocompletion;
1157 return shouldRemoveIndicator;
1158#endif // !PLATFORM(IOS_FAMILY)
1159#else // !HAVE(AUTOCORRECTION_ENHANCEMENTS)
1160 UNUSED_PARAM(shouldConsiderApplyingAutocorrection)(void)shouldConsiderApplyingAutocorrection;
1161 UNUSED_PARAM(isAutocompletion)(void)isAutocompletion;
1162#endif // !HAVE(AUTOCORRECTION_ENHANCEMENTS)
1163 return !autocorrectionWasApplied;
1164}
1165
1166static inline bool didApplyAutocorrection(Document& document, AlternativeTextController& alternativeTextController)
1167{
1168#if HAVE(AUTOCORRECTION_ENHANCEMENTS)(defined 1 && 1) && PLATFORM(IOS_FAMILY)(defined 1 && 1)
1169 bool autocorrectionWasApplied = alternativeTextController.applyAutocorrectionBeforeTypingIfAppropriate();
1170
1171 // On iOS, unlike macOS, autocorrection is applied as two separate text insertions: the correction
1172 // itself, followed by a space. This logic detects that an autocorrection was applied after the
1173 // space has been inserted.
1174
1175 auto selection = document.selection().selection();
1176 auto startOfSelection = selection.start();
1177
1178 auto wordStart = startOfWord(startOfSelection, WordSide::LeftWordIfOnBoundary);
1179 auto wordEnd = endOfWord(startOfSelection, WordSide::LeftWordIfOnBoundary);
1180
1181 if (auto range = makeSimpleRange(wordStart, wordEnd)) {
1182 if (CheckedPtr markers = document.markersIfExists(); markers && markers->hasMarkers(*range, DocumentMarkerType::CorrectionIndicator))
1183 autocorrectionWasApplied = true;
1184 }
1185
1186 return autocorrectionWasApplied;
1187#else
1188 UNUSED_PARAM(document)(void)document;
1189 return alternativeTextController.applyAutocorrectionBeforeTypingIfAppropriate();
1190#endif
1191}
1192
1193static inline void adjustMarkerTypesToRemoveForWordsAffectedByEditing(OptionSet<DocumentMarkerType>& markerTypes)
1194{
1195#if HAVE(AUTOCORRECTION_ENHANCEMENTS)(defined 1 && 1) && PLATFORM(IOS_FAMILY)(defined 1 && 1)
1196 markerTypes.remove(DocumentMarkerType::CorrectionIndicator);
1197#else
1198 UNUSED_PARAM(markerTypes)(void)markerTypes;
1199#endif
1200}
1201
1202static bool dispatchBeforeInputEvents(RefPtr<Element> startRoot, RefPtr<Element> endRoot, const AtomString& inputTypeName, IsInputMethodComposing isInputMethodComposing,
1203 const String& data = { }, RefPtr<DataTransfer>&& dataTransfer = nullptr, const Vector<Ref<StaticRange>>& targetRanges = { }, Event::IsCancelable cancelable = Event::IsCancelable::Yes)
1204{
1205 bool continueWithDefaultBehavior = true;
1206 if (startRoot)
1207 continueWithDefaultBehavior &= dispatchBeforeInputEvent(*startRoot, inputTypeName, isInputMethodComposing, data, WTF::move(dataTransfer), targetRanges, cancelable);
1208 if (endRoot && endRoot != startRoot)
1209 continueWithDefaultBehavior &= dispatchBeforeInputEvent(*endRoot, inputTypeName, isInputMethodComposing, data, WTF::move(dataTransfer), targetRanges, cancelable);
1210 return continueWithDefaultBehavior;
1211}
1212
1213static void dispatchInputEvents(RefPtr<Element> startRoot, RefPtr<Element> endRoot, const AtomString& inputTypeName, IsInputMethodComposing isInputMethodComposing,
1214 const String& data = { }, RefPtr<DataTransfer>&& dataTransfer = nullptr, const Vector<Ref<StaticRange>>& targetRanges = { })
1215{
1216 if (startRoot)
1217 dispatchInputEvent(*startRoot, inputTypeName, isInputMethodComposing, data, WTF::move(dataTransfer), targetRanges);
1218 if (endRoot && endRoot != startRoot)
1219 dispatchInputEvent(*endRoot, inputTypeName, isInputMethodComposing, data, WTF::move(dataTransfer), targetRanges);
1220}
1221
1222bool Editor::willApplyEditing(CompositeEditCommand& command, Vector<Ref<StaticRange>>&& targetRanges)
1223{
1224#if ENABLE(WRITING_TOOLS)(defined 1 && 1)
1225 if (suppressEditingForWritingTools()) {
1226 RELEASE_LOG(Editing, "Editor %p suppressed editing for Writing Tools", this)clang diagnostic push clang diagnostic ignored "-Wunknown-warning-option"
clang diagnostic ignored "-Wunsafe-buffer-usage" clang diagnostic
ignored "-Wunsafe-buffer-usage-in-libc-call" clang diagnostic
ignored "-Wunsafe-buffer-usage-in-format-attr-call" [[clang
::suppress]] __extension__({ os_log_t _log_tmp = (LogEditing.
osLogChannel); os_log_type_t _type_tmp = (OS_LOG_TYPE_DEFAULT
); if (os_log_type_enabled(_log_tmp, _type_tmp)) { __extension__
({ clang diagnostic push clang diagnostic ignored "-Wvla" clang
diagnostic error "-Wformat" _Static_assert(__builtin_constant_p
("Editor %p suppressed editing for Writing Tools"), "format/label/description argument must be a string constant"
); __attribute__((section("__TEXT,__oslogstring,cstring_literals"
),internal_linkage)) static const char _os_fmt_str[] __asm("LOS_LOG3"
) = "Editor %p suppressed editing for Writing Tools"; uint8_t
_Alignas(16) __attribute__((uninitialized)) _os_fmt_buf[__builtin_os_log_format_buffer_size
("Editor %p suppressed editing for Writing Tools", this)]; _os_log_impl
(&__dso_handle, _log_tmp, _type_tmp, _os_fmt_str, (uint8_t
*)__builtin_os_log_format(_os_fmt_buf, "Editor %p suppressed editing for Writing Tools"
, this), (uint32_t)sizeof(_os_fmt_buf)) clang diagnostic pop
; }); } }) clang diagnostic pop
;
1227 return false;
1228 }
1229#endif
1230
1231 m_hasHandledAnyEditing = true;
1232
1233 if (!command.shouldDispatchInputEvents())
1234 return true;
1235
1236 RefPtr composition { command.composition() };
1237 if (!composition)
1238 return true;
1239
1240 if (command.isTopLevelCommand() && command.isTypingCommand() && document().view())
1241 m_prohibitScrollingDueToContentSizeChangesWhileTyping = document().view()->prohibitScrollingWhenChangingContentSizeForScope();
1242
1243 auto isInputMethodComposing = command.isInputMethodComposing() ? IsInputMethodComposing::Yes : IsInputMethodComposing::No;
1244 return dispatchBeforeInputEvents(composition->startingRootEditableElement(), composition->endingRootEditableElement(), command.inputEventTypeName(), isInputMethodComposing,
1245 command.inputEventData(), command.inputEventDataTransfer(), targetRanges, command.isBeforeInputEventCancelable() ? Event::IsCancelable::Yes : Event::IsCancelable::No);
1246}
1247
1248void Editor::appliedEditing(CompositeEditCommand& command)
1249{
1250 LOG(Editing, "Editor %p appliedEditing", this)((void)0);
1251
1252 Ref document = this->document();
1253 document->updateLayout();
1254
1255 ASSERT(command.composition())((void)0);
1256 Ref composition = *command.composition();
1257 VisibleSelection newSelection(command.endingSelection());
1258
1259 bool wasUserEdit = [&command] {
1260 RefPtr typingCommand = dynamicDowncast<TypingCommand>(command);
1261 return !typingCommand || !typingCommand->triggeringEventIsUntrusted();
1262 }();
1263 notifyTextFromControls(composition->startingRootEditableElement(), composition->endingRootEditableElement(), wasUserEdit);
1264
1265 if (command.isTopLevelCommand()) {
1266 // Don't clear the typing style with this selection change. We do those things elsewhere if necessary.
1267 OptionSet<FrameSelection::SetSelectionOption> options;
1268 if (command.isDictationCommand())
1269 options.add(FrameSelection::SetSelectionOption::DictationTriggered);
1270
1271 changeSelectionAfterCommand(newSelection, options);
1272 }
1273
1274 auto isInputMethodComposing = command.isInputMethodComposing() ? IsInputMethodComposing::Yes : IsInputMethodComposing::No;
1275 if (command.shouldDispatchInputEvents()) {
1276 dispatchInputEvents(composition->startingRootEditableElement(), composition->endingRootEditableElement(), command.inputEventTypeName(), isInputMethodComposing,
1277 command.inputEventData(), command.inputEventDataTransfer());
1278 }
1279
1280 if (command.isTopLevelCommand()) {
1281 updateEditorUINowIfScheduled();
1282
1283 m_alternativeTextController->respondToAppliedEditing(&command);
1284
1285 if (!command.preservesTypingStyle())
1286 document->selection().clearTypingStyle();
1287
1288 // Command will be equal to last edit command only in the case of typing
1289 if (m_lastEditCommand.get() == &command)
1290 ASSERT(command.isTypingCommand())((void)0);
1291 else {
1292 // Only register a new undo command if the command passed in is
1293 // different from the last command
1294 m_lastEditCommand = command;
1295 if (client())
1296 client()->registerUndoStep(m_lastEditCommand->ensureComposition());
1297 }
1298 respondToChangedContents(newSelection);
1299
1300 if (command.isTypingCommand())
1301 m_prohibitScrollingDueToContentSizeChangesWhileTyping = nullptr;
1302 }
1303}
1304
1305bool Editor::willUnapplyEditing(const EditCommandComposition& composition) const
1306{
1307 TypingCommand::closeTyping(protect(document()));
1308 return dispatchBeforeInputEvents(composition.startingRootEditableElement(), composition.endingRootEditableElement(), "historyUndo"_s, IsInputMethodComposing::No);
1309}
1310
1311void Editor::unappliedEditing(EditCommandComposition& composition)
1312{
1313 protect(document())->updateLayout();
1314
1315 notifyTextFromControls(composition.startingRootEditableElement(), composition.endingRootEditableElement());
1316
1317 VisibleSelection newSelection(composition.startingSelection());
1318 changeSelectionAfterCommand(newSelection, FrameSelection::defaultSetSelectionOptions());
1319 dispatchInputEvents(composition.startingRootEditableElement(), composition.endingRootEditableElement(), "historyUndo"_s, IsInputMethodComposing::No);
1320
1321 updateEditorUINowIfScheduled();
1322
1323 m_alternativeTextController->respondToUnappliedEditing(&composition);
1324#if ENABLE(WRITING_TOOLS)(defined 1 && 1)
1325 if (RefPtr page = document().page())
1326 page->respondToUnappliedWritingToolsEditing(&composition);
1327#endif
1328
1329 m_lastEditCommand = nullptr;
1330 if (CheckedPtr client = this->client())
1331 client->registerRedoStep(composition);
1332 respondToChangedContents(newSelection);
1333}
1334
1335bool Editor::willReapplyEditing(const EditCommandComposition& composition) const
1336{
1337 return dispatchBeforeInputEvents(composition.startingRootEditableElement(), composition.endingRootEditableElement(), "historyRedo"_s, IsInputMethodComposing::No);
1338}
1339
1340void Editor::reappliedEditing(EditCommandComposition& composition)
1341{
1342 protect(document())->updateLayout();
1343
1344 notifyTextFromControls(composition.startingRootEditableElement(), composition.endingRootEditableElement());
1345
1346 VisibleSelection newSelection(composition.endingSelection());
1347 changeSelectionAfterCommand(newSelection, FrameSelection::defaultSetSelectionOptions());
1348 dispatchInputEvents(composition.startingRootEditableElement(), composition.endingRootEditableElement(), "historyRedo"_s, IsInputMethodComposing::No);
1349
1350 updateEditorUINowIfScheduled();
1351
1352#if ENABLE(WRITING_TOOLS)(defined 1 && 1)
1353 if (RefPtr page = document().page())
1354 page->respondToReappliedWritingToolsEditing(&composition);
1355#endif
1356
1357 m_lastEditCommand = nullptr;
1358 if (CheckedPtr client = this->client())
1359 client->registerUndoStep(composition);
1360 respondToChangedContents(newSelection);
1361}
1362
1363Editor::Editor(Document& document)
1364 : m_client(document.page() ? &document.page()->editorClient() : nullptr)
1365 , m_document(document)
1366 , m_killRing(makeUniqueRef<PAL::KillRing>())
1367 , m_spellChecker(makeUniqueRefWithoutRefCountedCheck<SpellChecker>(*this))
1368 , m_alternativeTextController(makeUniqueRef<AlternativeTextController>(document))
1369 , m_editorUIUpdateTimer(*this, &Editor::editorUIUpdateTimerFired)
1370#if ENABLE(TELEPHONE_NUMBER_DETECTION)(defined 1 && 1) && !PLATFORM(IOS_FAMILY)(defined 1 && 1)
1371 , m_telephoneNumberDetectionUpdateTimer(*this, &Editor::scanSelectionForTelephoneNumbers, 0_s)
1372#endif
1373{
1374}
1375
1376Editor::~Editor() = default;
1377
1378void Editor::ref() const
1379{
1380 m_document->ref();
1381}
1382
1383void Editor::deref() const
1384{
1385 m_document->deref();
1386}
1387
1388void Editor::clear()
1389{
1390 m_lastEditCommand = nullptr;
1391 if (m_compositionNode) {
1392 m_compositionNode = nullptr;
1393 if (CheckedPtr client = this->client())
1394 client->discardedComposition(protect(document()));
1395 }
1396 m_customCompositionUnderlines.clear();
1397 m_customCompositionHighlights.clear();
1398 m_customCompositionAnnotations.clear();
1399 m_shouldStyleWithCSS = false;
1400 m_defaultParagraphSeparator = EditorParagraphSeparator::div;
1401 m_mark = { };
1402 m_oldSelectionForEditorUIUpdate = { };
1403 m_editorUIUpdateTimer.stop();
1404 m_alternativeTextController->stopAlternativeTextUITimer();
1405
1406#if ENABLE(TELEPHONE_NUMBER_DETECTION)(defined 1 && 1) && !PLATFORM(IOS_FAMILY)(defined 1 && 1)
1407 m_telephoneNumberDetectionUpdateTimer.stop();
1408 m_detectedTelephoneNumberRanges.clear();
1409#endif
1410}
1411
1412bool Editor::insertText(const String& text, Event* triggeringEvent, TextEventInputType inputType)
1413{
1414 return document().frame()->eventHandler().handleTextInputEvent(text, triggeringEvent, inputType);
1415}
1416
1417bool Editor::insertTextForConfirmedComposition(const String& text)
1418{
1419 return document().frame()->eventHandler().handleTextInputEvent(text, 0, TextEventInputComposition);
1420}
1421
1422bool Editor::insertDictatedText(const String& text, const Vector<DictationAlternative>& dictationAlternatives, Event* triggeringEvent)
1423{
1424 return m_alternativeTextController->insertDictatedText(text, dictationAlternatives, triggeringEvent);
1425}
1426
1427bool Editor::insertTextWithoutSendingTextEvent(const String& text, bool selectInsertedText, TextEvent* triggeringEvent)
1428{
1429 if (text.isEmpty())
1430 return false;
1431
1432 VisibleSelection selection = selectionForCommand(triggeringEvent);
1433 if (!selection.isContentEditable())
1434 return false;
1435
1436 if (!shouldInsertText(text, selection.toNormalizedRange(), EditorInsertAction::Typed))
1437 return true;
1438
1439 // FIXME: Should pass false to updateMarkersForWordsAffectedByEditing() to not remove markers if
1440 // a leading or trailing no-break space is being inserted. See <https://webkit.org/b/212098>.
1441 bool isStartOfNewWord = deprecatedIsSpaceOrNewline(selection.visibleStart().characterBefore());
1442 updateMarkersForWordsAffectedByEditing(deprecatedIsSpaceOrNewline(text[0]) || isStartOfNewWord);
1443
1444 bool shouldConsiderApplyingAutocorrection = false;
1445 if (text == " "_s || text == "\t"_s)
1446 shouldConsiderApplyingAutocorrection = true;
1447
1448 if (text.length() == 1 && u_ispunct(text[0]) && !isAmbiguousBoundaryCharacter(text[0]))
1449 shouldConsiderApplyingAutocorrection = true;
1450
1451 bool autocorrectionWasApplied = shouldConsiderApplyingAutocorrection && didApplyAutocorrection(document(), m_alternativeTextController);
1452
1453 // Get the selection to use for the event that triggered this insertText.
1454 // If the event handler changed the selection, we may want to use a different selection
1455 // that is contained in the event target.
1456 selection = selectionForCommand(triggeringEvent);
1457 if (selection.isContentEditable()) {
1458 if (RefPtr selectionStart = selection.start().deprecatedNode()) {
1459 Ref document = selectionStart->document();
1460
1461 // Insert the text
1462 if (triggeringEvent && triggeringEvent->isDictation())
1463 DictationCommand::insertText(document.copyRef(), text, triggeringEvent->dictationAlternatives(), selection);
1464 else {
1465 auto options = OptionSet { TypingCommand::Option::RetainAutocorrectionIndicator };
1466 if (selectInsertedText)
1467 options.add(TypingCommand::Option::SelectInsertedText);
1468 if (triggeringEvent && triggeringEvent->isAutocompletion())
1469 options.add(TypingCommand::Option::IsAutocompletion);
1470 if (shouldRemoveAutocorrectionIndicator(shouldConsiderApplyingAutocorrection, autocorrectionWasApplied, options.contains(TypingCommand::Option::IsAutocompletion)))
1471 options.remove(TypingCommand::Option::RetainAutocorrectionIndicator);
1472 auto compositionType = triggeringEvent && triggeringEvent->isComposition() ? TypingCommand::TextCompositionType::Final : TypingCommand::TextCompositionType::None;
1473 TypingCommand::insertText(document.copyRef(), text, triggeringEvent, selection, options, compositionType);
1474 }
1475
1476 // Reveal the current selection. Note that focus may have changed after insertion.
1477 // FIXME: Selection is allowed even if setIgnoreSelectionChanges(true). Ideally setIgnoreSelectionChanges()
1478 // should be moved from Editor to a page-level like object. If it must remain a frame-specific concept
1479 // then this code should conditionalize revealing selection on whether the ignoreSelectionChanges() bit
1480 // is set for the newly focused frame.
1481 if ((!triggeringEvent || triggeringEvent->isTrusted()) && client() && client()->shouldRevealCurrentSelectionAfterInsertion()) {
1482 if (RefPtr page = document->page())
1483 page->revealCurrentSelection();
1484 }
1485 }
1486 }
1487
1488 return true;
1489}
1490
1491bool Editor::insertLineBreak()
1492{
1493 if (!canEdit())
1494 return false;
1495
1496 Ref document = this->document();
1497 if (!shouldInsertText("\n"_s, document->selection().selection().toNormalizedRange(), EditorInsertAction::Typed))
1498 return true;
1499
1500 VisiblePosition caret = document->selection().selection().visibleStart();
1501 bool alignToEdge = isEndOfEditableOrNonEditableContent(caret);
1502 bool autocorrectionIsApplied = m_alternativeTextController->applyAutocorrectionBeforeTypingIfAppropriate();
1503 TypingCommand::insertLineBreak(WTF::move(document), autocorrectionIsApplied ? TypingCommand::Option::RetainAutocorrectionIndicator : OptionSet<TypingCommand::Option> { });
1504 revealSelectionAfterEditingOperation(alignToEdge ? ScrollAlignment::alignToEdgeIfNeeded : ScrollAlignment::alignCenterIfNeeded);
1505
1506 return true;
1507}
1508
1509bool Editor::insertParagraphSeparator()
1510{
1511 if (!canEdit())
1512 return false;
1513
1514 if (!canEditRichly())
1515 return insertLineBreak();
1516
1517 Ref document = this->document();
1518 if (!shouldInsertText("\n"_s, document->selection().selection().toNormalizedRange(), EditorInsertAction::Typed))
1519 return true;
1520
1521 VisiblePosition caret = document->selection().selection().visibleStart();
1522 bool alignToEdge = isEndOfEditableOrNonEditableContent(caret);
1523 bool autocorrectionIsApplied = m_alternativeTextController->applyAutocorrectionBeforeTypingIfAppropriate();
1524 TypingCommand::insertParagraphSeparator(WTF::move(document), autocorrectionIsApplied ? TypingCommand::Option::RetainAutocorrectionIndicator : OptionSet<TypingCommand::Option> { });
1525 revealSelectionAfterEditingOperation(alignToEdge ? ScrollAlignment::alignToEdgeIfNeeded : ScrollAlignment::alignCenterIfNeeded);
1526
1527 return true;
1528}
1529
1530bool Editor::insertParagraphSeparatorInQuotedContent()
1531{
1532 // FIXME: Why is this missing calls to canEdit, canEditRichly, etc.?
1533 TypingCommand::insertParagraphSeparatorInQuotedContent(protect(document()));
1534 revealSelectionAfterEditingOperation();
1535 return true;
1536}
1537
1538void Editor::cut(FromMenuOrKeyBinding fromMenuOrKeyBinding)
1539{
1540 SetForScope copyScope { m_copyingFromMenuOrKeyBinding, fromMenuOrKeyBinding == FromMenuOrKeyBinding::Yes };
1541 if (tryDHTMLCut())
1542 return; // DHTML did the whole operation
1543 if (!canCut()) {
1544 SystemSoundManager::singleton().systemBeep();
1545 return;
1546 }
1547
1548 performCutOrCopy(CutAction);
1549}
1550
1551void Editor::copy(FromMenuOrKeyBinding fromMenuOrKeyBinding)
1552{
1553 SetForScope copyScope { m_copyingFromMenuOrKeyBinding, fromMenuOrKeyBinding == FromMenuOrKeyBinding::Yes };
1554 if (tryDHTMLCopy())
1555 return; // DHTML did the whole operation
1556 if (!canCopy()) {
1557 SystemSoundManager::singleton().systemBeep();
1558 return;
1559 }
1560
1561 performCutOrCopy(CopyAction);
1562}
1563
1564void Editor::copyFont(FromMenuOrKeyBinding fromMenuOrKeyBinding)
1565{
1566 SetForScope copyScope { m_copyingFromMenuOrKeyBinding, fromMenuOrKeyBinding == FromMenuOrKeyBinding::Yes };
1567 if (tryDHTMLCopy())
1568 return; // DHTML did the whole operation
1569 if (!canCopyFont()) {
1570 SystemSoundManager::singleton().systemBeep();
1571 return;
1572 }
1573
1574 willWriteSelectionToPasteboard(selectedRange());
1575 platformCopyFont();
1576 didWriteSelectionToPasteboard();
1577}
1578
1579bool Editor::canCopyFont() const
1580{
1581 auto& selection = document().selection().selection();
1582 if (selection.isNone() || (selection.isInPasswordField() && !selection.isInAutoFilledAndViewableField()))
1583 return false;
1584 return selection.isRange() || selection.isContentEditable();
1585}
1586
1587void Editor::postTextStateChangeNotificationForCut(const String& text, const VisibleSelection& selection)
1588{
1589 if (!AXObjectCache::accessibilityEnabled())
1590 return;
1591 if (!text.length())
1592 return;
1593 CheckedPtr cache = document().existingAXObjectCache();
1594 if (!cache)
1595 return;
1596 cache->postTextStateChangeNotification(selection.start().anchorNode(), AXTextEditType::Cut, text, selection.start());
1597}
1598
1599void Editor::performCutOrCopy(EditorActionSpecifier action)
1600{
1601 auto selection = selectedRange();
1602 willWriteSelectionToPasteboard(selection);
1603 if (action == CutAction) {
1604 if (!shouldDeleteRange(selection))
1605 return;
1606
1607 updateMarkersForWordsAffectedByEditing(true);
1608 }
1609
1610 Ref document = this->document();
1611 if (enclosingTextFormControl(document->selection().selection().start()))
1612 Pasteboard::createForCopyAndPaste(PagePasteboardContext::create(document->pageID()))->writePlainText(selectedTextForDataTransfer(), canSmartCopyOrDelete() ? Pasteboard::CanSmartReplace : Pasteboard::CannotSmartReplace);
1613 else {
1614 RefPtr<HTMLImageElement> imageElement;
1615 bool isSelectionInImageOverlay = selection && !selection->collapsed() && ImageOverlay::isInsideOverlay(*selection);
1616 if (action == CopyAction && !isSelectionInImageOverlay)
1617 imageElement = imageElementFromImageDocument(document);
1618
1619 if (imageElement) {
1620#if !PLATFORM(WIN)(defined WTF_PLATFORM_WIN && WTF_PLATFORM_WIN)
1621 writeImageToPasteboard(*Pasteboard::createForCopyAndPaste(PagePasteboardContext::create(document->pageID())), *imageElement, document->url(), document->title());
1622#else
1623 // FIXME: Delete after <http://webkit.org/b/177618> lands.
1624 Pasteboard::createForCopyAndPaste(PagePasteboardContext::create(document->pageID()))->writeImage(*imageElement, document->url(), document->title());
1625#endif
1626 } else {
1627#if !PLATFORM(WIN)(defined WTF_PLATFORM_WIN && WTF_PLATFORM_WIN)
1628 writeSelectionToPasteboard(*Pasteboard::createForCopyAndPaste(PagePasteboardContext::create(document->pageID())));
1629#else
1630 // FIXME: Delete after <http://webkit.org/b/177618> lands.
1631 Pasteboard::createForCopyAndPaste(PagePasteboardContext::create(document->pageID()))->writeSelection(selection, canSmartCopyOrDelete(), *document->frame(), IncludeImageAltTextForDataTransfer);
1632#endif
1633 }
1634 }
1635
1636 didWriteSelectionToPasteboard();
1637 if (action == CutAction) {
1638 String text;
1639 if (AXObjectCache::accessibilityEnabled())
1640 text = AccessibilityObject::stringForVisiblePositionRange(document->selection().selection());
1641 deleteSelectionWithSmartDelete(canSmartCopyOrDelete(), EditAction::Cut);
1642 if (AXObjectCache::accessibilityEnabled())
1643 postTextStateChangeNotificationForCut(text, document->selection().selection());
1644 }
1645}
1646
1647void Editor::paste(FromMenuOrKeyBinding fromMenuOrKeyBinding)
1648{
1649 paste(*Pasteboard::createForCopyAndPaste(PagePasteboardContext::create(document().pageID())), fromMenuOrKeyBinding);
1650}
1651
1652void Editor::paste(Pasteboard& pasteboard, FromMenuOrKeyBinding fromMenuOrKeyBinding)
1653{
1654 SetForScope pasteScope { m_pastingFromMenuOrKeyBinding, fromMenuOrKeyBinding == FromMenuOrKeyBinding::Yes };
1655
1656 if (!dispatchClipboardEvent(findEventTargetFromSelection(), ClipboardEventKind::Paste))
1657 return; // DHTML did the whole operation
1658 if (!canEdit())
1659 return;
1660 updateMarkersForWordsAffectedByEditing(false);
1661 ResourceCacheValidationSuppressor validationSuppressor(document().cachedResourceLoader());
1662 if (document().selection().selection().isContentRichlyEditable())
1663 pasteWithPasteboard(&pasteboard, { PasteOption::AllowPlainText });
1664 else
1665 pasteAsPlainTextWithPasteboard(pasteboard);
1666}
1667
1668void Editor::pasteAsPlainText(FromMenuOrKeyBinding fromMenuOrKeyBinding)
1669{
1670 SetForScope pasteScope { m_pastingFromMenuOrKeyBinding, fromMenuOrKeyBinding == FromMenuOrKeyBinding::Yes };
1671
1672 if (!dispatchClipboardEvent(findEventTargetFromSelection(), ClipboardEventKind::PasteAsPlainText))
1673 return;
1674 if (!canEdit())
1675 return;
1676 updateMarkersForWordsAffectedByEditing(false);
1677 pasteAsPlainTextWithPasteboard(*Pasteboard::createForCopyAndPaste(PagePasteboardContext::create(document().pageID())));
1678}
1679
1680void Editor::pasteAsQuotation(FromMenuOrKeyBinding fromMenuOrKeyBinding)
1681{
1682 SetForScope pasteScope { m_pastingFromMenuOrKeyBinding, fromMenuOrKeyBinding == FromMenuOrKeyBinding::Yes };
1683
1684 if (!dispatchClipboardEvent(findEventTargetFromSelection(), ClipboardEventKind::PasteAsQuotation))
1685 return;
1686 if (!canEdit())
1687 return;
1688 updateMarkersForWordsAffectedByEditing(false);
1689 Ref document = this->document();
1690 ResourceCacheValidationSuppressor validationSuppressor(document->cachedResourceLoader());
1691 auto pasteboard = Pasteboard::createForCopyAndPaste(PagePasteboardContext::create(document->pageID()));
1692 if (document->selection().selection().isContentRichlyEditable())
1693 pasteWithPasteboard(pasteboard.get(), { PasteOption::AllowPlainText, PasteOption::AsQuotation });
1694 else
1695 pasteAsPlainTextWithPasteboard(*pasteboard);
1696}
1697
1698void Editor::pasteFont(FromMenuOrKeyBinding fromMenuOrKeyBinding)
1699{
1700 SetForScope pasteScope { m_pastingFromMenuOrKeyBinding, fromMenuOrKeyBinding == FromMenuOrKeyBinding::Yes };
1701
1702 if (!dispatchClipboardEvent(findEventTargetFromSelection(), ClipboardEventKind::PasteFont))
1703 return;
1704 if (!canEdit())
1705 return;
1706 updateMarkersForWordsAffectedByEditing(false);
1707 ResourceCacheValidationSuppressor validationSuppressor(document().cachedResourceLoader());
1708 platformPasteFont();
1709}
1710
1711void Editor::quoteFragmentForPasting(DocumentFragment& fragment)
1712{
1713 Ref document = this->document();
1714 auto blockQuote = HTMLQuoteElement::create(blockquoteTag, document);
1715 blockQuote->setAttributeWithoutSynchronization(typeAttr, "cite"_s);
1716 blockQuote->setAttributeWithoutSynchronization(classAttr, ApplePasteAsQuotation);
1717
1718 RefPtr childNode = fragment.firstChild();
1719
1720 if (childNode) {
1721 while (childNode) {
1722 blockQuote->appendChild(*childNode);
1723 childNode = fragment.firstChild();
1724 }
1725 } else
1726 blockQuote->appendChild(HTMLBRElement::create(document));
1727
1728 fragment.appendChild(blockQuote);
1729}
1730
1731void Editor::performDelete()
1732{
1733 if (!canDelete()) {
1734 SystemSoundManager::singleton().systemBeep();
1735 return;
1736 }
1737
1738 addRangeToKillRing(*selectedRange(), KillRingInsertionMode::AppendText);
1739 deleteSelectionWithSmartDelete(canSmartCopyOrDelete());
1740
1741 // clear the "start new kill ring sequence" setting, because it was set to true
1742 // when the selection was updated by deleting the range
1743 setStartNewKillRingSequence(false);
1744}
1745
1746void Editor::changeSelectionListType()
1747{
1748 Ref document = this->document();
1749 if (auto type = ChangeListTypeCommand::listConversionType(document))
1750 ChangeListTypeCommand::create(WTF::move(document), *type)->apply();
1751}
1752
1753void Editor::simplifyMarkup(Node* startNode, Node* endNode)
1754{
1755 if (!startNode)
1756 return;
1757
1758 RefPtr<Node> pastEndNode;
1759 if (endNode) {
1760 if (!is_lt(treeOrder(*startNode, *endNode)))
1761 return;
1762 pastEndNode = NodeTraversal::next(*endNode);
1763 }
1764
1765 SimplifyMarkupCommand::create(protect(document()), startNode, pastEndNode.get())->apply();
1766}
1767
1768void Editor::copyURL(const URL& url, const String& title)
1769{
1770 copyURL(url, title, *Pasteboard::createForCopyAndPaste(PagePasteboardContext::create(document().pageID())));
1771}
1772
1773void Editor::copyURL(const URL& url, const String& title, Pasteboard& pasteboard)
1774{
1775 auto sanitizedURL = url;
1776 if (RefPtr page = document().page())
1777 sanitizedURL = page->applyLinkDecorationFiltering(url, LinkDecorationFilteringTrigger::Copy);
1778
1779 PasteboardURL pasteboardURL;
1780 pasteboardURL.url = sanitizedURL;
1781 pasteboardURL.title = title;
1782
1783#if PLATFORM(MAC)(defined WTF_PLATFORM_MAC && WTF_PLATFORM_MAC)
1784 pasteboardURL.userVisibleForm = userVisibleString(sanitizedURL);
1785#endif
1786
1787 pasteboard.write(pasteboardURL);
1788}
1789
1790PasteboardWriterData::URLData Editor::pasteboardWriterURL(const URL& url, const String& title)
1791{
1792 PasteboardWriterData::URLData result;
1793
1794 result.url = url;
1795 result.title = title;
1796#if PLATFORM(MAC)(defined WTF_PLATFORM_MAC && WTF_PLATFORM_MAC)
1797 result.userVisibleForm = userVisibleString(url);
1798#endif
1799
1800 return result;
1801}
1802
1803#if !PLATFORM(IOS_FAMILY)(defined 1 && 1)
1804
1805void Editor::copyImage(const HitTestResult& result)
1806{
1807 RefPtr element { result.innerNonSharedElement() };
1808 if (!element)
1809 return;
1810
1811 URL url = result.absoluteLinkURL();
1812 if (url.isEmpty())
1813 url = result.absoluteImageURL();
1814
1815#if !PLATFORM(WIN)(defined WTF_PLATFORM_WIN && WTF_PLATFORM_WIN)
1816 writeImageToPasteboard(*Pasteboard::createForCopyAndPaste(PagePasteboardContext::create(document().pageID())), *element, url, result.altDisplayString());
1817#else
1818 // FIXME: Delete after <http://webkit.org/b/177618> lands.
1819 Pasteboard::createForCopyAndPaste(PagePasteboardContext::create(document().pageID()))->writeImage(*element, url, result.altDisplayString());
1820#endif
1821}
1822
1823#endif
1824
1825void Editor::revealSelectionIfNeededAfterLoadingImageForElement(HTMLImageElement& element)
1826{
1827 if (m_imageElementsToLoadBeforeRevealingSelection.isEmpty())
1828 return;
1829
1830 if (!m_imageElementsToLoadBeforeRevealingSelection.remove(&element))
1831 return;
1832
1833 if (!m_imageElementsToLoadBeforeRevealingSelection.isEmpty())
1834 return;
1835
1836 // FIXME: This should be queued as a task for the next rendering update.
1837 Ref document = this->document();
1838 document->updateLayout();
1839 document->selection().setCaretRectNeedsUpdate();
1840 revealSelectionAfterEditingOperation();
1841}
1842
1843void Editor::renderLayerDidScroll(const RenderLayer& layer)
1844{
1845 if (m_imageElementsToLoadBeforeRevealingSelection.isEmpty())
1846 return;
1847
1848 RefPtr startContainer { document().selection().selection().start().containerNode() };
1849 if (!startContainer)
1850 return;
1851
1852 CheckedPtr startContainerRenderer = startContainer->renderer();
1853 if (!startContainerRenderer)
1854 return;
1855
1856 // FIXME: Ideally, this would also cancel deferred selection revealing if the selection is inside a subframe and a parent frame is scrolled.
1857 for (CheckedPtr enclosingLayer = startContainerRenderer->enclosingLayer(); enclosingLayer; enclosingLayer = enclosingLayer->parent()) {
1858 if (enclosingLayer.get() == &layer) {
1859 m_imageElementsToLoadBeforeRevealingSelection.clear();
1860 break;
1861 }
1862 }
1863}
1864
1865bool Editor::isContinuousSpellCheckingEnabled() const
1866{
1867 return client() && client()->isContinuousSpellCheckingEnabled();
1868}
1869
1870void Editor::toggleContinuousSpellChecking()
1871{
1872 if (client())
1873 client()->toggleContinuousSpellChecking();
1874}
1875
1876bool Editor::isGrammarCheckingEnabled()
1877{
1878 return client() && client()->isGrammarCheckingEnabled();
1879}
1880
1881void Editor::toggleGrammarChecking()
1882{
1883 if (client())
1884 client()->toggleGrammarChecking();
1885}
1886
1887int Editor::spellCheckerDocumentTag()
1888{
1889 return client() ? client()->spellCheckerDocumentTag() : 0;
1890}
1891
1892#if USE(APPKIT)(defined USE_APPKIT && USE_APPKIT)
1893
1894void Editor::uppercaseWord()
1895{
1896 if (client())
1897 client()->uppercaseWord();
1898}
1899
1900void Editor::lowercaseWord()
1901{
1902 if (client())
1903 client()->lowercaseWord();
1904}
1905
1906void Editor::capitalizeWord()
1907{
1908 if (client())
1909 client()->capitalizeWord();
1910}
1911
1912bool Editor::canApplyCaseTransformations(const String& selection)
1913{
1914 if (client())
1915 return client()->canApplyCaseTransformations(selection);
1916
1917 return true;
1918}
1919
1920bool Editor::canConvertToSimplifiedChinese(const String& selection)
1921{
1922 if (client())
1923 return client()->canConvertToSimplifiedChinese(selection);
1924
1925 return false;
1926}
1927
1928bool Editor::canConvertToTraditionalChinese(const String& selection)
1929{
1930 if (client())
1931 return client()->canConvertToTraditionalChinese(selection);
1932
1933 return false;
1934}
1935
1936void Editor::convertToTraditionalChinese()
1937{
1938 if (client())
1939 client()->convertToTraditionalChinese();
1940}
1941
1942void Editor::convertToSimplifiedChinese()
1943{
1944 if (client())
1945 client()->convertToSimplifiedChinese();
1946}
1947
1948#endif
1949
1950#if USE(AUTOMATIC_TEXT_REPLACEMENT)(defined USE_AUTOMATIC_TEXT_REPLACEMENT && USE_AUTOMATIC_TEXT_REPLACEMENT
)
1951
1952void Editor::showSubstitutionsPanel()
1953{
1954 if (!client()) {
1955 LOG_ERROR("No NSSpellChecker")((void)0);
1956 return;
1957 }
1958
1959 if (client()->substitutionsPanelIsShowing()) {
1960 client()->showSubstitutionsPanel(false);
1961 return;
1962 }
1963 client()->showSubstitutionsPanel(true);
1964}
1965
1966bool Editor::substitutionsPanelIsShowing()
1967{
1968 if (!client())
1969 return false;
1970 return client()->substitutionsPanelIsShowing();
1971}
1972
1973void Editor::toggleSmartInsertDelete()
1974{
1975 if (client())
1976 client()->toggleSmartInsertDelete();
1977}
1978
1979bool Editor::isAutomaticQuoteSubstitutionEnabled()
1980{
1981 return client() && client()->isAutomaticQuoteSubstitutionEnabled();
1982}
1983
1984void Editor::toggleAutomaticQuoteSubstitution()
1985{
1986 if (client())
1987 client()->toggleAutomaticQuoteSubstitution();
1988}
1989
1990bool Editor::isAutomaticLinkDetectionEnabled()
1991{
1992 return client() && client()->isAutomaticLinkDetectionEnabled();
1993}
1994
1995void Editor::toggleAutomaticLinkDetection()
1996{
1997 if (client())
1998 client()->toggleAutomaticLinkDetection();
1999}
2000
2001bool Editor::isAutomaticDashSubstitutionEnabled()
2002{
2003 return client() && client()->isAutomaticDashSubstitutionEnabled();
2004}
2005
2006void Editor::toggleAutomaticDashSubstitution()
2007{
2008 if (client())
2009 client()->toggleAutomaticDashSubstitution();
2010}
2011
2012bool Editor::isAutomaticTextReplacementEnabled()
2013{
2014 return client() && client()->isAutomaticTextReplacementEnabled();
2015}
2016
2017void Editor::toggleAutomaticTextReplacement()
2018{
2019 if (client())
2020 client()->toggleAutomaticTextReplacement();
2021}
2022
2023bool Editor::canEnableAutomaticSpellingCorrection() const
2024{
2025 return m_alternativeTextController->canEnableAutomaticSpellingCorrection();
2026}
2027
2028bool Editor::isAutomaticSpellingCorrectionEnabled()
2029{
2030 return m_alternativeTextController->isAutomaticSpellingCorrectionEnabled();
2031}
2032
2033void Editor::toggleAutomaticSpellingCorrection()
2034{
2035 if (client())
2036 client()->toggleAutomaticSpellingCorrection();
2037}
2038
2039void Editor::toggleSmartLists()
2040{
2041 Ref document = this->document();
2042 if (!document->settings().smartListsAvailable())
2043 return;
2044
2045 if (client())
2046 client()->toggleSmartLists();
2047}
2048
2049#endif // USE(AUTOMATIC_TEXT_REPLACEMENT)
2050
2051#if PLATFORM(COCOA)(defined 1 && 1)
2052bool Editor::isSmartListsEnabled()
2053{
2054 Ref document = this->document();
2055 if (!document->settings().smartListsAvailable())
2056 return false;
2057
2058#if PLATFORM(MAC)(defined WTF_PLATFORM_MAC && WTF_PLATFORM_MAC)
2059 return client() && client()->isSmartListsEnabled();
2060#else
2061 return true;
2062#endif
2063}
2064#endif // PLATFORM(COCOA)
2065
2066bool Editor::shouldEndEditing(const SimpleRange& range)
2067{
2068 return client() && client()->shouldEndEditing(range);
2069}
2070
2071bool Editor::shouldBeginEditing(const SimpleRange& range)
2072{
2073 return client() && client()->shouldBeginEditing(range);
2074}
2075
2076void Editor::clearUndoRedoOperations()
2077{
2078 if (client())
2079 client()->clearUndoRedoOperations();
2080}
2081
2082bool Editor::canUndo() const
2083{
2084 return client() && client()->canUndo();
2085}
2086
2087void Editor::undo()
2088{
2089 if (client())
2090 client()->undo();
2091}
2092
2093bool Editor::canRedo() const
2094{
2095 return client() && client()->canRedo();
2096}
2097
2098void Editor::redo()
2099{
2100 if (client())
2101 client()->redo();
2102}
2103
2104void Editor::registerCustomUndoStep(Ref<CustomUndoStep>&& undoStep)
2105{
2106 ASSERT(document().settings().undoManagerAPIEnabled())((void)0);
2107 if (CheckedPtr client = this->client())
2108 client->registerUndoStep(WTF::move(undoStep));
2109}
2110
2111void Editor::didBeginEditing()
2112{
2113 if (client())
2114 client()->didBeginEditing();
2115}
2116
2117void Editor::didEndEditing()
2118{
2119 if (client())
2120 client()->didEndEditing();
2121}
2122
2123void Editor::willWriteSelectionToPasteboard(const std::optional<SimpleRange>& range)
2124{
2125 if (client())
2126 client()->willWriteSelectionToPasteboard(range);
2127}
2128
2129void Editor::didWriteSelectionToPasteboard()
2130{
2131 if (client())
2132 client()->didWriteSelectionToPasteboard();
2133}
2134
2135void Editor::toggleBold()
2136{
2137 command("ToggleBold"_s).execute();
2138}
2139
2140void Editor::toggleUnderline()
2141{
2142 command("ToggleUnderline"_s).execute();
2143}
2144
2145void Editor::setTextAlignmentForChangedBaseWritingDirection(WritingDirection direction)
2146{
2147 // Note that the passed-in argument is the direction that has been changed to by
2148 // some code or user interaction outside the scope of this function. The former
2149 // direction is not known, nor is it required for the kind of text alignment
2150 // changes done by this function.
2151 //
2152 // Rules:
2153 // When text has no explicit alignment, set alignment to match the writing direction.
2154 // If the text has left or right alignment, flip left->right and right->left.
2155 // Otherwise, do nothing.
2156
2157 Ref document = this->document();
2158 RefPtr selectionStyle = EditingStyle::styleAtSelectionStart(document->selection().selection());
2159 if (!selectionStyle || !selectionStyle->style())
2160 return;
2161
2162 auto value = selectionStyle->style()->propertyAsValueID(CSSPropertyTextAlign);
2163 if (!value)
2164 return;
2165
2166 auto newValue = CSSValueInvalid;
2167 switch (*value) {
2168 case CSSValueStart:
2169 case CSSValueEnd:
2170 switch (direction) {
2171 case WritingDirection::Natural:
2172 return;
2173 case WritingDirection::LeftToRight:
2174 newValue = CSSValueLeft;
2175 break;
2176 case WritingDirection::RightToLeft:
2177 newValue = CSSValueRight;
2178 break;
2179 default:
2180 ASSERT_NOT_REACHED()((void)0);
2181 return;
2182 }
2183 break;
2184 case CSSValueLeft:
2185 case CSSValueWebkitLeft:
2186 newValue = CSSValueRight;
2187 break;
2188 case CSSValueRight:
2189 case CSSValueWebkitRight:
2190 newValue = CSSValueLeft;
2191 break;
2192 case CSSValueCenter:
2193 case CSSValueWebkitCenter:
2194 case CSSValueJustify:
2195 return;
2196 default:
2197 ASSERT_NOT_REACHED()((void)0);
2198 return;
2199 }
2200
2201 auto isTextControl = [](Element* focusedElement) {
2202 if (RefPtr input = dynamicDowncast<HTMLInputElement>(focusedElement))
2203 return input->isTextField() || input->isSearchField();
2204 return is<HTMLTextAreaElement>(focusedElement);
2205 };
2206
2207 if (RefPtr focusedElement = document->focusedElement(); isTextControl(focusedElement.get())) {
2208 if (direction != WritingDirection::Natural) {
2209 focusedElement->setAttributeWithoutSynchronization(alignAttr, nameString(newValue));
2210 document->updateStyleIfNeeded();
2211 }
2212 return;
2213 }
2214
2215 Ref style = MutableStyleProperties::create();
2216 style->setProperty(CSSPropertyTextAlign, newValue);
2217 applyParagraphStyle(style.ptr());
2218}
2219
2220void Editor::setBaseWritingDirection(WritingDirection direction)
2221{
2222 Ref document = this->document();
2223#if PLATFORM(IOS_FAMILY)(defined 1 && 1)
2224 if (inSameParagraph(document->selection().selection().visibleStart(), document->selection().selection().visibleEnd())
2225 && baseWritingDirectionForSelectionStart() == direction)
2226 return;
2227#endif
2228
2229 if (RefPtr focusedElement = dynamicDowncast<HTMLTextFormControlElement>(document->focusedElement()); focusedElement && focusedElement->isTextField()) {
2230 if (direction == WritingDirection::Natural)
2231 return;
2232
2233 auto directionValue = direction == WritingDirection::LeftToRight ? "ltr"_s : "rtl"_s;
2234 auto writingDirectionInputTypeName = inputTypeNameForEditingAction(EditAction::SetBlockWritingDirection);
2235 if (!dispatchBeforeInputEvent(*focusedElement, writingDirectionInputTypeName, IsInputMethodComposing::No, directionValue))
2236 return;
2237
2238 focusedElement->setAttributeWithoutSynchronization(dirAttr, directionValue);
2239 dispatchInputEvent(*focusedElement, writingDirectionInputTypeName, IsInputMethodComposing::No, directionValue);
2240 document->updateStyleIfNeeded();
2241 return;
2242 }
2243
2244 auto style = MutableStyleProperties::create();
2245 style->setProperty(CSSPropertyDirection, direction == WritingDirection::LeftToRight ? "ltr"_s : direction == WritingDirection::RightToLeft ? "rtl"_s : "inherit"_s);
2246 applyParagraphStyleToSelection(style.ptr(), EditAction::SetBlockWritingDirection);
2247}
2248
2249WritingDirection Editor::baseWritingDirectionForSelectionStart() const
2250{
2251 auto result = WritingDirection::LeftToRight;
2252
2253 Position pos = document().selection().selection().visibleStart().deepEquivalent();
2254 RefPtr node = pos.deprecatedNode();
2255 if (!node)
2256 return result;
2257
2258 ScriptDisallowedScope::InMainThread scriptDisallowedScope;
2259
2260 CheckedPtr renderer = node->renderer();
2261 if (renderer && !is<RenderBlock>(*renderer))
2262 renderer = renderer->containingBlock();
2263 if (!renderer)
2264 return result;
2265
2266 switch (downcast<RenderBlock>(*renderer).writingMode().bidiDirection()) {
2267 case TextDirection::LTR:
2268 return WritingDirection::LeftToRight;
2269 case TextDirection::RTL:
2270 return WritingDirection::RightToLeft;
2271 }
2272
2273 return result;
2274}
2275
2276void Editor::selectComposition()
2277{
2278 auto range = compositionRange();
2279 if (!range)
2280 return;
2281
2282 // The composition can start inside a composed character sequence, so we have to override checks.
2283 // See <http://bugs.webkit.org/show_bug.cgi?id=15781>
2284 VisibleSelection selection;
2285 selection.setWithoutValidation(makeDeprecatedLegacyPosition(range->start), makeDeprecatedLegacyPosition(range->end));
2286 document().selection().setSelection(selection, { });
2287}
2288
2289Node* Editor::nodeBeforeWritingSuggestions() const
2290{
2291 Ref document = this->document();
2292 if (!document->selection().isCaret())
2293 return nullptr;
2294
2295 auto position = document->selection().selection().end();
2296 RefPtr container = position.containerNode();
2297 if (!container)
2298 return nullptr;
2299
2300 if (auto* text = dynamicDowncast<Text>(container.get()))
2301 return text;
2302
2303 return position.computeNodeBeforePosition();
2304}
2305
2306Element* Editor::writingSuggestionsContainerElement() const
2307{
2308 RefPtr node = nodeBeforeWritingSuggestions();
2309 if (!node)
2310 return nullptr;
2311
2312 return node->parentElement();
2313}
2314
2315void Editor::removeWritingSuggestionIfNeeded()
2316{
2317 Ref document = this->document();
2318 document->updateStyleIfNeeded();
2319
2320 m_customCompositionAnnotations = { };
2321 m_isHandlingAcceptedCandidate = false;
2322
2323 RefPtr selectedElement = writingSuggestionsContainerElement();
2324 if (!selectedElement)
2325 return;
2326
2327 m_writingSuggestionData = nullptr;
2328 selectedElement->invalidateStyleAndRenderersForSubtree();
2329}
2330
2331void Editor::confirmComposition()
2332{
2333#if PLATFORM(MAC)(defined WTF_PLATFORM_MAC && WTF_PLATFORM_MAC)
2334 if (m_isHandlingAcceptedCandidate) {
2335 removeWritingSuggestionIfNeeded();
2336 return;
2337 }
2338#endif
2339
2340 if (!m_compositionNode)
2341 return;
2342 setComposition(compositionText(), ConfirmComposition);
2343}
2344
2345String Editor::compositionText() const
2346{
2347 if (!m_compositionNode)
2348 return { };
2349
2350 return protect(compositionNode())->data().substring(m_compositionStart, m_compositionEnd - m_compositionStart);
2351}
2352
2353bool Editor::hasDeadKeyComposition() const
2354{
2355 if (!m_compositionNode)
2356 return false;
2357
2358 if (m_compositionStart + 1 != m_compositionEnd)
2359 return false;
2360
2361 auto compositionText = this->compositionText();
2362 return compositionText.length() == 1 && isLetterOrSymbolModifier(compositionText[0]);
2363}
2364
2365void Editor::confirmOrCancelCompositionAndNotifyClient()
2366{
2367 if (!hasComposition())
2368 return;
2369
2370 Ref document = this->document();
2371 RefPtr frame = document->frame();
2372 if (!frame)
2373 return;
2374
2375 if (cancelCompositionIfSelectionIsInvalid())
2376 return;
2377
2378 confirmComposition();
2379
2380 if (CheckedPtr editorClient = client()) {
2381 editorClient->respondToChangedSelection(frame.get());
2382 editorClient->discardedComposition(document);
2383 }
2384}
2385
2386void Editor::cancelComposition()
2387{
2388#if PLATFORM(MAC)(defined WTF_PLATFORM_MAC && WTF_PLATFORM_MAC)
2389 if (m_isHandlingAcceptedCandidate) {
2390 removeWritingSuggestionIfNeeded();
2391 return;
2392 }
2393#endif
2394
2395 if (!m_compositionNode)
2396 return;
2397 setComposition(emptyString(), CancelComposition);
2398}
2399
2400bool Editor::cancelCompositionIfSelectionIsInvalid()
2401{
2402 unsigned start;
2403 unsigned end;
2404 if (!hasComposition() || ignoreSelectionChanges() || getCompositionSelection(start, end))
2405 return false;
2406
2407 cancelComposition();
2408 return true;
2409}
2410
2411void Editor::confirmComposition(const String& text)
2412{
2413 setComposition(text, ConfirmComposition);
2414}
2415
2416class SetCompositionScope {
2417public:
2418 SetCompositionScope(Ref<Document>&& document)
2419 : m_document(WTF::move(document))
2420 , m_typingGestureIndicator(*m_document->frame())
2421 {
2422 m_document->editor().setIgnoreSelectionChanges(true);
2423 }
2424
2425 ~SetCompositionScope()
2426 {
2427 m_document->editor().setIgnoreSelectionChanges(false);
2428 if (CheckedPtr editorClient = m_document->editor().client())
2429 editorClient->didUpdateComposition();
2430 }
2431
2432 RefPtr<Document> m_document;
2433 UserTypingGestureIndicator m_typingGestureIndicator;
2434};
2435
2436void Editor::setComposition(const String& text, SetCompositionMode mode)
2437{
2438 ASSERT(mode == ConfirmComposition || mode == CancelComposition)((void)0);
2439 Ref document = this->document();
2440 SetCompositionScope setCompositionScope(document.copyRef());
2441
2442 if (mode == CancelComposition)
2443 ASSERT(text == emptyString())((void)0);
2444 else
2445 selectComposition();
2446
2447 RefPtr previousCompositionNode = m_compositionNode;
2448 m_compositionNode = nullptr;
2449 m_customCompositionUnderlines.clear();
2450 m_customCompositionHighlights.clear();
2451 m_customCompositionAnnotations.clear();
2452
2453 if (CheckedPtr cache = document->existingAXObjectCache(); cache && previousCompositionNode)
2454 cache->onTextCompositionChange(*previousCompositionNode, CompositionState::Ended, false, text, m_compositionStart, m_isHandlingAcceptedCandidate);
2455
2456 if (document->selection().isNone())
2457 return;
2458
2459 // Always delete the current composition before inserting the finalized composition text if we're confirming our composition.
2460 // Our default behavior (if the beforeinput event is not prevented) is to insert the finalized composition text back in.
2461 // We pass TypingCommand::TextCompositionType::Pending here to indicate that we are deleting the pending composition.
2462 if (mode != CancelComposition)
2463 TypingCommand::deleteSelection(document.copyRef(), { }, TypingCommand::TextCompositionType::Pending);
2464
2465 insertTextForConfirmedComposition(text);
2466
2467 if (RefPtr target = document->focusedElement())
2468 target->dispatchEvent(CompositionEvent::create(eventNames().compositionendEvent, document->windowProxy(), text));
2469
2470 if (mode == CancelComposition) {
2471 // An open typing command that disagrees about current selection would cause issues with typing later on.
2472 TypingCommand::closeTyping(document);
2473 }
2474}
2475
2476void Editor::closeTyping()
2477{
2478 TypingCommand::closeTyping(m_document);
2479}
2480
2481RenderInline* Editor::writingSuggestionRenderer() const
2482{
2483 return m_writingSuggestionRenderer.get();
2484}
2485
2486void Editor::setWritingSuggestionRenderer(RenderInline& renderer)
2487{
2488 m_writingSuggestionRenderer = renderer;
2489}
2490
2491#if PLATFORM(COCOA)(defined 1 && 1)
2492void Editor::setWritingSuggestion(const String& fullTextWithPrediction, const CharacterRange& selection)
2493{
2494 Ref document = this->document();
2495 document->updateStyleIfNeeded();
2496
2497 RefPtr selectedElement = writingSuggestionsContainerElement();
2498 if (!selectedElement)
2499 return;
2500
2501 if (!selectedElement->hasEditableStyle())
2502 return;
2503
2504 auto range = document->selection().selection().firstRange();
2505 if (!range)
2506 return;
2507
2508 if (!range->collapsed())
2509 return;
2510
2511 if (!is<Text>(range->startContainer()))
2512 return;
2513
2514 range->start.offset = 0;
2515
2516 m_isHandlingAcceptedCandidate = true;
2517
2518 auto newText = fullTextWithPrediction.substring(0, selection.location);
2519 auto suggestionText = fullTextWithPrediction.substring(selection.location);
2520
2521 auto currentText = m_writingSuggestionData ? m_writingSuggestionData->currentText() : emptyString();
2522
2523 ASSERT(newText.isEmpty() || newText.startsWith(currentText))((void)0);
2524 auto textDelta = newText.isEmpty() ? emptyString() : newText.substring(currentText.length());
2525
2526 auto offset = range->endOffset();
2527 auto offsetWithDelta = currentText.isEmpty() ? offset : offset + textDelta.length();
2528
2529 if (!suggestionText.isEmpty()) {
2530 String originalPrefix;
2531 String originalSuffix;
2532 if (m_writingSuggestionData) {
2533 originalPrefix = m_writingSuggestionData->originalPrefix();
2534 originalSuffix = m_writingSuggestionData->originalSuffix();
2535 } else {
2536 originalPrefix = WebCore::plainTextReplacingNoBreakSpace(*range);
2537 originalSuffix = suggestionText;
2538 }
2539
2540 m_writingSuggestionData = makeUnique<WritingSuggestionData>(WTF::move(suggestionText), WTF::move(newText), WTF::move(offsetWithDelta), WTF::move(originalPrefix), WTF::move(originalSuffix), Editor::writingSuggestionsSupportsSuffix());
2541 } else
2542 m_writingSuggestionData = nullptr;
2543
2544 if (!currentText.isEmpty()) {
2545 SetForScope isInsertingTextForWritingSuggestionScope { m_isInsertingTextForWritingSuggestion, true };
2546 insertText(textDelta, nullptr, TextEventInputKeyboard);
2547 } else
2548 selectedElement->invalidateStyleAndRenderersForSubtree();
2549}
2550#endif
2551
2552void Editor::setComposition(const String& text, const Vector<CompositionUnderline>& underlines, const Vector<CompositionHighlight>& highlights, const HashMap<String, Vector<CharacterRange>>& annotations, unsigned selectionStart, unsigned selectionEnd)
2553{
2554 Ref document = this->document();
2555 SetCompositionScope setCompositionScope(document.copyRef());
2556
2557 // Updates styles before setting selection for composition to prevent
2558 // inserting the previous composition text into text nodes oddly.
2559 // See https://bugs.webkit.org/show_bug.cgi?id=46868
2560 document->updateStyleIfNeeded();
2561
2562 selectComposition();
2563
2564 if (document->selection().isNone())
2565 return;
2566
2567 String originalText = selectedText();
2568 bool isStartingToRecomposeExistingRange = !text.isEmpty() && selectionStart < selectionEnd && !hasComposition();
2569 if (isStartingToRecomposeExistingRange) {
2570 // We pass TypingCommand::TextCompositionType::Final here to indicate that we are removing composition text that has been finalized.
2571 TypingCommand::deleteSelection(document.copyRef(), { }, TypingCommand::TextCompositionType::Final);
2572 const VisibleSelection& currentSelection = document->selection().selection();
2573 if (currentSelection.isRange()) {
2574 // If deletion was prevented, then we need to collapse the selection to the end so that the original text will not be recomposed.
2575 document->selection().setSelection({ currentSelection.end(), currentSelection.end() });
2576 }
2577 }
2578
2579#if PLATFORM(IOS_FAMILY)(defined 1 && 1)
2580 client()->startDelayingAndCoalescingContentChangeNotifications();
2581#endif
2582
2583 RefPtr<CompositionEvent> event;
2584 RefPtr target { document->focusedElement() };
2585 if (target) {
2586 // Dispatch an appropriate composition event to the focused node.
2587 // We check the composition status and choose an appropriate composition event since this
2588 // function is used for three purposes:
2589 // 1. Starting a new composition.
2590 // Send a compositionstart and a compositionupdate event when this function creates
2591 // a new composition node, i.e.
2592 // m_compositionNode == 0 && !text.isEmpty().
2593 // Sending a compositionupdate event at this time ensures that at least one
2594 // compositionupdate event is dispatched.
2595 // 2. Updating the existing composition node.
2596 // Send a compositionupdate event when this function updates the existing composition
2597 // node, i.e. m_compositionNode != 0 && !text.isEmpty().
2598 // 3. Canceling the ongoing composition.
2599 // Send a compositionend event when function deletes the existing composition node, i.e.
2600 // m_compositionNode != 0 && test.isEmpty().
2601 if (!m_compositionNode) {
2602 // We should send a compositionstart event only when the given text is not empty because this
2603 // function doesn't create a composition node when the text is empty.
2604 if (!text.isEmpty()) {
2605 // When an inline predicition is being offered, there will be text and a non-zero amount of highlights.
2606 m_isHandlingAcceptedCandidate = !highlights.isEmpty();
2607
2608 target->dispatchEvent(CompositionEvent::create(eventNames().compositionstartEvent, document->windowProxy(), originalText));
2609 event = CompositionEvent::create(eventNames().compositionupdateEvent, document->windowProxy(), text);
2610 }
2611 } else if (!text.isEmpty())
2612 event = CompositionEvent::create(eventNames().compositionupdateEvent, document->windowProxy(), text);
2613
2614 if (event)
2615 target->dispatchEvent(*event);
2616 }
2617
2618 // If text is empty, then delete the old composition here. If text is non-empty, InsertTextCommand::input
2619 // will delete the old composition with an optimized replace operation.
2620 if (text.isEmpty()) {
2621 // The absence of text implies that there are currently no inline predicitions being offered.
2622 m_isHandlingAcceptedCandidate = false;
2623
2624 TypingCommand::deleteSelection(document.copyRef(), TypingCommand::Option::PreventSpellChecking, TypingCommand::TextCompositionType::Pending);
2625 if (target)
2626 target->dispatchEvent(CompositionEvent::create(eventNames().compositionendEvent, document->windowProxy(), text));
2627 }
2628
2629 RefPtr previousCompositionNode = m_compositionNode;
2630 m_compositionNode = nullptr;
2631 m_customCompositionUnderlines.clear();
2632 m_customCompositionHighlights.clear();
2633 m_customCompositionAnnotations.clear();
2634
2635 if (!text.isEmpty()) {
2636 TypingCommand::insertText(document.copyRef(), text, event.get(), OptionSet { TypingCommand::Option::SelectInsertedText, TypingCommand::Option::PreventSpellChecking }, TypingCommand::TextCompositionType::Pending);
2637
2638 // Find out what node has the composition now.
2639 Position base = document->selection().selection().base().downstream();
2640 Position extent = document->selection().selection().extent();
2641 RefPtr baseNode = base.deprecatedNode();
2642 unsigned baseOffset = base.deprecatedEditingOffset();
2643 RefPtr extentNode = extent.deprecatedNode();
2644 unsigned extentOffset = extent.deprecatedEditingOffset();
2645
2646 if (RefPtr baseTextNode = dynamicDowncast<Text>(baseNode); baseTextNode && baseNode == extentNode && baseOffset + text.length() == extentOffset) {
2647 m_compositionNode = baseTextNode;
2648 m_compositionStart = baseOffset;
2649 m_compositionEnd = extentOffset;
2650 m_customCompositionUnderlines = underlines;
2651 for (auto& underline : m_customCompositionUnderlines) {
2652 underline.startOffset += baseOffset;
2653 underline.endOffset += baseOffset;
2654 }
2655 m_customCompositionHighlights = highlights;
2656 for (auto& highlight : m_customCompositionHighlights) {
2657 highlight.startOffset += baseOffset;
2658 highlight.endOffset += baseOffset;
2659 }
2660
2661 m_customCompositionAnnotations = annotations;
2662 for (auto it = m_customCompositionAnnotations.begin(); it != m_customCompositionAnnotations.end(); ++it) {
2663 for (auto& range : it->value)
2664 range.location += baseOffset;
2665 }
2666
2667 if (CheckedPtr renderer = baseTextNode->renderer())
2668 renderer->repaint();
2669
2670 unsigned start = std::min(baseOffset + selectionStart, extentOffset);
2671 unsigned end = std::min(std::max(start, baseOffset + selectionEnd), extentOffset);
2672 auto range = SimpleRange { { *baseTextNode, start }, { *baseTextNode, end } };
2673 document->selection().setSelectedRange(range, Affinity::Downstream, FrameSelection::ShouldCloseTyping::No);
2674 }
2675 }
2676
2677 if (CheckedPtr cache = document->existingAXObjectCache()) {
2678 if (previousCompositionNode && previousCompositionNode != m_compositionNode) {
2679 auto state = m_compositionNode ? CompositionState::InProgress : CompositionState::Ended;
2680 cache->onTextCompositionChange(*previousCompositionNode, state, true, text, m_compositionStart, m_isHandlingAcceptedCandidate);
2681 }
2682 if (RefPtr compositionNode = this->compositionNode()) {
2683 auto state = previousCompositionNode ? CompositionState::InProgress : CompositionState::Started;
2684 cache->onTextCompositionChange(*compositionNode, state, true, text, m_compositionStart, m_isHandlingAcceptedCandidate);
2685 }
2686 }
2687
2688#if PLATFORM(IOS_FAMILY)(defined 1 && 1)
2689 client()->stopDelayingAndCoalescingContentChangeNotifications();
2690#endif
2691}
2692
2693void Editor::ignoreSpelling()
2694{
2695 if (!client())
2696 return;
2697
2698 if (auto selectedRange = document().selection().selection().toNormalizedRange())
2699 removeMarkers(*selectedRange, DocumentMarkerType::Spelling);
2700
2701 String text = selectedText();
2702 ASSERT(text.length())((void)0);
2703 textChecker()->ignoreWordInSpellDocument(text);
2704}
2705
2706void Editor::learnSpelling()
2707{
2708 if (!client())
2709 return;
2710
2711 // FIXME: On macOS, when use "learn" button on "Spelling and Grammar" panel, we don't call this function. It should remove misspelling markers around the learned word, see <rdar://problem/5396072>.
2712
2713 if (auto selectedRange = document().selection().selection().toNormalizedRange())
2714 removeMarkers(*selectedRange, DocumentMarkerType::Spelling);
2715
2716 String text = selectedText();
2717 ASSERT(text.length())((void)0);
2718 textChecker()->learnWord(text);
2719}
2720
2721#if !PLATFORM(IOS_FAMILY)(defined 1 && 1)
2722
2723void Editor::advanceToNextMisspelling(bool startBeforeSelection)
2724{
2725 Ref document = this->document();
2726
2727 // The basic approach is to search in two phases: from the selection end to the end of the document, and
2728 // then we wrap and search from the document start to (approximately) where we started.
2729
2730 // Start at the end of the selection, search to edge of document.
2731 // Starting at the selection end makes repeated "check spelling" commands work.
2732 VisibleSelection selection(document->selection().selection());
2733 auto spellingSearchRange = makeRangeSelectingNodeContents(document);
2734
2735 bool startedWithSelection = false;
2736 if (selection.start().deprecatedNode()) {
2737 startedWithSelection = true;
2738 if (startBeforeSelection) {
2739 // Match AppKit's rule: Start 1 character before the selection.
2740 auto start = selection.visibleStart();
2741 auto beforeStart = start.previous();
2742 if (beforeStart.isNotNull())
2743 start = beforeStart;
2744 spellingSearchRange.start = *makeBoundaryPoint(start);
2745 } else
2746 spellingSearchRange.start = *makeBoundaryPoint(selection.visibleEnd());
2747 }
2748
2749 auto position = makeDeprecatedLegacyPosition(spellingSearchRange.start);
2750 if (!isEditablePosition(position)) {
2751 // This shouldn't happen in very often because the Spelling menu items aren't enabled unless the
2752 // selection is editable.
2753 // This can happen in Mail for a mix of non-editable and editable content (like Stationary),
2754 // when spell checking the whole document before sending the message.
2755 // In that case the document might not be editable, but there are editable pockets that need to be spell checked.
2756
2757 position = VisiblePosition(firstEditablePositionAfterPositionInRoot(position, document->documentElement())).deepEquivalent();
2758 if (position.isNull())
2759 return;
2760
2761 if (auto point = makeBoundaryPoint(position.parentAnchoredEquivalent()))
2762 spellingSearchRange.start = *point;
2763 startedWithSelection = false; // won't need to wrap
2764 }
2765
2766 // topNode defines the whole range we want to operate on
2767 RefPtr topNode { highestEditableRoot(position) };
2768 if (topNode)
2769 spellingSearchRange.end = makeBoundaryPointAfterNodeContents(*topNode);
2770
2771 // If spellingSearchRange starts in the middle of a word, advance to the next word so we start checking
2772 // at a word boundary. Going back by one char and then forward by a word does the trick.
2773 if (startedWithSelection) {
2774 auto oneBeforeStart = VisiblePosition(makeContainerOffsetPosition(spellingSearchRange.start)).previous();
2775 if (oneBeforeStart.isNotNull())
2776 spellingSearchRange.start = *makeBoundaryPoint(endOfWord(oneBeforeStart));
2777 // else we were already at the start of the editable node
2778 }
2779
2780 if (spellingSearchRange.collapsed())
2781 return; // nothing to search in
2782
2783 // Get the spell checker if it is available
2784 if (!client())
2785 return;
2786
2787 // We go to the end of our first range instead of the start of it, just to be sure
2788 // we don't get foiled by any word boundary problems at the start. It means we might
2789 // do a tiny bit more searching.
2790 auto searchEndAfterWrap = spellingSearchRange.end;
2791
2792 TextCheckingHelper::MisspelledWord misspelledWord;
2793 TextCheckingHelper::UngrammaticalPhrase ungrammaticalPhrase;
2794 auto grammarSearchRange = spellingSearchRange;
2795
2796 if (unifiedTextCheckerEnabled()) {
2797 auto foundItem = TextCheckingHelper(*client(), spellingSearchRange).findFirstMisspelledWordOrUngrammaticalPhrase(isGrammarCheckingEnabled());
2798 if (auto* word = std::get_if<TextCheckingHelper::MisspelledWord>(&foundItem))
2799 misspelledWord = WTF::move(*word);
2800 else
2801 ungrammaticalPhrase = std::get<TextCheckingHelper::UngrammaticalPhrase>(WTF::move(foundItem));
2802 } else {
2803 misspelledWord = TextCheckingHelper(*client(), spellingSearchRange).findFirstMisspelledWord();
2804
2805 if (!misspelledWord.word.isEmpty()) {
2806 // Stop looking at start of next misspelled word
2807 CharacterIterator characterIterator(grammarSearchRange);
2808 characterIterator.advance(misspelledWord.offset);
2809 grammarSearchRange.end = characterIterator.range().start;
2810 }
2811
2812 if (isGrammarCheckingEnabled())
2813 ungrammaticalPhrase = TextCheckingHelper(*client(), grammarSearchRange).findFirstUngrammaticalPhrase();
2814 }
2815
2816 // If we found neither bad grammar nor a misspelled word, wrap and try again (but don't bother if we started at the beginning of the
2817 // block rather than at a selection).
2818 if (startedWithSelection && misspelledWord.word.isEmpty() && ungrammaticalPhrase.phrase.isEmpty()) {
2819 if (topNode)
2820 spellingSearchRange.start = makeBoundaryPointBeforeNodeContents(*topNode);
2821 spellingSearchRange.end = searchEndAfterWrap;
2822
2823 if (unifiedTextCheckerEnabled()) {
2824 auto foundItem = TextCheckingHelper(*client(), spellingSearchRange).findFirstMisspelledWordOrUngrammaticalPhrase(isGrammarCheckingEnabled());
2825 if (auto* word = std::get_if<TextCheckingHelper::MisspelledWord>(&foundItem))
2826 misspelledWord = WTF::move(*word);
2827 else
2828 ungrammaticalPhrase = std::get<TextCheckingHelper::UngrammaticalPhrase>(WTF::move(foundItem));
2829 } else {
2830 misspelledWord = TextCheckingHelper(*client(), spellingSearchRange).findFirstMisspelledWord();
2831
2832 grammarSearchRange = spellingSearchRange;
2833 if (!misspelledWord.word.isEmpty()) {
2834 // Stop looking at start of next misspelled word
2835 CharacterIterator characterIterator(grammarSearchRange);
2836 characterIterator.advance(misspelledWord.offset);
2837 grammarSearchRange.end = characterIterator.range().start;
2838 }
2839
2840 if (isGrammarCheckingEnabled())
2841 ungrammaticalPhrase = TextCheckingHelper(*client(), grammarSearchRange).findFirstUngrammaticalPhrase();
2842 }
2843 }
2844
2845 if (!ungrammaticalPhrase.phrase.isEmpty()) {
2846 // We found bad grammar. Since we only searched for bad grammar up to the first misspelled word, the bad grammar
2847 // takes precedence and we ignore any potential misspelled word. Select the grammar detail, update the spelling
2848 // panel, and store a marker so we draw the green squiggle later.
2849
2850 ASSERT(ungrammaticalPhrase.phrase.length() > 0)((void)0);
2851 ASSERT(ungrammaticalPhrase.detail.range.length > 0)((void)0);
2852
2853 // FIXME 4859190: This gets confused with doubled punctuation at the end of a paragraph.
2854 auto badGrammarRange = resolveCharacterRange(grammarSearchRange, { ungrammaticalPhrase.offset + ungrammaticalPhrase.detail.range.location, ungrammaticalPhrase.detail.range.length });
2855 document->selection().setSelection(VisibleSelection(badGrammarRange));
2856 document->selection().revealSelection();
2857
2858 client()->updateSpellingUIWithGrammarString(ungrammaticalPhrase.phrase, ungrammaticalPhrase.detail);
2859 addMarker(badGrammarRange, DocumentMarkerType::Grammar, ungrammaticalPhrase.detail.userDescription);
2860 } else if (!misspelledWord.word.isEmpty()) {
2861 // We found a misspelling, but not any earlier bad grammar. Select the misspelling, update the spelling panel, and store
2862 // a marker so we draw the red squiggle later.
2863
2864 auto misspellingRange = resolveCharacterRange(spellingSearchRange, { misspelledWord.offset, misspelledWord.word.length() });
2865 document->selection().setSelection(VisibleSelection(misspellingRange));
2866 document->selection().revealSelection();
2867
2868 client()->updateSpellingUIWithMisspelledWord(misspelledWord.word);
2869 addMarker(misspellingRange, DocumentMarkerType::Spelling);
2870 }
2871}
2872
2873#endif // !PLATFORM(IOS_FAMILY)
2874
2875String Editor::misspelledWordAtCaretOrRange(Node* clickedNode) const
2876{
2877 if (!isContinuousSpellCheckingEnabled() || !clickedNode || !isSpellCheckingEnabledFor(clickedNode))
2878 return String();
2879
2880 VisibleSelection selection = document().selection().selection();
2881 if (!selection.isContentEditable() || selection.isNone())
2882 return String();
2883
2884 VisibleSelection wordSelection(selection.base());
2885 wordSelection.expandUsingGranularity(TextGranularity::WordGranularity);
2886 auto wordRange = wordSelection.toNormalizedRange();
2887 if (!wordRange)
2888 return String();
2889
2890 // In compliance with GTK+ applications, additionally allow to provide suggestions when the current
2891 // selection exactly match the word selection.
2892 if (selection.isRange() && *wordRange != selection.toNormalizedRange())
2893 return String();
2894
2895 String word = plainText(*wordRange);
2896 if (word.isEmpty() || !client())
2897 return String();
2898
2899 int wordLength = word.length();
2900 int misspellingLocation = -1;
2901 int misspellingLength = 0;
2902 textChecker()->checkSpellingOfString(word, &misspellingLocation, &misspellingLength);
2903
2904 return misspellingLength == wordLength ? word : String();
2905}
2906
2907String Editor::misspelledSelectionString() const
2908{
2909 String selectedString = selectedText();
2910 int length = selectedString.length();
2911 if (!length || !client())
2912 return String();
2913
2914 int misspellingLocation = -1;
2915 int misspellingLength = 0;
2916 textChecker()->checkSpellingOfString(selectedString, &misspellingLocation, &misspellingLength);
2917
2918 // The selection only counts as misspelled if the selected text is exactly one misspelled word
2919 if (misspellingLength != length)
2920 return String();
2921
2922 // Update the spelling panel to be displaying this error (whether or not the spelling panel is on screen).
2923 // This is necessary to make a subsequent call to [NSSpellChecker ignoreWord:inSpellDocumentWithTag:] work
2924 // correctly; that call behaves differently based on whether the spelling panel is displaying a misspelling
2925 // or a grammar error.
2926 client()->updateSpellingUIWithMisspelledWord(selectedString);
2927
2928 return selectedString;
2929}
2930
2931Vector<String> Editor::guessesForMisspelledWord(const String& word) const
2932{
2933 ASSERT(word.length())((void)0);
2934
2935 Vector<String> guesses;
2936 if (client())
2937 textChecker()->getGuessesForWord(word, String(), document().selection().selection(), guesses);
2938 return guesses;
2939}
2940
2941TextCheckingGuesses Editor::guessesForMisspelledOrUngrammatical()
2942{
2943 if (unifiedTextCheckerEnabled()) {
2944 std::optional<SimpleRange> range;
2945 auto selection = document().selection().selection();
2946 if (selection.isCaret() && behavior().shouldAllowSpellingSuggestionsWithoutSelection()) {
2947 auto wordSelection = VisibleSelection(selection.base());
2948 wordSelection.expandUsingGranularity(TextGranularity::WordGranularity);
2949 range = wordSelection.toNormalizedRange();
2950 } else
2951 range = selection.toNormalizedRange();
2952 if (!range || !client())
2953 return { };
2954 return TextCheckingHelper(*client(), *range).guessesForMisspelledWordOrUngrammaticalPhrase(isGrammarCheckingEnabled());
2955 }
2956
2957 auto misspelledWord = behavior().shouldAllowSpellingSuggestionsWithoutSelection() ? misspelledWordAtCaretOrRange(document().focusedElement()) : misspelledSelectionString();
2958 if (misspelledWord.isEmpty())
2959 return { };
2960 return { guessesForMisspelledWord(misspelledWord), true, false };
2961}
2962
2963void Editor::showSpellingGuessPanel()
2964{
2965 if (!client()) {
2966 LOG_ERROR("No NSSpellChecker")((void)0);
2967 return;
2968 }
2969
2970 if (client()->spellingUIIsShowing()) {
2971 client()->showSpellingUI(false);
2972 return;
2973 }
2974
2975#if !PLATFORM(IOS_FAMILY)(defined 1 && 1)
2976 advanceToNextMisspelling(true);
2977#endif
2978 client()->showSpellingUI(true);
2979}
2980
2981bool Editor::spellingPanelIsShowing()
2982{
2983 if (!client())
2984 return false;
2985 return client()->spellingUIIsShowing();
2986}
2987
2988void Editor::clearMisspellingsAndBadGrammar(const VisibleSelection& movingSelection)
2989{
2990 if (auto selectedRange = movingSelection.toNormalizedRange())
2991 removeMarkers(*selectedRange, { DocumentMarkerType::Spelling, DocumentMarkerType::Grammar });
2992}
2993
2994void Editor::markMisspellingsAndBadGrammar(const VisibleSelection &movingSelection)
2995{
2996 markMisspellingsAndBadGrammar(movingSelection, isContinuousSpellCheckingEnabled() && isGrammarCheckingEnabled(), movingSelection);
2997}
2998
2999void Editor::markMisspellingsAfterTypingToWord(const VisiblePosition& wordStart, const VisibleSelection& selectionAfterTyping, AllowTextReplacement allowTextReplacement)
3000{
3001 Ref document = this->document();
3002
3003 if (platformOrClientDrivenTextCheckerEnabled())
3004 return;
3005
3006#if PLATFORM(IOS_FAMILY)(defined 1 && 1)
3007 UNUSED_PARAM(selectionAfterTyping)(void)selectionAfterTyping;
3008 UNUSED_PARAM(allowTextReplacement)(void)allowTextReplacement;
3009 OptionSet<TextCheckingType> textCheckingOptions;
3010 if (isContinuousSpellCheckingEnabled())
3011 textCheckingOptions.add(TextCheckingType::Spelling);
3012 if (!textCheckingOptions.contains(TextCheckingType::Spelling))
3013 return;
3014
3015 auto adjacentWords = VisibleSelection(startOfWord(wordStart, WordSide::LeftWordIfOnBoundary), endOfWord(wordStart, WordSide::RightWordIfOnBoundary));
3016 auto adjacentWordRange = adjacentWords.toNormalizedRange();
3017
3018#if ENABLE(POST_EDITING_GRAMMAR_CHECKING)(defined 1 && 1)
3019 if (isGrammarCheckingEnabled()) {
3020 textCheckingOptions.add(TextCheckingType::Grammar);
3021 textCheckingOptions.add(TextCheckingType::Correction);
3022 auto sentenceStart = startOfSentence(wordStart);
3023 auto sentenceEnd = endOfSentence(wordStart);
3024 VisibleSelection fullSentence(sentenceStart, sentenceEnd);
3025 auto fullSentenceRange = fullSentence.toNormalizedRange();
3026 if (!fullSentenceRange)
3027 return;
3028
3029 markAllMisspellingsAndBadGrammarInRanges(textCheckingOptions, adjacentWordRange, adjacentWordRange, fullSentenceRange);
3030 } else
3031#endif
3032 {
3033 markAllMisspellingsAndBadGrammarInRanges(textCheckingOptions, adjacentWordRange, adjacentWordRange, adjacentWordRange);
3034 }
3035#else
3036#if !USE(AUTOMATIC_TEXT_REPLACEMENT)(defined USE_AUTOMATIC_TEXT_REPLACEMENT && USE_AUTOMATIC_TEXT_REPLACEMENT
)
3037 UNUSED_PARAM(allowTextReplacement)(void)allowTextReplacement;
3038#endif
3039
3040 if (unifiedTextCheckerEnabled()) {
3041 m_alternativeTextController->applyPendingCorrection(selectionAfterTyping);
3042
3043 OptionSet<TextCheckingType> textCheckingOptions;
3044
3045 if (isContinuousSpellCheckingEnabled())
3046 textCheckingOptions.add(TextCheckingType::Spelling);
3047
3048#if USE(AUTOMATIC_TEXT_REPLACEMENT)(defined USE_AUTOMATIC_TEXT_REPLACEMENT && USE_AUTOMATIC_TEXT_REPLACEMENT
)
3049 if (allowTextReplacement == AllowTextReplacement::Yes
3050 && (isAutomaticQuoteSubstitutionEnabled()
3051 || isAutomaticLinkDetectionEnabled()
3052 || isAutomaticDashSubstitutionEnabled()
3053 || isAutomaticTextReplacementEnabled()
3054 || (textCheckingOptions.contains(TextCheckingType::Spelling) && isAutomaticSpellingCorrectionEnabled())))
3055 textCheckingOptions.add(TextCheckingType::Replacement);
3056#endif
3057 if (!textCheckingOptions.contains(TextCheckingType::Spelling) && !textCheckingOptions.contains(TextCheckingType::Replacement))
3058 return;
3059
3060 if (isGrammarCheckingEnabled())
3061 textCheckingOptions.add(TextCheckingType::Grammar);
3062
3063 auto sentenceStart = startOfSentence(wordStart);
3064 auto sentenceEnd = endOfSentence(wordStart);
3065 VisibleSelection fullSentence(sentenceStart, sentenceEnd);
3066 auto fullSentenceRange = fullSentence.toNormalizedRange();
3067 if (!fullSentenceRange)
3068 return;
3069
3070 auto spellCheckingStart = wordStart;
3071 auto spellCheckingEnd = wordStart;
3072
3073 // FIXME: The following logic doesn't handle adding spelling markers due to retro sentence corrections when an
3074 // incorrectly spelled range is separated from the start of the current word by a text node inside an element
3075 // with spellcheck disabled. To fix this, we need to refactor markAllMisspellingsAndBadGrammarInRanges so that
3076 // it can handle a list of spelling ranges, alongside the grammar range.
3077 while (sentenceStart < spellCheckingStart) {
3078 auto previousPosition = spellCheckingStart.previous(CannotCrossEditingBoundary);
3079 if (previousPosition.isNull() || previousPosition == spellCheckingStart)
3080 break;
3081
3082 RefPtr container = previousPosition.deepEquivalent().downstream().containerNode();
3083 RefPtr containerElement = dynamicDowncast<Element>(container);
3084 if (!containerElement)
3085 containerElement = container->parentElement();
3086 if (containerElement && !containerElement->isSpellCheckingEnabled())
3087 break;
3088
3089 spellCheckingStart = previousPosition;
3090 }
3091
3092 while (spellCheckingEnd < sentenceEnd) {
3093 auto nextPosition = spellCheckingEnd.next(CannotCrossEditingBoundary);
3094 if (nextPosition.isNull() || nextPosition == spellCheckingEnd)
3095 break;
3096
3097 if (RefPtr containerElement = nextPosition.deepEquivalent().upstream().containerOrParentElement()) {
3098 if (!containerElement->isSpellCheckingEnabled())
3099 break;
3100 }
3101
3102 spellCheckingEnd = nextPosition;
3103 }
3104
3105 auto spellCheckingRange = VisibleSelection(spellCheckingStart, spellCheckingEnd).toNormalizedRange();
3106 if (!spellCheckingRange)
3107 return;
3108
3109 auto adjacentWordRange = intersection(VisibleSelection(startOfWord(wordStart, WordSide::LeftWordIfOnBoundary), endOfWord(wordStart, WordSide::RightWordIfOnBoundary)).toNormalizedRange(), fullSentenceRange);
3110 if (!adjacentWordRange)
3111 return;
3112
3113 // The spelling and grammar markers in these ranges are recomputed. This is because typing a word may
3114 // cause any other part of the current sentence to lose or gain spelling correction markers, due to
3115 // sentence retro correction. As such, we expand the spell checking range to encompass as much of the
3116 // full sentence as we can, respecting boundaries where spellchecking is disabled.
3117 removeMarkers(*fullSentenceRange, DocumentMarkerType::Grammar);
3118 removeMarkers(*spellCheckingRange, DocumentMarkerType::Spelling);
3119 markAllMisspellingsAndBadGrammarInRanges(textCheckingOptions, spellCheckingRange, adjacentWordRange, fullSentenceRange);
3120 return;
3121 }
3122
3123 if (!isContinuousSpellCheckingEnabled())
3124 return;
3125
3126 // Check spelling of one word
3127 auto misspellingRange = markMisspellings(VisibleSelection(startOfWord(wordStart, WordSide::LeftWordIfOnBoundary), endOfWord(wordStart, WordSide::RightWordIfOnBoundary)));
3128
3129 // Autocorrect the misspelled word.
3130 if (!misspellingRange)
3131 return;
3132
3133 if (!isGrammarCheckingEnabled())
3134 return;
3135
3136 // Check grammar of entire sentence
3137 markBadGrammar(VisibleSelection(startOfSentence(wordStart), endOfSentence(wordStart)));
3138#endif
3139}
3140
3141std::optional<SimpleRange> Editor::markMisspellingsOrBadGrammar(const VisibleSelection& selection, bool checkSpelling)
3142{
3143#if !PLATFORM(IOS_FAMILY)(defined 1 && 1)
3144 // This function is called with a selection already expanded to word boundaries.
3145 // Might be nice to assert that here.
3146
3147 // This function is used only for as-you-type checking, so if that's off we do nothing. Note that
3148 // grammar checking can only be on if spell checking is also on.
3149 if (!isContinuousSpellCheckingEnabled())
3150 return std::nullopt;
3151
3152 auto searchRange = selection.toNormalizedRange();
3153 if (!searchRange)
3154 return std::nullopt;
3155
3156 // If we're not in an editable node, bail.
3157 Ref editableNode = searchRange->startContainer();
3158 if (!editableNode->hasEditableStyle())
3159 return std::nullopt;
3160
3161 if (!isSpellCheckingEnabledFor(editableNode.ptr()))
3162 return std::nullopt;
3163
3164 // Get the spell checker if it is available
3165 if (!client())
3166 return std::nullopt;
3167
3168 TextCheckingHelper checker(*client(), *searchRange);
3169 if (checkSpelling)
3170 return checker.markAllMisspelledWords();
3171 if (isGrammarCheckingEnabled())
3172 checker.markAllUngrammaticalPhrases();
3173 return std::nullopt;
3174#else
3175 UNUSED_PARAM(selection)(void)selection;
3176 UNUSED_PARAM(checkSpelling)(void)checkSpelling;
3177 return std::nullopt;
3178#endif
3179}
3180
3181bool Editor::isSpellCheckingEnabledFor(Node* node) const
3182{
3183 if (!node)
3184 return false;
3185 RefPtr element = dynamicDowncast<Element>(*node);
3186 if (!element)
3187 element = node->parentElement();
3188 if (!element)
3189 return false;
3190 if (element->isInUserAgentShadowTree()) {
3191 if (RefPtr textControl = enclosingTextFormControl(firstPositionInOrBeforeNode(element.get())))
3192 return textControl->isSpellCheckingEnabled();
3193 }
3194 return element->isSpellCheckingEnabled();
3195}
3196
3197bool Editor::isSpellCheckingEnabledInFocusedNode() const
3198{
3199 return isSpellCheckingEnabledFor(document().selection().selection().start().deprecatedNode());
3200}
3201
3202std::optional<SimpleRange> Editor::markMisspellings(const VisibleSelection& selection)
3203{
3204 return markMisspellingsOrBadGrammar(selection, true);
3205}
3206
3207void Editor::markBadGrammar(const VisibleSelection& selection)
3208{
3209 markMisspellingsOrBadGrammar(selection, false);
3210}
3211
3212void Editor::markAllMisspellingsAndBadGrammarInRanges(OptionSet<TextCheckingType> textCheckingOptions, const std::optional<SimpleRange>& spellingRange, const std::optional<SimpleRange>& automaticReplacementRange, const std::optional<SimpleRange>& grammarRange)
3213{
3214 if (platformOrClientDrivenTextCheckerEnabled())
3215 return;
3216
3217 ASSERT(unifiedTextCheckerEnabled())((void)0);
3218
3219 // There shouldn't be pending autocorrection at this moment.
3220 ASSERT(!m_alternativeTextController->hasPendingCorrection())((void)0);
3221
3222 bool shouldMarkGrammar = textCheckingOptions.contains(TextCheckingType::Grammar);
3223 bool shouldShowCorrectionPanel = textCheckingOptions.contains(TextCheckingType::ShowCorrectionPanel);
3224
3225 // This function is called with selections already expanded to word boundaries.
3226 if (!client() || !spellingRange || (shouldMarkGrammar && !grammarRange))
3227 return;
3228
3229 // Do not mark spelling or grammer corrections when an inline prediction candidate is currently being offered.
3230 if (m_isHandlingAcceptedCandidate)
3231 return;
3232
3233 // If we're not in an editable node, bail.
3234 Ref editableNode = spellingRange->startContainer();
3235 if (!editableNode->hasEditableStyle())
3236 return;
3237
3238 if (!isSpellCheckingEnabledFor(editableNode.ptr()))
3239 return;
3240
3241 auto& rangeToCheck = shouldMarkGrammar ? *grammarRange : *spellingRange;
3242 TextCheckingParagraph paragraphToCheck(rangeToCheck);
3243 if (paragraphToCheck.isEmpty())
3244 return;
3245
3246 bool asynchronous = document().settings().asynchronousSpellCheckingEnabled() && !shouldShowCorrectionPanel;
3247
3248 // In asynchronous mode, we intentionally check paragraph-wide sentence.
3249 auto resolvedOptions = resolveTextCheckingTypeMask(editableNode, textCheckingOptions);
3250 auto& paragraphRange = paragraphToCheck.paragraphRange();
3251 auto& checkingRange = asynchronous ? paragraphRange : rangeToCheck;
3252 auto& textReplacementRange = automaticReplacementRange ? *automaticReplacementRange : rangeToCheck;
3253 auto request = SpellCheckRequest::create(resolvedOptions, TextCheckingProcessType::TextCheckingProcessIncremental, checkingRange, textReplacementRange, paragraphRange);
3254 if (!request)
3255 return;
3256
3257 if (asynchronous) {
3258 m_spellChecker->requestCheckingFor(request.releaseNonNull());
3259 return;
3260 }
3261
3262 Vector<TextCheckingResult> results;
3263 checkTextOfParagraph(*textChecker(), paragraphToCheck.text(), resolvedOptions, results, document().selection().selection());
3264 markAndReplaceFor(request.releaseNonNull(), results);
3265
3266#if PLATFORM(COCOA)(defined 1 && 1)
3267 bool extendedProofreadingEnabled = document().settings().extendedProofreadingEnabled();
3268
3269 if (shouldMarkGrammar && extendedProofreadingEnabled) {
3270 RefPtr extendedRequest = SpellCheckRequest::create(resolvedOptions, TextCheckingProcessType::TextCheckingProcessIncremental, checkingRange, textReplacementRange, paragraphRange);
3271 if (extendedRequest)
3272 m_spellChecker->requestExtendedCheckingFor(extendedRequest.releaseNonNull(), results);
3273 }
3274#endif
3275}
3276
3277static bool isAutomaticTextReplacementType(TextCheckingType type)
3278{
3279 switch (type) {
3280 case TextCheckingType::None:
3281 case TextCheckingType::Spelling:
3282 case TextCheckingType::Grammar:
3283 return false;
3284 case TextCheckingType::Link:
3285 case TextCheckingType::Quote:
3286 case TextCheckingType::Dash:
3287 case TextCheckingType::Replacement:
3288 case TextCheckingType::Correction:
3289 case TextCheckingType::ShowCorrectionPanel:
3290 return true;
3291 }
3292 ASSERT_NOT_REACHED()((void)0);
3293 return false;
3294}
3295
3296void Editor::replaceRangeForSpellChecking(const SimpleRange& rangeToReplace, const String& replacement)
3297{
3298 SpellingCorrectionCommand::create(rangeToReplace, replacement)->apply();
3299}
3300
3301static void correctSpellcheckingPreservingTextCheckingParagraph(TextCheckingParagraph& paragraph, const SimpleRange& rangeToReplace, const String& replacement, CharacterRange resultCharacterRange)
3302{
3303 Ref scopeNode = downcast<ContainerNode>(paragraph.paragraphRange().startContainer().rootNode());
3304 auto paragraphCharacterRange = characterRange(makeBoundaryPointBeforeNodeContents(scopeNode), paragraph.paragraphRange());
3305
3306 SpellingCorrectionCommand::create(rangeToReplace, replacement)->apply();
3307
3308 // TextCheckingParagraph may be orphaned after SpellingCorrectionCommand mutated DOM.
3309 // See <rdar://10305315>, http://webkit.org/b/89526.
3310
3311 paragraphCharacterRange.length += replacement.length() - resultCharacterRange.length;
3312 auto newParagraphRange = resolveCharacterRange(makeRangeSelectingNodeContents(scopeNode), paragraphCharacterRange);
3313 auto spellCheckingRange = resolveCharacterRange(newParagraphRange, { resultCharacterRange.location, replacement.length() });
3314 paragraph = TextCheckingParagraph(spellCheckingRange, spellCheckingRange, newParagraphRange);
3315}
3316
3317void Editor::markAndReplaceFor(const SpellCheckRequest& request, const Vector<TextCheckingResult>& results)
3318{
3319 Ref document = this->document();
3320
3321 auto textCheckingOptions = request.data().checkingTypes();
3322 TextCheckingParagraph paragraph(request.checkingRange(), request.automaticReplacementRange(), request.paragraphRange());
3323
3324 const bool shouldPerformReplacement = textCheckingOptions.containsAny({ TextCheckingType::Quote, TextCheckingType::Dash, TextCheckingType::Replacement });
3325 const bool shouldMarkSpelling = textCheckingOptions.contains(TextCheckingType::Spelling);
3326 const bool shouldMarkGrammar = textCheckingOptions.contains(TextCheckingType::Grammar);
3327 const bool shouldMarkLink = textCheckingOptions.contains(TextCheckingType::Link);
3328 const bool shouldShowCorrectionPanel = textCheckingOptions.contains(TextCheckingType::ShowCorrectionPanel);
3329 const bool shouldCheckForCorrection = shouldShowCorrectionPanel || textCheckingOptions.contains(TextCheckingType::Correction);
3330#if !USE(AUTOCORRECTION_PANEL)(defined USE_AUTOCORRECTION_PANEL && USE_AUTOCORRECTION_PANEL
)
3331 ASSERT(!shouldShowCorrectionPanel)((void)0);
3332#endif
3333
3334 // Expand the range to encompass entire paragraphs, since text checking needs that much context.
3335 uint64_t selectionOffset = 0;
3336 bool useAmbiguousBoundaryOffset = false;
3337 bool selectionChanged = false;
3338 bool restoreSelectionAfterChange = false;
3339
3340 if (shouldPerformReplacement || shouldMarkSpelling || shouldCheckForCorrection) {
3341 if (document->selection().isCaret()) {
3342 // Attempt to save the caret position so we can restore it later if needed
3343 Position caretPosition = document->selection().selection().end();
3344 selectionOffset = paragraph.offsetTo(caretPosition).releaseReturnValue();
3345 restoreSelectionAfterChange = true;
3346 if (selectionOffset > 0 && selectionOffset <= paragraph.text().length() && isAmbiguousBoundaryCharacter(paragraph.text()[selectionOffset - 1]))
3347 useAmbiguousBoundaryOffset = true;
3348 }
3349 }
3350
3351 int offsetDueToReplacement = 0;
3352 Vector<CharacterRange> previousGrammarRanges;
3353
3354 for (unsigned i = 0; i < results.size(); i++) {
3355 auto spellingRangeEndOffset = paragraph.checkingEnd() + offsetDueToReplacement;
3356 if (!results[i].type.hasExactlyOneBitSet()) {
3357 ASSERT_NOT_REACHED()((void)0);
3358 continue;
3359 }
3360 TextCheckingType resultType = *results[i].type.toSingleValue();
3361 auto resultLocation = results[i].range.location + offsetDueToReplacement;
3362 auto resultLength = results[i].range.length;
3363 auto resultEndLocation = resultLocation + resultLength;
3364 auto automaticReplacementStartLocation = paragraph.automaticReplacementStart();
3365 auto automaticReplacementEndLocation = automaticReplacementStartLocation + paragraph.automaticReplacementLength() + offsetDueToReplacement;
3366 const String& replacement = results[i].replacement;
3367 bool resultEndsAtAmbiguousBoundary = useAmbiguousBoundaryOffset && selectionOffset - 1 <= resultEndLocation;
3368
3369 bool resultRangeIsAcceptableForReplacement = automaticReplacementStartLocation <= resultEndLocation && resultEndLocation <= automaticReplacementEndLocation;
3370 // In this case the result range just has to touch the automatic replacement range, so we can handle replacing non-word text such as punctuation.
3371#if ENABLE(POST_EDITING_GRAMMAR_CHECKING)(defined 1 && 1)
3372 if (!resultRangeIsAcceptableForReplacement && shouldCheckForCorrection && resultType == TextCheckingType::Correction)
3373 resultRangeIsAcceptableForReplacement = results[i].details.size() > 0;
3374 if (!resultRangeIsAcceptableForReplacement && shouldMarkGrammar && shouldCheckForCorrection && resultType == TextCheckingType::Correction) {
3375 resultRangeIsAcceptableForReplacement = previousGrammarRanges.containsIf([&](auto& range) {
3376 return range.location == resultLocation && range.length == resultLength;
3377 });
3378 }
3379#endif
3380
3381 // Only mark misspelling if:
3382 // 1. Current text checking isn't done for autocorrection, in which case shouldMarkSpelling is false.
3383 // 2. Result falls within spellingRange.
3384 // 3. The word in question doesn't end at an ambiguous boundary. For instance, we would not mark
3385 // "wouldn'" as misspelled right after apostrophe is typed.
3386 if (shouldMarkSpelling && !shouldShowCorrectionPanel && resultType == TextCheckingType::Spelling
3387 && resultLocation >= paragraph.checkingStart() && resultEndLocation <= spellingRangeEndOffset && !resultEndsAtAmbiguousBoundary) {
3388 ASSERT(resultLength > 0)((void)0);
3389 auto misspellingRange = paragraph.subrange({ resultLocation, resultLength });
3390 if (!m_alternativeTextController->isSpellingMarkerAllowed(misspellingRange))
3391 continue;
3392 addMarker(misspellingRange, DocumentMarkerType::Spelling, replacement);
3393 } else if (shouldMarkGrammar && resultType == TextCheckingType::Grammar && paragraph.checkingRangeCovers({ resultLocation, resultLength })) {
3394 ASSERT(resultLength > 0)((void)0);
3395 for (auto& detail : results[i].details) {
3396 ASSERT(detail.range.length > 0)((void)0);
3397 if (paragraph.checkingRangeCovers({ resultLocation + detail.range.location, detail.range.length })) {
3398 auto badGrammarRange = paragraph.subrange({ resultLocation + detail.range.location, detail.range.length });
3399 addMarker(badGrammarRange, DocumentMarkerType::Grammar, detail.userDescription);
3400 previousGrammarRanges.append(CharacterRange(resultLocation + detail.range.location, detail.range.length));
3401 }
3402 }
3403 } else if (resultRangeIsAcceptableForReplacement && isAutomaticTextReplacementType(resultType)) {
3404 ASSERT(resultLength > 0)((void)0);
3405
3406 if (shouldShowCorrectionPanel && (resultEndLocation < automaticReplacementEndLocation
3407 || (resultType != TextCheckingType::Replacement && resultType != TextCheckingType::Correction)))
3408 continue;
3409
3410 // Apply replacement if:
3411 // 1. The replacement length is non-zero.
3412 // 2. The result doesn't end at an ambiguous boundary.
3413 // (FIXME: this is required until 6853027 is fixed and text checking can do this for us
3414 bool doReplacement = replacement.length() > 0 && !resultEndsAtAmbiguousBoundary;
3415 auto rangeToReplace = paragraph.subrange({ resultLocation, resultLength });
3416
3417 // Adding links should be done only immediately after they are typed.
3418 if (resultType == TextCheckingType::Link && selectionOffset != resultEndLocation + 1)
3419 continue;
3420
3421 if (!(shouldPerformReplacement || shouldCheckForCorrection || shouldMarkLink) || !doReplacement)
3422 continue;
3423
3424 String replacedString = plainText(rangeToReplace);
3425 bool existingMarkersPermitReplacement = m_alternativeTextController->processMarkersOnTextToBeReplacedByResult(results[i], rangeToReplace, replacedString);
3426#if ENABLE(POST_EDITING_GRAMMAR_CHECKING)(defined 1 && 1)
3427 if (!existingMarkersPermitReplacement && shouldCheckForCorrection && resultType == TextCheckingType::Correction)
3428 existingMarkersPermitReplacement = results[i].details.size() > 0;
3429#endif
3430 if (!existingMarkersPermitReplacement)
3431 continue;
3432
3433 if (shouldShowCorrectionPanel) {
3434 if (resultEndLocation == automaticReplacementEndLocation) {
3435 // We only show the correction panel on the last word.
3436 m_alternativeTextController->show(rangeToReplace, replacement);
3437 break;
3438 }
3439 // If this function is called for showing correction panel, we ignore other correction or replacement.
3440 continue;
3441 }
3442
3443 VisibleSelection selectionToReplace(rangeToReplace);
3444 if (selectionToReplace != document->selection().selection()) {
3445 if (!document->selection().shouldChangeSelection(selectionToReplace))
3446 continue;
3447 }
3448
3449 if (resultType == TextCheckingType::Link) {
3450 document->selection().setSelection(selectionToReplace);
3451 selectionChanged = true;
3452 restoreSelectionAfterChange = false;
3453 if (canEditRichly())
3454 CreateLinkCommand::create(document.copyRef(), replacement)->apply();
3455 } else if (canEdit() && shouldInsertText(replacement, rangeToReplace, EditorInsertAction::Typed)) {
3456 correctSpellcheckingPreservingTextCheckingParagraph(paragraph, rangeToReplace, replacement, { resultLocation, resultLength });
3457
3458 if (CheckedPtr cache = document->existingAXObjectCache()) {
3459 if (RefPtr root = document->selection().selection().rootEditableElement())
3460 cache->onAutocorrectionOccured(*root.get());
3461 }
3462
3463 // Skip all other results for the replaced text.
3464 while (i + 1 < results.size() && results[i + 1].range.location + offsetDueToReplacement <= resultLocation)
3465 i++;
3466
3467 selectionChanged = true;
3468 offsetDueToReplacement += replacement.length() - resultLength;
3469 if (resultLocation < selectionOffset)
3470 selectionOffset += replacement.length() - resultLength;
3471
3472 if (resultType == TextCheckingType::Correction) {
3473 auto replacementRange = paragraph.subrange({ resultLocation, replacement.length() });
3474 m_alternativeTextController->recordAutocorrectionResponse(AutocorrectionResponse::Accepted, replacedString, replacementRange);
3475
3476 // Add a marker so that corrections can easily be undone and won't be re-corrected.
3477 m_alternativeTextController->markCorrection(replacementRange, replacedString);
3478 }
3479 }
3480 }
3481 }
3482
3483 if (selectionChanged) {
3484 TextCheckingParagraph extendedParagraph(WTF::move(paragraph));
3485 // Restore the caret position if we have made any replacements
3486 extendedParagraph.expandRangeToNextEnd();
3487 if (restoreSelectionAfterChange && selectionOffset <= extendedParagraph.rangeLength()) {
3488 auto selectionRange = extendedParagraph.subrange({ 0, selectionOffset });
3489 document->selection().moveTo(makeContainerOffsetPosition(selectionRange.end), Affinity::Downstream);
3490 } else {
3491 // If this fails for any reason, the fallback is to go one position beyond the last replacement
3492 document->selection().moveTo(document->selection().selection().end());
3493 document->selection().modify(FrameSelection::Alteration::Move, SelectionDirection::Forward, TextGranularity::CharacterGranularity);
3494 }
3495 }
3496}
3497
3498void Editor::changeBackToReplacedString(const String& replacedString)
3499{
3500#if !PLATFORM(IOS_FAMILY)(defined 1 && 1)
3501 ASSERT(unifiedTextCheckerEnabled())((void)0);
3502
3503 if (replacedString.isEmpty())
3504 return;
3505
3506 auto selection = selectedRange();
3507 if (!selection || !shouldInsertText(replacedString, selection, EditorInsertAction::Pasted))
3508 return;
3509
3510 m_alternativeTextController->recordAutocorrectionResponse(AutocorrectionResponse::Reverted, replacedString, *selection);
3511 TextCheckingParagraph paragraph(*selection);
3512 replaceSelectionWithText(replacedString, SelectReplacement::No, SmartReplace::No, EditAction::Insert);
3513 auto changedRange = paragraph.subrange(CharacterRange(paragraph.checkingStart(), replacedString.length()));
3514 addMarker(changedRange, DocumentMarkerType::Replacement, String());
3515 m_alternativeTextController->markReversed(changedRange);
3516#else
3517 ASSERT_NOT_REACHED()((void)0);
3518 UNUSED_PARAM(replacedString)(void)replacedString;
3519#endif // !PLATFORM(IOS_FAMILY)
3520}
3521
3522
3523void Editor::markMisspellingsAndBadGrammar(const VisibleSelection& spellingSelection, bool markGrammar, const VisibleSelection& grammarSelection)
3524{
3525 if (platformOrClientDrivenTextCheckerEnabled())
3526 return;
3527
3528 if (unifiedTextCheckerEnabled()) {
3529 if (!isContinuousSpellCheckingEnabled())
3530 return;
3531
3532 // markMisspellingsAndBadGrammar() is triggered by selection change, in which case we check spelling and grammar, but don't autocorrect misspellings.
3533 OptionSet<TextCheckingType> textCheckingOptions { TextCheckingType::Spelling };
3534 if (markGrammar && isGrammarCheckingEnabled())
3535 textCheckingOptions.add(TextCheckingType::Grammar);
3536 auto spellCheckingRange = spellingSelection.toNormalizedRange();
3537 markAllMisspellingsAndBadGrammarInRanges(textCheckingOptions, spellCheckingRange, spellCheckingRange, grammarSelection.toNormalizedRange());
3538 return;
3539 }
3540
3541 markMisspellings(spellingSelection);
3542 if (markGrammar)
3543 markBadGrammar(grammarSelection);
3544}
3545
3546void Editor::unappliedSpellCorrection(const VisibleSelection& selectionOfCorrected, const String& corrected, const String& correction)
3547{
3548 m_alternativeTextController->respondToUnappliedSpellCorrection(selectionOfCorrected, corrected, correction);
3549}
3550
3551void Editor::updateMarkersForWordsAffectedByEditing(bool doNotRemoveIfSelectionAtWordBoundary)
3552{
3553 Ref document = this->document();
3554 CheckedPtr markers = document->markersIfExists();
3555 if (!markers || !markers->hasMarkers())
3556 return;
3557
3558 if (!m_alternativeTextController->shouldRemoveMarkersUponEditing() && (!textChecker() || textChecker()->shouldEraseMarkersAfterChangeSelection(TextCheckingType::Spelling)))
3559 return;
3560
3561 // We want to remove the markers from a word if an editing command will change the word. This can happen in one of
3562 // several scenarios:
3563 // 1. Insert in the middle of a word.
3564 // 2. Appending non whitespace at the beginning of word.
3565 // 3. Appending non whitespace at the end of word.
3566 // Note that, appending only whitespaces at the beginning or end of word won't change the word, so we don't need to
3567 // remove the markers on that word.
3568 // Of course, if current selection is a range, we potentially will edit two words that fall on the boundaries of
3569 // selection, and remove words between the selection boundaries.
3570 //
3571 VisiblePosition startOfSelection = document->selection().selection().start();
3572 VisiblePosition endOfSelection = document->selection().selection().end();
3573 if (startOfSelection.isNull())
3574 return;
3575 // First word is the word that ends after or on the start of selection.
3576 VisiblePosition startOfFirstWord = startOfWord(startOfSelection, WordSide::LeftWordIfOnBoundary);
3577 VisiblePosition endOfFirstWord = endOfWord(startOfSelection, WordSide::LeftWordIfOnBoundary);
3578 // Last word is the word that begins before or on the end of selection
3579 VisiblePosition startOfLastWord = startOfWord(endOfSelection, WordSide::RightWordIfOnBoundary);
3580 VisiblePosition endOfLastWord = endOfWord(endOfSelection, WordSide::RightWordIfOnBoundary);
3581
3582 if (startOfFirstWord.isNull()) {
3583 startOfFirstWord = startOfWord(startOfSelection, WordSide::RightWordIfOnBoundary);
3584 endOfFirstWord = endOfWord(startOfSelection, WordSide::RightWordIfOnBoundary);
3585 }
3586
3587 if (endOfLastWord.isNull()) {
3588 startOfLastWord = startOfWord(endOfSelection, WordSide::LeftWordIfOnBoundary);
3589 endOfLastWord = endOfWord(endOfSelection, WordSide::LeftWordIfOnBoundary);
3590 }
3591
3592 auto originalEndOfFirstWord = endOfFirstWord;
3593 auto originalStartOfLastWord = startOfLastWord;
3594
3595 // If doNotRemoveIfSelectionAtWordBoundary is true, and first word ends at the start of selection,
3596 // we choose next word as the first word.
3597 if (doNotRemoveIfSelectionAtWordBoundary && endOfFirstWord == startOfSelection) {
3598 startOfFirstWord = nextWordPosition(startOfFirstWord);
3599 endOfFirstWord = endOfWord(startOfFirstWord, WordSide::RightWordIfOnBoundary);
3600 if (startOfFirstWord == originalStartOfLastWord)
3601 return;
3602 }
3603
3604 // If doNotRemoveIfSelectionAtWordBoundary is true, and last word begins at the end of selection,
3605 // we choose previous word as the last word.
3606 if (doNotRemoveIfSelectionAtWordBoundary && startOfLastWord == endOfSelection) {
3607 startOfLastWord = previousWordPosition(startOfLastWord);
3608 endOfLastWord = endOfWord(startOfLastWord, WordSide::RightWordIfOnBoundary);
3609 if (endOfLastWord == originalEndOfFirstWord)
3610 return;
3611 }
3612
3613 if (startOfFirstWord.isNull() || endOfFirstWord.isNull() || startOfLastWord.isNull() || endOfLastWord.isNull())
3614 return;
3615
3616 // Now we remove markers on everything between startOfFirstWord and endOfLastWord.
3617 // However, if an autocorrection change a single word to multiple words, we want to remove correction mark from all the
3618 // resulted words even we only edit one of them. For example, assuming autocorrection changes "avantgarde" to "avant
3619 // garde", we will have CorrectionIndicator marker on both words and on the whitespace between them. If we then edit garde,
3620 // we would like to remove the marker from word "avant" and whitespace as well. So we need to get the continous range of
3621 // of marker that contains the word in question, and remove marker on that whole range.
3622 auto wordRange = *makeSimpleRange(startOfFirstWord, endOfLastWord);
3623
3624 for (auto& marker : markers->markersInRange(wordRange, DocumentMarkerType::DictationAlternatives))
3625 m_alternativeTextController->removeDictationAlternativesForMarker(*marker);
3626
3627 OptionSet<DocumentMarkerType> markerTypesToRemove {
3628 DocumentMarkerType::CorrectionIndicator,
3629 DocumentMarkerType::SpellCheckingExemption,
3630 DocumentMarkerType::Spelling,
3631#if !PLATFORM(IOS_FAMILY)(defined 1 && 1)
3632 DocumentMarkerType::Grammar,
3633#endif
3634 };
3635
3636 if (CheckedPtr client = this->client(); client && client->shouldRemoveDictationAlternativesAfterEditing())
3637 markerTypesToRemove.add(DocumentMarkerType::DictationAlternatives);
3638
3639 adjustMarkerTypesToRemoveForWordsAffectedByEditing(markerTypesToRemove);
3640
3641 removeMarkers(wordRange, markerTypesToRemove, RemovePartiallyOverlappingMarker::Yes);
3642 markers->clearDescriptionOnMarkersIntersectingRange(wordRange, DocumentMarkerType::Replacement);
3643}
3644
3645void Editor::deletedAutocorrectionAtPosition(const Position& position, const String& originalString)
3646{
3647 m_alternativeTextController->deletedAutocorrectionAtPosition(position, originalString);
3648}
3649
3650std::optional<SimpleRange> Editor::rangeForPoint(const IntPoint& windowPoint)
3651{
3652 RefPtr document = this->document().frame()->documentAtPoint(windowPoint);
3653 if (!document)
3654 return std::nullopt;
3655 RefPtr frame = document->frame();
3656 if (!frame)
3657 return std::nullopt;
3658 RefPtr frameView = frame->view();
3659 if (!frameView)
3660 return std::nullopt;
3661 return VisibleSelection { frame->visiblePositionForPoint(frameView->windowToContents(windowPoint)) }.toNormalizedRange();
3662}
3663
3664void Editor::revealSelectionAfterEditingOperation(const ScrollAlignment& alignment, RevealExtentOption revealExtentOption)
3665{
3666 if (m_ignoreSelectionChanges)
3667 return;
3668
3669 SelectionRevealMode revealMode = SelectionRevealMode::Reveal;
3670 Ref document = this->document();
3671 document->selection().revealSelection({ revealMode, alignment, revealExtentOption });
3672}
3673
3674void Editor::setIgnoreSelectionChanges(bool ignore, RevealSelection shouldRevealExistingSelection)
3675{
3676 if (m_ignoreSelectionChanges == ignore)
3677 return;
3678
3679 m_ignoreSelectionChanges = ignore;
3680#if PLATFORM(IOS_FAMILY)(defined 1 && 1)
3681 // FIXME: Should suppress selection change notifications during a composition change <https://webkit.org/b/38830>
3682 if (!ignore)
3683 respondToChangedSelection(document().selection().selection(), { });
3684#endif
3685 if (!ignore && shouldRevealExistingSelection == RevealSelection::Yes)
3686 revealSelectionAfterEditingOperation(ScrollAlignment::alignToEdgeIfNeeded, RevealExtentOption::RevealExtent);
3687}
3688
3689std::optional<SimpleRange> Editor::compositionRange() const
3690{
3691 if (!m_compositionNode)
3692 return std::nullopt;
3693 unsigned length = m_compositionNode->length();
3694 unsigned start = std::min(m_compositionStart, length);
3695 unsigned end = std::clamp(m_compositionEnd, start, length);
3696 if (start >= end)
3697 return std::nullopt;
3698 return { { { *m_compositionNode, start }, { *m_compositionNode, end } } };
3699}
3700
3701bool Editor::getCompositionSelection(unsigned& selectionStart, unsigned& selectionEnd) const
3702{
3703 if (!m_compositionNode)
3704 return false;
3705 Ref document = this->document();
3706 const VisibleSelection& selection = document->selection().selection();
3707 Position start = selection.start();
3708 if (start.deprecatedNode() != m_compositionNode)
3709 return false;
3710 Position end = selection.end();
3711 if (end.deprecatedNode() != m_compositionNode)
3712 return false;
3713
3714 if (static_cast<unsigned>(start.deprecatedEditingOffset()) < m_compositionStart)
3715 return false;
3716 if (static_cast<unsigned>(end.deprecatedEditingOffset()) > m_compositionEnd)
3717 return false;
3718
3719 selectionStart = start.deprecatedEditingOffset() - m_compositionStart;
3720 selectionEnd = start.deprecatedEditingOffset() - m_compositionEnd;
3721 return true;
3722}
3723
3724void Editor::transpose()
3725{
3726 if (!canEdit())
3727 return;
3728
3729 Ref document = this->document();
3730 VisibleSelection selection = document->selection().selection();
3731 if (!selection.isCaret())
3732 return;
3733
3734 // Make a selection that goes back one character and forward two characters.
3735 VisiblePosition caret = selection.visibleStart();
3736 VisiblePosition next = isEndOfParagraph(caret) ? caret : caret.next();
3737 VisiblePosition previous = next.previous();
3738 if (next == previous)
3739 return;
3740 previous = previous.previous();
3741 if (!inSameParagraph(next, previous))
3742 return;
3743 auto range = makeSimpleRange(previous, next);
3744 if (!range)
3745 return;
3746 VisibleSelection newSelection(*range);
3747
3748 // Transpose the two characters.
3749 String text = plainText(*range);
3750 if (text.length() != 2)
3751 return;
3752
3753 // FIXME: This likely won't work with graphemes.
3754 String transposed = makeString(text[1], text[0]);
3755
3756 // Select the two characters.
3757 if (newSelection != document->selection().selection()) {
3758 if (!document->selection().shouldChangeSelection(newSelection))
3759 return;
3760 document->selection().setSelection(newSelection);
3761 }
3762
3763 // Insert the transposed characters.
3764 if (!shouldInsertText(transposed, newSelection.firstRange(), EditorInsertAction::Typed))
3765 return;
3766 replaceSelectionWithText(transposed, SelectReplacement::No, SmartReplace::No, EditAction::Insert);
3767}
3768
3769void Editor::addRangeToKillRing(const SimpleRange& range, KillRingInsertionMode mode)
3770{
3771 addTextToKillRing(plainText(range), mode);
3772}
3773
3774void Editor::addTextToKillRing(const String& text, KillRingInsertionMode mode)
3775{
3776 if (m_shouldStartNewKillRingSequence)
3777 killRing().startNewSequence();
3778
3779 m_shouldStartNewKillRingSequence = false;
3780
3781 // If the kill was from a backwards motion, prepend to the kill ring.
3782 // This will ensure that alternating forward and backward kills will
3783 // build up the original string in the kill ring without permuting it.
3784 switch (mode) {
3785 case KillRingInsertionMode::PrependText:
3786 killRing().prepend(text);
3787 break;
3788 case KillRingInsertionMode::AppendText:
3789 killRing().append(text);
3790 break;
3791 }
3792}
3793
3794void Editor::startAlternativeTextUITimer()
3795{
3796 m_alternativeTextController->startAlternativeTextUITimer(AlternativeTextType::Correction);
3797}
3798
3799void Editor::handleAlternativeTextUIResult(const String& correction)
3800{
3801 m_alternativeTextController->handleAlternativeTextUIResult(correction);
3802}
3803
3804
3805void Editor::dismissCorrectionPanelAsIgnored()
3806{
3807 m_alternativeTextController->dismiss(ReasonForDismissingAlternativeText::Ignored);
3808}
3809
3810void Editor::changeSelectionAfterCommand(const VisibleSelection& newSelection, OptionSet<FrameSelection::SetSelectionOption> options)
3811{
3812 Ref document = this->document();
3813
3814 if (newSelection.isOrphan() || newSelection.document() != document.ptr())
3815 return;
3816
3817 // If there is no selection change, don't bother sending shouldChangeSelection, but still call setSelection,
3818 // because there is work that it must do in this situation.
3819 // The old selection can be invalid here and calling shouldChangeSelection can produce some strange calls.
3820 // See <rdar://problem/5729315> Some shouldChangeSelectedDOMRange contain Ranges for selections that are no longer valid
3821 bool selectionDidNotChangeDOMPosition = newSelection == document->selection().selection();
3822 if (selectionDidNotChangeDOMPosition || document->selection().shouldChangeSelection(newSelection))
3823 document->selection().setSelection(newSelection, options);
3824
3825 ASSERT_WITH_SECURITY_IMPLICATION(!document->selection().selection().isOrphan())((void)0);
3826
3827 // Some editing operations change the selection visually without affecting its position within the DOM.
3828 // For example when you press return in the following (the caret is marked by ^):
3829 // <div contentEditable="true"><div>^Hello</div></div>
3830 // WebCore inserts <div><br></div> *before* the current block, which correctly moves the paragraph down but which doesn't
3831 // change the caret's DOM position (["hello", 0]). In these situations the above FrameSelection::setSelection call
3832 // does not call EditorClient::respondToChangedSelection(), which, on the Mac, sends selection change notifications and
3833 // starts a new kill ring sequence, but we want to do these things (matches AppKit).
3834#if PLATFORM(IOS_FAMILY)(defined 1 && 1)
3835 // FIXME: Should suppress selection change notifications during a composition change <https://webkit.org/b/38830>
3836 if (m_ignoreSelectionChanges)
3837 return;
3838#endif
3839 if (selectionDidNotChangeDOMPosition && client())
3840 client()->respondToChangedSelection(document->frame());
3841}
3842
3843String Editor::selectedText() const
3844{
3845 auto options = OptionSet { TextIteratorBehavior::TraversesFlatTree };
3846 if (!document().quirks().needsToCopyUserSelectNoneQuirk())
3847 options.add(TextIteratorBehavior::IgnoresUserSelectNone);
3848 return selectedText(options);
3849}
3850
3851String Editor::selectedTextForDataTransfer() const
3852{
3853 auto options = OptionSet { TextIteratorBehavior::EmitsImageAltText, TextIteratorBehavior::TraversesFlatTree };
3854 if (!document().quirks().needsToCopyUserSelectNoneQuirk())
3855 options.add(TextIteratorBehavior::IgnoresUserSelectNone);
3856 return selectedText(options);
3857}
3858
3859String Editor::selectedText(TextIteratorBehaviors behaviors) const
3860{
3861 // We remove '\0' characters because they are not visibly rendered to the user.
3862 auto range = document().selection().selection().firstRange();
3863 return range ? makeStringByReplacingAll(plainText(*range, behaviors), '\0', ""_s) : emptyString();
3864}
3865
3866RefPtr<TextPlaceholderElement> Editor::insertTextPlaceholder(const IntSize& size)
3867{
3868 Ref document = this->document();
3869 if (document->selection().isNone() || !document->selection().selection().isContentEditable())
3870 return nullptr;
3871
3872 // FIXME: Write in terms of replaceSelectionWithFragment(). See <https://bugs.webkit.org/show_bug.cgi?id=208744>.
3873 deleteSelectionWithSmartDelete(false);
3874
3875 auto range = document->selection().selection().toNormalizedRange();
3876 if (!range)
3877 return nullptr;
3878
3879 auto placeholder = TextPlaceholderElement::create(document, size);
3880 createLiveRange(*range)->insertNode(placeholder.copyRef());
3881
3882 // Inserting the placeholder can run arbitrary JavaScript. Check that it still has a parent.
3883 if (!placeholder->parentNode())
3884 return nullptr;
3885
3886 document->selection().addCaretVisibilitySuppressionReason(CaretVisibilitySuppressionReason::TextPlaceholderIsShowing);
3887
3888 document->selection().setSelection(VisibleSelection { positionInParentBeforeNode(placeholder) }, FrameSelection::defaultSetSelectionOptions(UserTriggered::Yes));
3889
3890#if ENABLE(WRITING_TOOLS)(defined 1 && 1)
3891 // For Writing Tools, we need the snapshot of the last inserted placeholder.
3892 if (auto placeholderRange = makeRangeSelectingNode(placeholder.get()))
3893 document->page()->chrome().client().saveSnapshotOfTextPlaceholderForAnimation(*placeholderRange);
3894#endif
3895
3896 return placeholder;
3897}
3898
3899void Editor::removeTextPlaceholder(TextPlaceholderElement& placeholder)
3900{
3901 ASSERT(placeholder.isConnected())((void)0);
3902
3903 Ref document = this->document();
3904
3905 // Save off state so that we can set the text insertion position to just before the placeholder element after removal.
3906 RefPtr savedRootEditableElement { placeholder.rootEditableElement() };
3907 auto savedPositionBeforePlaceholder = positionInParentBeforeNode(placeholder);
3908
3909 // FIXME: Save the current selection if it has changed since the placeholder was inserted
3910 // and restore it after text insertion.
3911 placeholder.remove();
3912 // To match the Legacy WebKit implementation, set the text insertion point to be before where the placeholder used to be.
3913 if (document->selection().isFocusedAndActive() && document->focusedElement() == savedRootEditableElement)
3914 document->selection().setSelection(VisibleSelection { savedPositionBeforePlaceholder }, FrameSelection::defaultSetSelectionOptions(UserTriggered::Yes));
3915
3916 document->selection().removeCaretVisibilitySuppressionReason(CaretVisibilitySuppressionReason::TextPlaceholderIsShowing);
3917}
3918
3919static inline void collapseCaretWidth(IntRect& rect)
3920{
3921 // FIXME: Width adjustment doesn't work for rotated text.
3922 if (rect.width() == caretWidth())
3923 rect.setWidth(0);
3924 else if (rect.height() == caretWidth())
3925 rect.setHeight(0);
3926}
3927
3928IntRect Editor::firstRectForRange(const SimpleRange& range) const
3929{
3930 protect(range.start.document())->updateLayout();
3931
3932 VisiblePosition start(makeDeprecatedLegacyPosition(range.start));
3933
3934 if (range.collapsed()) {
3935 // FIXME: Getting caret rect and removing caret width is a very roundabout way to get collapsed range location.
3936 // In particular, width adjustment doesn't work for rotated text.
3937 IntRect startCaretRect = RenderedPosition(start).absoluteRect();
3938 collapseCaretWidth(startCaretRect);
3939 return startCaretRect;
3940 }
3941
3942 VisiblePosition end(makeDeprecatedLegacyPosition(range.end), Affinity::Upstream);
3943
3944 if (inSameLine(start, end))
3945 return enclosingIntRect(unitedBoundingBoxes(RenderObject::absoluteTextQuads(range)));
3946
3947 return RenderedPosition(start).absoluteRect(CaretRectMode::ExpandToEndOfLine);
3948}
3949
3950bool Editor::shouldChangeSelection(const VisibleSelection& oldSelection, const VisibleSelection& newSelection, Affinity affinity, bool stillSelecting) const
3951{
3952#if PLATFORM(IOS_FAMILY)(defined 1 && 1)
3953 if (document().frame() && document().frame()->selectionChangeCallbacksDisabled())
3954 return true;
3955#endif
3956 return client() && client()->shouldChangeSelectedRange(oldSelection.toNormalizedRange(), newSelection.toNormalizedRange(), affinity, stillSelecting);
3957}
3958
3959void Editor::computeAndSetTypingStyle(EditingStyle& style, EditAction editingAction)
3960{
3961 Ref document = this->document();
3962 if (style.isEmpty()) {
3963 document->selection().clearTypingStyle();
3964 return;
3965 }
3966
3967 // Calculate the current typing style.
3968 RefPtr<EditingStyle> typingStyle;
3969 if (RefPtr existingTypingStyle = document->selection().typingStyle())
3970 typingStyle = existingTypingStyle->copy();
3971 else
3972 typingStyle = EditingStyle::create();
3973 typingStyle->overrideTypingStyleAt(style, document->selection().selection().visibleStart().deepEquivalent());
3974
3975 // Handle block styles, substracting these from the typing style.
3976 RefPtr<EditingStyle> blockStyle = typingStyle->extractAndRemoveBlockProperties();
3977 if (!blockStyle->isEmpty())
3978 ApplyStyleCommand::create(document.copyRef(), blockStyle.get(), editingAction)->apply();
3979
3980 // Set the remaining style as the typing style.
3981 document->selection().setTypingStyle(WTF::move(typingStyle));
3982}
3983
3984void Editor::computeAndSetTypingStyle(StyleProperties& properties, EditAction editingAction)
3985{
3986 return computeAndSetTypingStyle(EditingStyle::create(&properties), editingAction);
3987}
3988
3989void Editor::textFieldDidBeginEditing(Element& e)
3990{
3991 if (client())
3992 client()->textFieldDidBeginEditing(e);
3993}
3994
3995void Editor::textFieldDidEndEditing(Element& e)
3996{
3997 dismissCorrectionPanelAsIgnored();
3998 if (client())
3999 client()->textFieldDidEndEditing(e);
4000}
4001
4002void Editor::textDidChangeInTextField(Element& e)
4003{
4004 if (client())
4005 client()->textDidChangeInTextField(e);
4006}
4007
4008bool Editor::doTextFieldCommandFromEvent(Element& e, KeyboardEvent* ke)
4009{
4010 if (client())
4011 return client()->doTextFieldCommandFromEvent(e, ke);
4012
4013 return false;
4014}
4015
4016void Editor::textWillBeDeletedInTextField(Element& input)
4017{
4018 if (client())
4019 client()->textWillBeDeletedInTextField(input);
4020}
4021
4022void Editor::textDidChangeInTextArea(Element& e)
4023{
4024 if (client())
4025 client()->textDidChangeInTextArea(e);
4026}
4027
4028void Editor::applyEditingStyleToBodyElement() const
4029{
4030 RefPtr body { document().body() };
4031 if (!body)
4032 return;
4033 body->setInlineStyleProperty(CSSPropertyOverflowWrap, CSSValueBreakWord);
4034 body->setInlineStyleProperty(CSSPropertyWebkitNbspMode, CSSValueSpace);
4035 body->setInlineStyleProperty(CSSPropertyLineBreak, CSSValueAfterWhiteSpace);
4036}
4037
4038std::optional<SimpleRange> Editor::findString(const String& target, FindOptions options)
4039{
4040 Ref document = this->document();
4041 std::optional<SimpleRange> resultRange;
4042 {
4043 document->updateLayoutIgnorePendingStylesheets({ LayoutOptions::TreatContentVisibilityAutoAsVisible, LayoutOptions::TreatRevealedWhenFoundAsVisible });
4044 Style::PostResolutionCallbackDisabler disabler(document);
4045 VisibleSelection selection = document->selection().selection();
4046 auto referenceRange = selection.firstRange();
4047 if (!referenceRange || referenceRange->collapsed())
4048 referenceRange = selection.range();
4049 resultRange = rangeOfString(target, referenceRange, options);
4050 }
4051
4052 if (!resultRange)
4053 return std::nullopt;
4054
4055 if (!options.contains(FindOption::DoNotSetSelection))
4056 document->selection().setSelection(VisibleSelection(*resultRange));
4057
4058 if (!(options.contains(FindOption::DoNotRevealSelection)))
4059 document->selection().revealSelection();
4060
4061 return resultRange;
4062}
4063
4064template<typename T> static auto& start(T& range, FindOptions options)
4065{
4066 return options.contains(FindOption::Backwards) ? range.end : range.start;
4067}
4068
4069template<typename T> static auto& end(T& range, FindOptions options)
4070{
4071 return options.contains(FindOption::Backwards) ? range.start : range.end;
4072}
4073
4074static BoundaryPoint makeBoundaryPointAfterNodeContents(Node& node, FindOptions options)
4075{
4076 return options.contains(FindOption::Backwards) ? makeBoundaryPointBeforeNodeContents(node) : makeBoundaryPointAfterNodeContents(node);
4077}
4078
4079static std::optional<BoundaryPoint> makeBoundaryPointAfterNode(Node& node, FindOptions options)
4080{
4081 return options.contains(FindOption::Backwards) ? makeBoundaryPointBeforeNode(node) : makeBoundaryPointAfterNode(node);
4082}
4083
4084static SimpleRange collapseIfRootsDiffer(SimpleRange&& range)
4085{
4086 // FIXME: This helps correct results in some cases involving shadow trees. But we can incorrectly find a string with middle characters in an input element and first and last characters outside it.
4087 return &range.start.container->rootNode() == &range.end.container->rootNode()
4088 ? WTF::move(range) : SimpleRange { range.start, range.start };
4089}
4090
4091std::optional<SimpleRange> Editor::rangeOfString(const String& target, const std::optional<SimpleRange>& referenceRange, FindOptions options)
4092{
4093 if (target.isEmpty())
4094 return std::nullopt;
4095
4096 // Start from an edge of the reference range, if there's a reference range that's not in shadow content. Which edge
4097 // is used depends on whether we're searching forward or backward, and whether startInSelection is set.
4098
4099 bool startInReferenceRange = referenceRange && options.contains(FindOption::StartInSelection);
4100 RefPtr shadowTreeRoot = referenceRange ? referenceRange->startContainer().containingShadowRoot() : nullptr;
4101
4102 Ref document = this->document();
4103 auto searchRange = makeRangeSelectingNodeContents(document);
4104 if (referenceRange)
4105 start(searchRange, options) = startInReferenceRange ? start(*referenceRange, options) : end(*referenceRange, options);
4106 if (shadowTreeRoot)
4107 end(searchRange, options) = makeBoundaryPointAfterNodeContents(*shadowTreeRoot, options);
4108 auto resultRange = collapseIfRootsDiffer(findPlainText(searchRange, target, options));
4109
4110 // If we started in the reference range and the found range exactly matches the reference range, find again.
4111 // Build a selection with the found range to remove collapsed whitespace.
4112 // Compare ranges instead of selection objects to ignore the way that the current selection was made.
4113 auto resultSelection = VisibleSelection(resultRange);
4114 auto normalizedRange = resultSelection.toNormalizedRange();
4115 auto resultRangeForComparison = !normalizedRange || normalizedRange->collapsed() ? resultSelection.range() : normalizedRange;
4116 if (startInReferenceRange && resultRangeForComparison == referenceRange) {
4117 searchRange = makeRangeSelectingNodeContents(document);
4118 start(searchRange, options) = end(*referenceRange, options);
4119 if (shadowTreeRoot)
4120 end(searchRange, options) = makeBoundaryPointAfterNodeContents(*shadowTreeRoot, options);
4121 resultRange = collapseIfRootsDiffer(findPlainText(searchRange, target, options));
4122 }
4123
4124 // If nothing was found in the shadow tree, search in main content following the shadow tree.
4125 if (resultRange.collapsed() && shadowTreeRoot) {
4126 searchRange = makeRangeSelectingNodeContents(document);
4127 if (RefPtr host = shadowTreeRoot->shadowHost())
4128 start(searchRange, options) = *makeBoundaryPointAfterNode(*host, options);
4129 resultRange = collapseIfRootsDiffer(findPlainText(searchRange, target, options));
4130 }
4131
4132 // If we didn't find anything and we're wrapping, search again in the entire document (this will
4133 // redundantly re-search the area already searched in some cases).
4134 if (resultRange.collapsed() && options.contains(FindOption::WrapAround)) {
4135 resultRange = collapseIfRootsDiffer(findPlainText(makeRangeSelectingNodeContents(document), target, options));
4136 // We used to return false here if we ended up with the same range that we started with
4137 // (e.g., the reference range was already the only instance of this text). But we decided that
4138 // this should be a success case instead, so we'll just fall through in that case.
4139 }
4140
4141 return resultRange.collapsed() ? std::nullopt : std::make_optional(resultRange);
4142}
4143
4144static bool isFrameInRange(LocalFrame& frame, const SimpleRange& range)
4145{
4146 for (RefPtr ownerElement = frame.ownerElement(); ownerElement; ownerElement = ownerElement->document().ownerElement()) {
4147 if (&ownerElement->document() == &range.start.document())
4148 return intersects<ComposedTree>(range, *ownerElement);
4149 }
4150 return false;
4151}
4152
4153unsigned Editor::countMatchesForText(const String& target, const std::optional<SimpleRange>& range, FindOptions options, unsigned limit, bool markMatches, Vector<SimpleRange>* matches)
4154{
4155 if (target.isEmpty())
4156 return 0;
4157
4158 Ref document = this->document();
4159 document->updateLayoutIgnorePendingStylesheets({ LayoutOptions::TreatContentVisibilityAutoAsVisible, LayoutOptions::TreatRevealedWhenFoundAsVisible });
4160
4161 std::optional<SimpleRange> searchRange;
4162 if (range) {
4163 if (&range->start.document() == document.ptr())
4164 searchRange = *range;
4165 else if (!isFrameInRange(*document->frame(), *range))
4166 return 0;
4167 }
4168 if (!searchRange)
4169 searchRange = makeRangeSelectingNodeContents(document);
4170
4171 auto allMatches = findAllPlainText(*searchRange, target, options - FindOption::Backwards, limit);
4172
4173 if (matches)
4174 matches->appendVector(allMatches);
4175
4176 if (markMatches) {
4177 for (const auto& match : allMatches)
4178 addMarker(match, DocumentMarkerType::TextMatch);
4179 }
4180
4181 return allMatches.size();
4182}
4183
4184void Editor::setMarkedTextMatchesAreHighlighted(bool flag)
4185{
4186 if (flag == m_areMarkedTextMatchesHighlighted)
4187 return;
4188
4189 m_areMarkedTextMatchesHighlighted = flag;
4190 if (CheckedPtr markers = document().markersIfExists())
4191 markers->repaintMarkers(DocumentMarkerType::TextMatch);
4192}
4193
4194#if !PLATFORM(MAC)(defined WTF_PLATFORM_MAC && WTF_PLATFORM_MAC)
4195void Editor::selectionWillChange()
4196{
4197}
4198#endif
4199
4200void Editor::respondToChangedSelection(const VisibleSelection&, OptionSet<FrameSelection::SetSelectionOption> options)
4201{
4202#if PLATFORM(IOS_FAMILY)(defined 1 && 1)
4203 // FIXME: Should suppress selection change notifications during a composition change <https://webkit.org/b/38830>
4204 if (m_ignoreSelectionChanges)
4205 return;
4206#endif
4207
4208 Ref document = this->document();
4209
4210#if PLATFORM(IOS_FAMILY)(defined 1 && 1)
4211 auto continueDisplayingSuggestion = [&] {
4212 if (!m_writingSuggestionData)
4213 return false;
4214
4215 auto newSelection = document->selection().selection();
4216
4217 auto range = newSelection.firstRange();
4218 if (!range)
4219 return false;
4220
4221 range->start.offset = 0;
4222
4223 auto completion = makeString(m_writingSuggestionData->originalPrefix(), m_writingSuggestionData->originalSuffix());
4224 auto content = plainTextReplacingNoBreakSpace(*range);
4225
4226 auto currentFullText = makeString(content, m_writingSuggestionData->content());
4227
4228 if (completion == content)
4229 return false;
4230
4231 if (completion != currentFullText)
4232 return false;
4233
4234 if (content.length() <= m_writingSuggestionData->originalPrefix().length())
4235 return false;
4236
4237 return completion.startsWith(content);
4238 }();
4239
4240 if (m_writingSuggestionData && !continueDisplayingSuggestion)
4241 removeWritingSuggestionIfNeeded();
4242#endif
4243
4244 if (client())
4245 client()->respondToChangedSelection(document->frame());
4246
4247#if ENABLE(TELEPHONE_NUMBER_DETECTION)(defined 1 && 1) && !PLATFORM(IOS_FAMILY)(defined 1 && 1)
4248 if (shouldDetectTelephoneNumbers())
4249 m_telephoneNumberDetectionUpdateTimer.restart();
4250#endif
4251
4252 setStartNewKillRingSequence(true);
4253 m_imageElementsToLoadBeforeRevealingSelection.clear();
4254
4255 if (!m_hasHandledAnyEditing && !document->hasHadUserInteraction() && !document->isTopDocument())
4256 return;
4257
4258 if (m_editorUIUpdateTimer.isActive())
4259 return;
4260
4261 // Don't check spelling and grammar if the change of selection is triggered by spelling correction itself.
4262 m_editorUIUpdateTimerShouldCheckSpellingAndGrammar = options.contains(FrameSelection::SetSelectionOption::CloseTyping) && !options.contains(FrameSelection::SetSelectionOption::SpellCorrectionTriggered);
4263 m_editorUIUpdateTimerWasTriggeredByDictation = options.contains(FrameSelection::SetSelectionOption::DictationTriggered);
4264 scheduleEditorUIUpdate();
4265}
4266
4267#if ENABLE(TELEPHONE_NUMBER_DETECTION)(defined 1 && 1) && PLATFORM(MAC)(defined WTF_PLATFORM_MAC && WTF_PLATFORM_MAC)
4268
4269bool Editor::shouldDetectTelephoneNumbers() const
4270{
4271 return document().isTelephoneNumberParsingEnabled() && TelephoneNumberDetector::isSupported();
4272}
4273
4274static Vector<SimpleRange> scanForTelephoneNumbers(const SimpleRange& range)
4275{
4276 // Don't scan for phone numbers inside editable regions.
4277 if (Ref startNode = range.startContainer(); startNode->hasEditableStyle())
4278 return { };
4279
4280 if (ImageOverlay::isInsideOverlay(range))
4281 return { };
4282
4283 auto text = plainText(range);
4284 Vector<SimpleRange> result;
4285 int relativeStartPosition = 0;
4286 int relativeEndPosition = 0;
4287 auto characters = StringView { text }.upconvertedCharacters();
4288 auto span = characters.span();
4289 while (!span.empty() && TelephoneNumberDetector::find(span, &relativeStartPosition, &relativeEndPosition)) {
4290 auto scannerPosition = span.data() - characters.span().data();
4291 ASSERT(scannerPosition + relativeEndPosition <= text.length())((void)0);
4292 result.append(resolveCharacterRange(range, CharacterRange(scannerPosition + relativeStartPosition, relativeEndPosition - relativeStartPosition)));
4293 skip(span, relativeEndPosition);
4294 }
4295 return result;
4296}
4297
4298static std::optional<SimpleRange> extendSelection(const SimpleRange& range, unsigned charactersToExtend)
4299{
4300 auto start = makeDeprecatedLegacyPosition(range.start);
4301 auto end = makeDeprecatedLegacyPosition(range.end);
4302 for (unsigned i = 0; i < charactersToExtend; ++i) {
4303 start = start.previous(Character);
4304 end = end.next(Character);
4305 }
4306 return makeSimpleRange(start, end);
4307}
4308
4309void Editor::scanSelectionForTelephoneNumbers()
4310{
4311 // FIXME: Why is it helpful here to check client for null?
4312 if (!shouldDetectTelephoneNumbers() || !client())
4313 return;
4314
4315 m_detectedTelephoneNumberRanges.clear();
4316
4317 auto notifyController = makeScopeExit([&] {
4318 if (RefPtr page = document().page())
4319 protect(page->servicesOverlayController())->selectedTelephoneNumberRangesChanged();
4320 });
4321
4322 auto selection = document().selection().selection();
4323 if (!selection.isRange())
4324 return;
4325
4326 auto selectedRange = selection.firstRange();
4327 if (!selectedRange)
4328 return;
4329
4330 // Extend the range a few characters in each direction to detect incompletely selected phone numbers.
4331 constexpr unsigned charactersToExtend = 15;
4332 auto extendedRange = extendSelection(*selectedRange, charactersToExtend);
4333 if (!extendedRange)
4334 return;
4335
4336 for (auto& range : scanForTelephoneNumbers(*extendedRange)) {
4337 // FIXME: Why do we do this unconditionally instead of when only when it overlaps the selection?
4338 addMarker(range, DocumentMarkerType::TelephoneNumber);
4339
4340 // Only consider ranges with a detected telephone number if they overlap with the selection.
4341 if (intersects<ComposedTree>(range, *selectedRange))
4342 m_detectedTelephoneNumberRanges.append(range);
4343 }
4344}
4345
4346#endif // ENABLE(TELEPHONE_NUMBER_DETECTION) && !PLATFORM(IOS_FAMILY)
4347
4348void Editor::updateEditorUINowIfScheduled()
4349{
4350 if (!m_editorUIUpdateTimer.isActive())
4351 return;
4352 m_editorUIUpdateTimer.stop();
4353 editorUIUpdateTimerFired();
4354}
4355
4356void Editor::editorUIUpdateTimerFired()
4357{
4358 VisibleSelection oldSelection = m_oldSelectionForEditorUIUpdate;
4359
4360 m_alternativeTextController->stopPendingCorrection(oldSelection);
4361
4362 bool isContinuousSpellCheckingEnabled = this->isContinuousSpellCheckingEnabled();
4363 bool isContinuousGrammarCheckingEnabled = isContinuousSpellCheckingEnabled && isGrammarCheckingEnabled();
4364 Ref document = this->document();
4365 if (isContinuousSpellCheckingEnabled) {
4366 VisibleSelection newAdjacentWords;
4367 VisibleSelection newSelectedSentence;
4368 bool caretBrowsing = document->settings().caretBrowsingEnabled();
4369 if (document->selection().selection().isContentEditable() || caretBrowsing) {
4370 VisiblePosition newStart(document->selection().selection().visibleStart());
4371#if !PLATFORM(IOS_FAMILY)(defined 1 && 1)
4372 newAdjacentWords = VisibleSelection(startOfWord(newStart, WordSide::LeftWordIfOnBoundary), endOfWord(newStart, WordSide::RightWordIfOnBoundary));
4373#else
4374 // If this bug gets fixed, this PLATFORM(IOS_FAMILY) code could be removed:
4375 // <rdar://problem/7259611> Word boundary code on iPhone gives different results than desktop
4376 WordSide startWordSide = WordSide::LeftWordIfOnBoundary;
4377 char32_t c = newStart.characterBefore();
4378 // FIXME: VisiblePosition::characterAfter() and characterBefore() do not emit newlines the same
4379 // way as TextIterator, so we do an isStartOfParagraph check here.
4380 if (deprecatedIsSpaceOrNewline(c) || c == noBreakSpace || isStartOfParagraph(newStart)) {
4381 startWordSide = WordSide::RightWordIfOnBoundary;
4382 }
4383 newAdjacentWords = VisibleSelection(startOfWord(newStart, startWordSide), endOfWord(newStart, WordSide::RightWordIfOnBoundary));
4384#endif // !PLATFORM(IOS_FAMILY)
4385 if (isContinuousGrammarCheckingEnabled)
4386 newSelectedSentence = VisibleSelection(startOfSentence(newStart), endOfSentence(newStart));
4387 }
4388
4389 // When typing we check spelling elsewhere, so don't redo it here.
4390 // If this is a change in selection resulting from a delete operation,
4391 // oldSelection may no longer be in the document.
4392 if (m_editorUIUpdateTimerShouldCheckSpellingAndGrammar && oldSelection.isContentEditable() && oldSelection.start().deprecatedNode() && oldSelection.start().anchorNode()->isConnected()) {
4393 VisiblePosition oldStart(oldSelection.visibleStart());
4394 VisibleSelection oldAdjacentWords = VisibleSelection(startOfWord(oldStart, WordSide::LeftWordIfOnBoundary), endOfWord(oldStart, WordSide::RightWordIfOnBoundary));
4395 if (oldAdjacentWords != newAdjacentWords) {
4396 if (isContinuousGrammarCheckingEnabled) {
4397 VisibleSelection oldSelectedSentence = VisibleSelection(startOfSentence(oldStart), endOfSentence(oldStart));
4398 markMisspellingsAndBadGrammar(oldAdjacentWords, oldSelectedSentence != newSelectedSentence, oldSelectedSentence);
4399 } else
4400 markMisspellingsAndBadGrammar(oldAdjacentWords, false, oldAdjacentWords);
4401 }
4402 }
4403
4404 if (!textChecker() || textChecker()->shouldEraseMarkersAfterChangeSelection(TextCheckingType::Spelling)) {
4405 if (auto wordRange = newAdjacentWords.toNormalizedRange())
4406 removeMarkers(*wordRange, DocumentMarkerType::Spelling);
4407 }
4408 if (!textChecker() || textChecker()->shouldEraseMarkersAfterChangeSelection(TextCheckingType::Grammar)) {
4409 if (auto sentenceRange = newSelectedSentence.toNormalizedRange())
4410 removeMarkers(*sentenceRange, DocumentMarkerType::Grammar);
4411 }
4412 }
4413
4414 // When continuous spell checking is off, existing markers disappear after the selection changes.
4415 if (CheckedPtr markers = document->markersIfExists()) {
4416 if (!isContinuousSpellCheckingEnabled)
4417 markers->removeMarkers(DocumentMarkerType::Spelling);
4418 if (!isContinuousGrammarCheckingEnabled)
4419 markers->removeMarkers(DocumentMarkerType::Grammar);
4420 }
4421
4422 if (!m_editorUIUpdateTimerWasTriggeredByDictation)
4423 m_alternativeTextController->respondToChangedSelection(oldSelection);
4424
4425 m_oldSelectionForEditorUIUpdate = document->selection().selection();
4426
4427#if ENABLE(ATTACHMENT_ELEMENT)(defined 1 && 1)
4428 notifyClientOfAttachmentUpdates();
4429#endif
4430}
4431
4432static RefPtr<Node> findFirstMarkable(Node* startingNode)
4433{
4434 RefPtr node { startingNode };
4435 while (node) {
4436 {
4437 ScriptDisallowedScope::InMainThread scriptDisallowedScope;
4438 if (!node->renderer())
4439 return nullptr;
4440 if (node->renderer()->isRenderTextOrLineBreak())
4441 return node;
4442 }
4443 if (RefPtr element = dynamicDowncast<HTMLTextFormControlElement>(*node); element && element->isTextField())
4444 node = element->visiblePositionForIndex(1).deepEquivalent().deprecatedNode();
4445 else if (node->firstChild())
4446 node = node->firstChild();
4447 else
4448 node = node->nextSibling();
4449 }
4450
4451 return nullptr;
4452}
4453
4454bool Editor::selectionStartHasMarkerFor(DocumentMarkerType markerType, int from, int length) const
4455{
4456 auto node = findFirstMarkable(protect(document().selection().selection().start().deprecatedNode()).get());
4457 if (!node)
4458 return false;
4459
4460 CheckedPtr markers = document().markersIfExists();
4461 if (!markers)
4462 return false;
4463
4464 unsigned startOffset = static_cast<unsigned>(from);
4465 unsigned endOffset = static_cast<unsigned>(from + length);
4466 for (auto& marker : markers->markersFor(*node, markerType)) {
4467 if (marker->startOffset() <= startOffset && endOffset <= marker->endOffset())
4468 return true;
4469 }
4470
4471 return false;
4472}
4473
4474void Editor::selectionStartSetMarkerForTesting(DocumentMarkerType markerType, int from, int length, const String& data)
4475{
4476 RefPtr node = findFirstMarkable(protect(document().selection().selection().start().deprecatedNode()).get());
4477 if (!node)
4478 return;
4479
4480 RefPtr text = dynamicDowncast<Text>(node);
4481 if (!text)
4482 return;
4483
4484 CheckedRef markers = document().markers();
4485
4486 unsigned unsignedFrom = static_cast<unsigned>(from);
4487 unsigned unsignedLength = static_cast<unsigned>(length);
4488
4489 switch (markerType) {
4490 case DocumentMarkerType::TransparentContent:
4491 markers->addMarker(*text, unsignedFrom, unsignedLength, markerType, DocumentMarker::TransparentContentData { node, WTF::UUID { 0 } });
4492 return;
4493
4494 case DocumentMarkerType::DraggedContent:
4495 markers->addMarker(*text, unsignedFrom, unsignedLength, markerType, node);
4496 return;
4497
4498 case DocumentMarkerType::Grammar:
4499 case DocumentMarkerType::Spelling:
4500 case DocumentMarkerType::Replacement:
4501 markers->addMarker(*text, unsignedFrom, unsignedLength, markerType, data);
4502 return;
4503
4504 default:
4505 // FIXME: Support more marker types in this testing utility function.
4506 RELEASE_ASSERT_NOT_REACHED()do { WTF::isIntegralOrPointerType(); compilerFenceForCrash();
WTFCrashWithInfo(4506, "/Volumes/Data/worker/Apple-iOS-26-Safer-CPP-Checks-EWS/build/Source/WebCore/editing/Editor.cpp"
, __PRETTY_FUNCTION__ ); } while (false)
;
4507 }
4508}
4509
4510OptionSet<TextCheckingType> Editor::resolveTextCheckingTypeMask(const Node& rootEditableElement, OptionSet<TextCheckingType> textCheckingOptions)
4511{
4512#if USE(AUTOMATIC_TEXT_REPLACEMENT)(defined USE_AUTOMATIC_TEXT_REPLACEMENT && USE_AUTOMATIC_TEXT_REPLACEMENT
)
&& !PLATFORM(IOS_FAMILY)(defined 1 && 1)
4513 bool onlyAllowsTextReplacement = false;
4514 if (RefPtr host = dynamicDowncast<HTMLInputElement>(rootEditableElement.shadowHost()))
4515 onlyAllowsTextReplacement = host->isSpellcheckDisabledExceptTextReplacement();
4516 if (onlyAllowsTextReplacement)
4517 textCheckingOptions = textCheckingOptions & TextCheckingType::Replacement;
4518#else
4519 UNUSED_PARAM(rootEditableElement)(void)rootEditableElement;
4520#endif
4521
4522 bool shouldMarkSpelling = textCheckingOptions.contains(TextCheckingType::Spelling);
4523 bool shouldMarkGrammar = textCheckingOptions.contains(TextCheckingType::Grammar);
4524#if !PLATFORM(IOS_FAMILY)(defined 1 && 1)
4525 bool shouldShowCorrectionPanel = textCheckingOptions.contains(TextCheckingType::ShowCorrectionPanel);
4526 bool shouldCheckForCorrection = shouldShowCorrectionPanel || textCheckingOptions.contains(TextCheckingType::Correction);
4527#else
4528 bool shouldCheckForCorrection = textCheckingOptions.contains(TextCheckingType::Correction);
4529#endif
4530
4531 OptionSet<TextCheckingType> checkingTypes;
4532 if (shouldMarkSpelling)
4533 checkingTypes.add(TextCheckingType::Spelling);
4534 if (shouldMarkGrammar)
4535 checkingTypes.add(TextCheckingType::Grammar);
4536 if (shouldCheckForCorrection)
4537 checkingTypes.add(TextCheckingType::Correction);
4538#if !PLATFORM(IOS_FAMILY)(defined 1 && 1)
4539 if (shouldShowCorrectionPanel)
4540 checkingTypes.add(TextCheckingType::ShowCorrectionPanel);
4541
4542#if USE(AUTOMATIC_TEXT_REPLACEMENT)(defined USE_AUTOMATIC_TEXT_REPLACEMENT && USE_AUTOMATIC_TEXT_REPLACEMENT
)
4543 bool shouldPerformReplacement = textCheckingOptions.contains(TextCheckingType::Replacement);
4544 if (shouldPerformReplacement) {
4545 if (!onlyAllowsTextReplacement) {
4546 if (isAutomaticLinkDetectionEnabled())
4547 checkingTypes.add(TextCheckingType::Link);
4548 if (isAutomaticQuoteSubstitutionEnabled())
4549 checkingTypes.add(TextCheckingType::Quote);
4550 if (isAutomaticDashSubstitutionEnabled())
4551 checkingTypes.add(TextCheckingType::Dash);
4552 if (shouldMarkSpelling && isAutomaticSpellingCorrectionEnabled())
4553 checkingTypes.add(TextCheckingType::Correction);
4554 }
4555 if (isAutomaticTextReplacementEnabled())
4556 checkingTypes.add(TextCheckingType::Replacement);
4557 }
4558#endif
4559#endif // !PLATFORM(IOS_FAMILY)
4560
4561 return checkingTypes;
4562}
4563
4564static bool candidateWouldReplaceText(const VisibleSelection& selection)
4565{
4566 // If the character behind the caret in the current selection is anything but a space or a newline then we should
4567 // replace the whole current word with the candidate.
4568 char32_t characterAfterSelection, characterBeforeSelection, twoCharacterBeforeSelection = 0;
4569 charactersAroundPosition(selection.visibleStart(), characterAfterSelection, characterBeforeSelection, twoCharacterBeforeSelection);
4570 return !(characterBeforeSelection == '\0' || characterBeforeSelection == '\n' || characterBeforeSelection == ' ');
4571}
4572
4573String Editor::stringForCandidateRequest() const
4574{
4575 auto& selection = document().selection().selection();
4576 auto range = selection.isCaret() ? wordRangeFromPosition(selection.start()) : selection.toNormalizedRange();
4577 if (!range)
4578 return { };
4579 if (!candidateWouldReplaceText(selection))
4580 return { };
4581 return plainText(*range);
4582}
4583
4584std::optional<SimpleRange> Editor::contextRangeForCandidateRequest() const
4585{
4586 auto& selection = document().selection().selection();
4587 return makeSimpleRange(startOfParagraph(selection.visibleStart()), endOfParagraph(selection.visibleEnd()));
4588}
4589
4590std::optional<SimpleRange> Editor::rangeForTextCheckingResult(const TextCheckingResult& result) const
4591{
4592 if (!result.range.length)
4593 return std::nullopt;
4594 auto contextRange = contextRangeForCandidateRequest();
4595 if (!contextRange)
4596 return std::nullopt;
4597 return resolveCharacterRange(*contextRange, result.range);
4598}
4599
4600void Editor::scheduleEditorUIUpdate()
4601{
4602 m_editorUIUpdateTimer.startOneShot(0_s);
4603}
4604
4605#if !PLATFORM(COCOA)(defined 1 && 1)
4606
4607String Editor::platformContentTypeForBlobType(const String& type) const
4608{
4609 return type;
4610}
4611
4612#endif
4613
4614static Vector<TextList> editableTextListsAtPositionInDescendingOrder(const Position& position)
4615{
4616 RefPtr startContainer { position.containerNode() };
4617 if (!startContainer)
4618 return { };
4619
4620 auto editableRoot = highestEditableRoot(firstPositionInOrBeforeNode(startContainer.get()));
4621 if (!editableRoot)
4622 return { };
4623
4624 Vector<Ref<HTMLElement>> enclosingLists;
4625 for (Ref ancestor : ancestorsOfType<HTMLElement>(*startContainer)) {
4626 if (ancestor.ptr() == editableRoot.get())
4627 break;
4628
4629 if (!ancestor->renderer())
4630 continue;
4631
4632 if (isAnyOf<HTMLUListElement, HTMLOListElement>(ancestor))
4633 enclosingLists.append(WTF::move(ancestor));
4634 }
4635
4636 Vector<TextList> textLists;
4637 textLists.reserveInitialCapacity(enclosingLists.size());
4638 for (auto iterator = enclosingLists.rbegin(); iterator != enclosingLists.rend(); ++iterator) {
4639 Ref list = *iterator;
4640 RefPtr orderedList = dynamicDowncast<HTMLOListElement>(list);
4641 if (!list->renderer())
4642 continue;
4643 auto style = list->renderer()->style().listStyleType();
4644 textLists.append({ style, orderedList ? orderedList->start() : 1, !!orderedList });
4645 }
4646
4647 return textLists;
4648}
4649
4650FontAttributes Editor::fontAttributesAtSelectionStart()
4651{
4652 FontAttributes attributes;
4653 attributes.font = fontForSelection(attributes.hasMultipleFonts);
4654
4655 RefPtr<Node> nodeToRemove;
4656 auto nodeRemovalScope = makeScopeExit([&nodeToRemove]() {
4657 if (nodeToRemove)
4658 nodeToRemove->remove();
4659 });
4660
4661 CheckedPtr style = styleForSelectionStart(nodeToRemove);
4662 if (!style)
4663 return { };
4664
4665 ScriptDisallowedScope::InMainThread scriptDisallowedScope;
4666
4667 // FIXME: for now, always report the colors after applying -apple-color-filter. In future not all clients
4668 // may want this, so we may have to add a setting to control it. See also editingAttributedStringFromRange().
4669 auto backgroundColor = style->visitedDependentBackgroundColorApplyingColorFilter();
4670 if (backgroundColor.isVisible())
4671 attributes.backgroundColor = backgroundColor;
4672
4673 auto foregroundColor = style->visitedDependentColorApplyingColorFilter();
4674 // FIXME: isBlackColor not suitable for dark mode.
4675 if (foregroundColor.isValid() && !Color::isBlackColor(foregroundColor))
4676 attributes.foregroundColor = foregroundColor;
4677
4678 attributes.fontShadow = WTF::switchOn(style->textShadow(),
4679 [&](const CSS::Keyword::None&) {
4680 return FontShadow { };
4681 },
4682 [&](const auto& shadows) {
4683 Style::ColorResolver colorResolver { *style };
4684 const auto& zoomFactor = style->usedZoomForLength();
4685 return FontShadow {
4686 colorResolver.colorResolvingCurrentColorApplyingColorFilter(shadows[0].color),
4687 {
4688 shadows[0].location.x().resolveZoom(zoomFactor),
4689 shadows[0].location.y().resolveZoom(zoomFactor),
4690 },
4691 shadows[0].blur.resolveZoom(zoomFactor)
4692 };
4693 }
4694 );
4695
4696 attributes.subscriptOrSuperscript = WTF::switchOn(style->verticalAlign(),
4697 [](const CSS::Keyword::Baseline&) { return FontAttributes::SubscriptOrSuperscript::None; },
4698 [](const CSS::Keyword::Sub&) { return FontAttributes::SubscriptOrSuperscript::Subscript; },
4699 [](const CSS::Keyword::Super&) { return FontAttributes::SubscriptOrSuperscript::Superscript; },
4700 [](const CSS::Keyword::Bottom&) { return FontAttributes::SubscriptOrSuperscript::None; },
4701 [](const CSS::Keyword::Middle&) { return FontAttributes::SubscriptOrSuperscript::None; },
4702 [](const CSS::Keyword::TextBottom&) { return FontAttributes::SubscriptOrSuperscript::None; },
4703 [](const CSS::Keyword::TextTop&) { return FontAttributes::SubscriptOrSuperscript::None; },
4704 [](const CSS::Keyword::Top&) { return FontAttributes::SubscriptOrSuperscript::None; },
4705 [](const CSS::Keyword::WebkitBaselineMiddle&) { return FontAttributes::SubscriptOrSuperscript::None; },
4706 [](const Style::VerticalAlign::Length&) { return FontAttributes::SubscriptOrSuperscript::None; }
4707 );
4708
4709 attributes.textLists = editableTextListsAtPositionInDescendingOrder(document().selection().selection().start());
4710
4711 switch (style->textAlign()) {
4712 case Style::TextAlign::Right:
4713 case Style::TextAlign::WebKitRight:
4714 attributes.horizontalAlignment = FontAttributes::HorizontalAlignment::Right;
4715 break;
4716 case Style::TextAlign::Left:
4717 case Style::TextAlign::WebKitLeft:
4718 attributes.horizontalAlignment = FontAttributes::HorizontalAlignment::Left;
4719 break;
4720 case Style::TextAlign::Center:
4721 case Style::TextAlign::WebKitCenter:
4722 attributes.horizontalAlignment = FontAttributes::HorizontalAlignment::Center;
4723 break;
4724 case Style::TextAlign::Justify:
4725 attributes.horizontalAlignment = FontAttributes::HorizontalAlignment::Justify;
4726 break;
4727 case Style::TextAlign::Start:
4728 if (style->hasExplicitlySetDirection())
4729 attributes.horizontalAlignment = style->writingMode().deprecatedIsLeftToRightDirection() ? FontAttributes::HorizontalAlignment::Left : FontAttributes::HorizontalAlignment::Right;
4730 else
4731 attributes.horizontalAlignment = FontAttributes::HorizontalAlignment::Natural;
4732 break;
4733 case Style::TextAlign::End:
4734 attributes.horizontalAlignment = style->writingMode().deprecatedIsLeftToRightDirection() ? FontAttributes::HorizontalAlignment::Right : FontAttributes::HorizontalAlignment::Left;
4735 break;
4736 }
4737
4738 RefPtr typingStyle { document().selection().typingStyle() };
4739 if (typingStyle && typingStyle->style()) {
4740 if (RefPtr value = dynamicDowncast<CSSValueList>(typingStyle->style()->getPropertyCSSValue(CSSPropertyWebkitTextDecorationsInEffect))) {
4741 if (value->hasValue(CSSValueLineThrough))
4742 attributes.hasStrikeThrough = true;
4743 if (value->hasValue(CSSValueUnderline))
4744 attributes.hasUnderline = true;
4745 }
4746 } else {
4747 auto decoration = style->textDecorationLineInEffect();
4748 if (decoration.hasLineThrough())
4749 attributes.hasStrikeThrough = true;
4750 if (decoration.hasUnderline())
4751 attributes.hasUnderline = true;
4752 }
4753
4754 return attributes;
4755}
4756
4757#if ENABLE(ATTACHMENT_ELEMENT)(defined 1 && 1)
4758
4759PromisedAttachmentInfo Editor::promisedAttachmentInfo(Element& element)
4760{
4761 CheckedPtr client = this->client();
4762 if (!client || !client->supportsClientSideAttachmentData())
4763 return { };
4764
4765 RefPtr<HTMLAttachmentElement> attachment;
4766 if (auto* possibleAttachment = dynamicDowncast<HTMLAttachmentElement>(element))
4767 attachment = possibleAttachment;
4768 else if (auto* image = dynamicDowncast<HTMLImageElement>(element))
4769 attachment = image->attachmentElement();
4770
4771 if (!attachment)
4772 return { };
4773
4774 Vector<std::pair<String, RefPtr<WebCore::SharedBuffer>>> additionalTypesAndData;
4775#if PLATFORM(COCOA)(defined 1 && 1)
4776 getPasteboardTypesAndDataForAttachment(element, additionalTypesAndData);
4777#endif
4778
4779 return { attachment->uniqueIdentifier(), WTF::move(additionalTypesAndData) };
4780}
4781
4782void Editor::registerAttachmentIdentifier(const String& identifier, const String& contentType, const String& preferredFileName, Ref<FragmentedSharedBuffer>&& data)
4783{
4784 if (CheckedPtr client = this->client())
4785 client->registerAttachmentIdentifier(identifier, contentType, preferredFileName, WTF::move(data));
4786}
4787
4788void Editor::registerAttachmentIdentifier(const String& identifier, const String& contentType, const String& filePath)
4789{
4790 if (CheckedPtr client = this->client())
4791 client->registerAttachmentIdentifier(identifier, contentType, filePath);
4792}
4793
4794void Editor::registerAttachments(Vector<SerializedAttachmentData>&& data)
4795{
4796 if (CheckedPtr client = this->client())
4797 client->registerAttachments(WTF::move(data));
4798}
4799
4800void Editor::registerAttachmentIdentifier(const String& identifier, const AttachmentAssociatedElement& element)
4801{
4802 CheckedPtr client = this->client();
4803 if (!client)
4804 return;
4805
4806 auto attachmentInfo = [&]() -> std::optional<std::tuple<String, String, Ref<FragmentedSharedBuffer>>> {
4807 RefPtr imageElement = dynamicDowncast<HTMLImageElement>(element.asHTMLElement());
4808 if (!imageElement)
4809 return std::nullopt;
4810
4811 CheckedPtr renderer = dynamicDowncast<RenderImage>(imageElement->renderer());
4812 if (!renderer)
4813 return std::nullopt;
4814
4815 auto* cachedImage = renderer->cachedImage();
Local variable 'cachedImage' is uncounted and unsafe
4816 if (!cachedImage || cachedImage->errorOccurred())
4817 return std::nullopt;
4818
4819 String contentType;
4820 if (RefPtr image = cachedImage->image())
4821 contentType = image->mimeType();
4822
4823 if (contentType.isEmpty())
4824 return std::nullopt;
4825
4826 RefPtr imageData = cachedImage->resourceBuffer();
4827 if (!imageData)
4828 return std::nullopt;
4829
4830 String name = imageElement->attributeWithoutSynchronization(altAttr);
4831 if (name.isEmpty())
4832 name = imageElement->document().completeURL(imageElement->imageSourceURL()).lastPathComponent().toString();
4833
4834 if (name.isEmpty())
4835 return std::nullopt;
4836
4837 return { { WTF::move(contentType), WTF::move(name), imageData.releaseNonNull() } };
4838 }();
4839
4840 if (attachmentInfo) {
4841 auto& [contentType, preferredFileName, data] = *attachmentInfo;
4842 client->registerAttachmentIdentifier(identifier, WTF::move(contentType), WTF::move(preferredFileName), WTF::move(data));
4843 } else
4844 client->registerAttachmentIdentifier(identifier);
4845}
4846
4847void Editor::cloneAttachmentData(const String& fromIdentifier, const String& toIdentifier)
4848{
4849 if (CheckedPtr client = this->client())
4850 client->cloneAttachmentData(fromIdentifier, toIdentifier);
4851}
4852
4853void Editor::didInsertAttachmentElement(HTMLAttachmentElement& attachment)
4854{
4855 auto identifier = attachment.uniqueIdentifier();
4856 if (identifier.isEmpty())
4857 return;
4858
4859 if (!m_removedAttachmentIdentifiers.take(identifier))
4860 m_insertedAttachmentIdentifiers.add(identifier);
4861 scheduleEditorUIUpdate();
4862}
4863
4864void Editor::didRemoveAttachmentElement(HTMLAttachmentElement& attachment)
4865{
4866 auto identifier = attachment.uniqueIdentifier();
4867 if (identifier.isEmpty())
4868 return;
4869
4870 if (!m_insertedAttachmentIdentifiers.take(identifier))
4871 m_removedAttachmentIdentifiers.add(identifier);
4872 scheduleEditorUIUpdate();
4873}
4874
4875void Editor::notifyClientOfAttachmentUpdates()
4876{
4877 auto removedAttachmentIdentifiers = WTF::move(m_removedAttachmentIdentifiers);
4878 auto insertedAttachmentIdentifiers = WTF::move(m_insertedAttachmentIdentifiers);
4879 if (!client())
4880 return;
4881
4882 for (auto& identifier : removedAttachmentIdentifiers)
4883 client()->didRemoveAttachmentWithIdentifier(identifier);
4884
4885 for (auto& identifier : insertedAttachmentIdentifiers) {
4886 if (auto attachment = document().attachmentForIdentifier(identifier))
4887 client()->didInsertAttachmentWithIdentifier(identifier, attachment->attributeWithoutSynchronization(HTMLNames::srcAttr), attachment->associatedElementType());
4888 else
4889 ASSERT_NOT_REACHED()((void)0);
4890 }
4891}
4892
4893void Editor::insertAttachment(const String& identifier, std::optional<uint64_t>&& fileSize, const AtomString& fileName, const AtomString& contentType)
4894{
4895 Ref document = this->document();
4896 auto attachment = HTMLAttachmentElement::create(HTMLNames::attachmentTag, document);
4897 attachment->setUniqueIdentifier(identifier);
4898 attachment->updateAttributes(WTF::move(fileSize), contentType, fileName);
4899
4900 auto fragmentToInsert = document->createDocumentFragment();
4901 fragmentToInsert->appendChild(attachment.get());
4902
4903 replaceSelectionWithFragment(fragmentToInsert.get(), SelectReplacement::No, SmartReplace::No, MatchStyle::Yes);
4904}
4905
4906#endif // ENABLE(ATTACHMENT_ELEMENT)
4907
4908void Editor::handleAcceptedCandidate(TextCheckingResult acceptedCandidate)
4909{
4910 const VisibleSelection& selection = document().selection().selection();
4911
4912 m_isHandlingAcceptedCandidate = true;
4913
4914 if (auto range = rangeForTextCheckingResult(acceptedCandidate)) {
4915 if (shouldInsertText(acceptedCandidate.replacement, *range, EditorInsertAction::Typed))
4916 ReplaceRangeWithTextCommand::create(*range, acceptedCandidate.replacement)->apply();
4917 } else
4918 insertText(acceptedCandidate.replacement, nullptr);
4919
4920 if (auto insertedCandidateRange = rangeExpandedByCharactersInDirectionAtWordBoundary(selection.visibleStart(), acceptedCandidate.replacement.length(), SelectionDirection::Backward))
4921 addMarker(*insertedCandidateRange, DocumentMarkerType::AcceptedCandidate, acceptedCandidate.replacement);
4922
4923 m_isHandlingAcceptedCandidate = false;
4924}
4925
4926bool Editor::unifiedTextCheckerEnabled() const
4927{
4928 return WebCore::unifiedTextCheckerEnabled(document().frame());
4929}
4930
4931Vector<String> Editor::dictationAlternativesForMarker(const DocumentMarker& marker)
4932{
4933 return m_alternativeTextController->dictationAlternativesForMarker(marker);
4934}
4935
4936void Editor::applyDictationAlternative(const String& alternativeString)
4937{
4938 m_alternativeTextController->applyDictationAlternative(alternativeString);
4939}
4940
4941void Editor::toggleOverwriteModeEnabled()
4942{
4943 m_overwriteModeEnabled = !m_overwriteModeEnabled;
4944 document().selection().setShouldShowBlockCursor(m_overwriteModeEnabled);
4945}
4946
4947std::optional<SimpleRange> Editor::adjustedSelectionRange()
4948{
4949 // FIXME: Why do we need to adjust the selection to include the anchor tag it's in? Whoever wrote this code originally forgot to leave us a comment explaining the rationale.
4950 auto range = selectedRange();
4951 if (range) {
4952 if (RefPtr enclosingAnchor = enclosingElementWithTag(firstPositionInNode(*commonInclusiveAncestor<ComposedTree>(*range)), HTMLNames::aTag)) {
4953 if (firstPositionInOrBeforeNode(range->start.container.ptr()) >= makeDeprecatedLegacyPosition(range->start))
4954 range->start = makeBoundaryPointBeforeNodeContents(*enclosingAnchor);
4955 }
4956 }
4957 return range;
4958}
4959
4960// FIXME: This figures out the current style by inserting a <span>!
4961const RenderStyle* Editor::styleForSelectionStart(RefPtr<Node>& nodeToRemove)
4962{
4963 nodeToRemove = nullptr;
4964
4965 Ref document = this->document();
4966 if (document->selection().isNone())
4967 return nullptr;
4968
4969 Position position = adjustedSelectionStartForStyleComputation(document->selection().selection());
4970 if (!position.isCandidate() || position.isNull())
4971 return nullptr;
4972
4973 RefPtr typingStyle { document->selection().typingStyle() };
4974 if (!typingStyle || !typingStyle->style())
4975 return &position.deprecatedNode()->renderer()->style();
4976
4977 auto styleElement = HTMLSpanElement::create(document);
4978
4979 auto styleText = makeAtomString(typingStyle->style()->asText(CSS::defaultSerializationContext()), " display: inline"_s);
4980 styleElement->setAttribute(HTMLNames::styleAttr, styleText);
4981
4982 styleElement->appendChild(document->createEditingTextNode(String { emptyString() }));
4983
4984 RefPtr positionNode = position.deprecatedNode();
4985 ASSERT(positionNode)((void)0);
4986 RefPtr parent { positionNode->parentNode() };
4987 if (!parent || parent->appendChild(styleElement.get()).hasException())
4988 return nullptr;
4989
4990 nodeToRemove = styleElement.ptr();
4991
4992 document->updateStyleIfNeeded();
4993 return styleElement->renderer() ? &styleElement->renderer()->style() : nullptr;
4994}
4995
4996RefPtr<Font> Editor::fontForSelection(bool& hasMultipleFonts)
4997{
4998 hasMultipleFonts = false;
4999
5000 Ref document = this->document();
5001 if (!document->selection().isRange()) {
5002 RefPtr<Node> nodeToRemove;
5003 RefPtr<Font> font;
5004 {
5005 CheckedPtr style = styleForSelectionStart(nodeToRemove);
5006 if (!style)
5007 return nullptr;
5008 ScriptDisallowedScope::InMainThread scriptDisallowedScope;
5009 font = const_cast<Font*>(&style->fontCascade().primaryFont());
5010 }
5011
5012 if (nodeToRemove)
5013 nodeToRemove->remove();
5014
5015 return font;
5016 }
5017
5018 auto range = document->selection().selection().toNormalizedRange();
5019 if (!range)
5020 return nullptr;
5021
5022 // FIXME: Adjusting the start may move it past the end. In that case the iterator below will go on to the end of the document.
5023 auto adjustedStart = makeBoundaryPoint(adjustedSelectionStartForStyleComputation(document->selection().selection()));
5024 if (!adjustedStart)
5025 return nullptr;
5026 range->start = *adjustedStart;
5027
5028 ScriptDisallowedScope::InMainThread scriptDisallowedScope;
5029
5030 RefPtr<Font> font;
5031 for (Ref node : intersectingNodes(*range)) {
5032 CheckedPtr renderer = node->renderer();
5033 if (!renderer)
5034 continue;
5035 // The font of intermediate nodes that don't affect the rendering of text are not necessary to report, so limit to only such nodes.
5036 if (!node->isTextNode() && !renderer->isBR() && !TextNodeTraversal::firstChild(node))
5037 continue;
5038 Ref primaryFont = renderer->style().fontCascade().primaryFont();
5039 if (!font)
5040 font = const_cast<Font*>(primaryFont.ptr());
5041 else if (font != primaryFont.ptr()) {
5042 hasMultipleFonts = true;
5043 break;
5044 }
5045 }
5046
5047 return font;
5048}
5049
5050bool Editor::canCopyExcludingStandaloneImages() const
5051{
5052 auto& selection = document().selection().selection();
5053 return selection.isRange() && !selection.isInPasswordField();
5054}
5055
5056} // namespace WebCore