blob: 1eddc150789907a7a552e27360ca46497ddaa1ab [file] [log] [blame]
rniwa@webkit.orge9da7a82019-10-25 00:08:13 +00001/*
2 * Copyright (C) 2019 Apple Inc. All rights reserved.
3 *
4 * Redistribution and use in source and binary forms, with or without
5 * modification, are permitted provided that the following conditions
6 * are met:
7 * 1. Redistributions of source code must retain the above copyright
8 * notice, this list of conditions and the following disclaimer.
9 * 2. Redistributions in binary form must reproduce the above copyright
10 * notice, this list of conditions and the following disclaimer in the
11 * documentation and/or other materials provided with the distribution.
12 *
13 * THIS SOFTWARE IS PROVIDED BY APPLE INC. AND ITS CONTRIBUTORS ``AS IS''
14 * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
15 * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
16 * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL APPLE INC. OR ITS CONTRIBUTORS
17 * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
18 * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
19 * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
20 * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
21 * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
22 * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF
23 * THE POSSIBILITY OF SUCH DAMAGE.
24 */
25
26#include "config.h"
27#include "TextManipulationController.h"
28
wenson_hsieh@apple.come2c4adf2020-04-27 21:12:52 +000029#include "AccessibilityObject.h"
rniwa@webkit.org7afdb0b2019-10-25 20:41:15 +000030#include "CharacterData.h"
rniwa@webkit.orge9da7a82019-10-25 00:08:13 +000031#include "Editing.h"
rniwa@webkit.org7afdb0b2019-10-25 20:41:15 +000032#include "ElementAncestorIterator.h"
rniwa@webkit.orgc46d21d2019-12-16 22:36:55 +000033#include "EventLoop.h"
don.olmstead@sony.comfad633c2020-10-01 23:12:32 +000034#include "FrameView.h"
sihui_liu@apple.com5a2c1d32020-04-23 17:23:54 +000035#include "HTMLBRElement.h"
don.olmstead@sony.comf5e4de72020-03-12 03:19:09 +000036#include "HTMLElement.h"
wenson_hsieh@apple.com5f24d542020-05-02 02:26:23 +000037#include "HTMLInputElement.h"
rniwa@webkit.org640b0732020-03-08 03:52:06 +000038#include "HTMLNames.h"
sihui_liu@apple.com5a2c1d32020-04-23 17:23:54 +000039#include "HTMLParserIdioms.h"
wenson_hsieh@apple.comf0ad7db2020-06-03 21:08:21 +000040#include "InputTypeNames.h"
wenson_hsieh@apple.comf2cc5ed12020-06-25 20:37:34 +000041#include "NodeRenderStyle.h"
rniwa@webkit.org900f5602019-12-21 02:09:50 +000042#include "NodeTraversal.h"
43#include "PseudoElement.h"
don.olmstead@sony.comfad633c2020-10-01 23:12:32 +000044#include "RenderBox.h"
rniwa@webkit.orge9da7a82019-10-25 00:08:13 +000045#include "ScriptDisallowedScope.h"
rniwa@webkit.org900f5602019-12-21 02:09:50 +000046#include "Text.h"
rniwa@webkit.orge9da7a82019-10-25 00:08:13 +000047#include "TextIterator.h"
48#include "VisibleUnits.h"
49
50namespace WebCore {
51
rniwa@webkit.org7afdb0b2019-10-25 20:41:15 +000052inline bool TextManipulationController::ExclusionRule::match(const Element& element) const
53{
commit-queue@webkit.orgf0c773b2021-10-11 17:22:53 +000054 return WTF::switchOn(rule, [&element] (ElementRule rule) {
rniwa@webkit.org7afdb0b2019-10-25 20:41:15 +000055 return rule.localName == element.localName();
56 }, [&element] (AttributeRule rule) {
57 return equalIgnoringASCIICase(element.getAttribute(rule.name), rule.value);
commit-queue@webkit.orgd8730752019-12-04 18:36:15 +000058 }, [&element] (ClassRule rule) {
59 return element.hasClass() && element.classNames().contains(rule.className);
rniwa@webkit.org7afdb0b2019-10-25 20:41:15 +000060 });
61}
62
63class ExclusionRuleMatcher {
64public:
65 using ExclusionRule = TextManipulationController::ExclusionRule;
66 using Type = TextManipulationController::ExclusionRule::Type;
67
68 ExclusionRuleMatcher(const Vector<ExclusionRule>& rules)
69 : m_rules(rules)
70 { }
71
72 bool isExcluded(Node* node)
73 {
74 if (!node)
75 return false;
76
77 RefPtr<Element> startingElement = is<Element>(*node) ? downcast<Element>(node) : node->parentElement();
78 if (!startingElement)
79 return false;
80
81 Type type = Type::Include;
82 RefPtr<Element> matchingElement;
darin@apple.com25c0c842020-02-23 04:56:03 +000083 for (auto& element : lineageOfType<Element>(*startingElement)) {
rniwa@webkit.org7afdb0b2019-10-25 20:41:15 +000084 if (auto typeOrNullopt = typeForElement(element)) {
85 type = *typeOrNullopt;
86 matchingElement = &element;
87 break;
88 }
89 }
90
darin@apple.com25c0c842020-02-23 04:56:03 +000091 for (auto& element : lineageOfType<Element>(*startingElement)) {
rniwa@webkit.org7afdb0b2019-10-25 20:41:15 +000092 m_cache.set(element, type);
93 if (&element == matchingElement)
94 break;
95 }
96
97 return type == Type::Exclude;
98 }
99
darin@apple.coma4ddc782021-05-30 16:11:40 +0000100 std::optional<Type> typeForElement(Element& element)
rniwa@webkit.org7afdb0b2019-10-25 20:41:15 +0000101 {
102 auto it = m_cache.find(element);
103 if (it != m_cache.end())
104 return it->value;
105
106 for (auto& rule : m_rules) {
107 if (rule.match(element))
108 return rule.type;
109 }
110
darin@apple.com7c840b62021-05-28 01:26:23 +0000111 return std::nullopt;
rniwa@webkit.org7afdb0b2019-10-25 20:41:15 +0000112 }
113
114private:
115 const Vector<ExclusionRule>& m_rules;
116 HashMap<Ref<Element>, ExclusionRule::Type> m_cache;
117};
118
rniwa@webkit.orge9da7a82019-10-25 00:08:13 +0000119TextManipulationController::TextManipulationController(Document& document)
cdumez@apple.comf440d4c2021-10-13 15:37:52 +0000120 : m_document(document)
rniwa@webkit.orge9da7a82019-10-25 00:08:13 +0000121{
122}
123
rniwa@webkit.org7afdb0b2019-10-25 20:41:15 +0000124void TextManipulationController::startObservingParagraphs(ManipulationItemCallback&& callback, Vector<ExclusionRule>&& exclusionRules)
rniwa@webkit.orge9da7a82019-10-25 00:08:13 +0000125{
cdumez@apple.come1840be2021-09-20 21:45:40 +0000126 RefPtr document { m_document.get() };
rniwa@webkit.orge9da7a82019-10-25 00:08:13 +0000127 if (!document)
128 return;
129
130 m_callback = WTFMove(callback);
rniwa@webkit.org7afdb0b2019-10-25 20:41:15 +0000131 m_exclusionRules = WTFMove(exclusionRules);
rniwa@webkit.orge9da7a82019-10-25 00:08:13 +0000132
rniwa@webkit.org640b0732020-03-08 03:52:06 +0000133 observeParagraphs(firstPositionInNode(m_document.get()), lastPositionInNode(m_document.get()));
rniwa@webkit.orge44b91352020-03-04 05:22:14 +0000134 flushPendingItemsForCallback();
rniwa@webkit.orgc46d21d2019-12-16 22:36:55 +0000135}
136
sihui_liu@apple.com0e39ec02020-06-09 06:54:20 +0000137static bool isInPrivateUseArea(UChar character)
138{
139 return 0xE000 <= character && character <= 0xF8FF;
140}
141
142static bool isTokenDelimiter(UChar character)
143{
144 return isHTMLLineBreak(character) || isInPrivateUseArea(character);
145}
146
commit-queue@webkit.org91b8d042020-07-27 22:02:03 +0000147static bool isNotSpace(UChar character)
148{
149 if (character == noBreakSpace)
150 return false;
151
152 return isNotHTMLSpace(character);
153}
154
rniwa@webkit.orgcffb7892020-03-07 01:14:23 +0000155class ParagraphContentIterator {
156public:
157 ParagraphContentIterator(const Position& start, const Position& end)
wenson_hsieh@apple.com834717a2021-06-07 04:11:09 +0000158 : m_iterator(*makeSimpleRange(start, end), TextIteratorBehavior::IgnoresStyleVisibility)
sihui_liu@apple.com12ab7a72020-06-01 23:54:34 +0000159 , m_node(start.firstNode())
rniwa@webkit.orgcffb7892020-03-07 01:14:23 +0000160 , m_pastEndNode(end.firstNode())
161 {
sihui_liu@apple.com12ab7a72020-06-01 23:54:34 +0000162 if (shouldAdvanceIteratorPastCurrentNode())
163 advanceIteratorNodeAndUpdateText();
rniwa@webkit.orgcffb7892020-03-07 01:14:23 +0000164 }
165
166 void advance()
167 {
darin@apple.com7c840b62021-05-28 01:26:23 +0000168 m_text = std::nullopt;
sihui_liu@apple.com12ab7a72020-06-01 23:54:34 +0000169 advanceNode();
rniwa@webkit.orgcffb7892020-03-07 01:14:23 +0000170
sihui_liu@apple.com12ab7a72020-06-01 23:54:34 +0000171 if (shouldAdvanceIteratorPastCurrentNode())
172 advanceIteratorNodeAndUpdateText();
rniwa@webkit.orgcffb7892020-03-07 01:14:23 +0000173 }
174
175 struct CurrentContent {
176 RefPtr<Node> node;
sihui_liu@apple.com0e39ec02020-06-09 06:54:20 +0000177 Vector<String> text;
rniwa@webkit.orgcffb7892020-03-07 01:14:23 +0000178 bool isTextContent { false };
179 bool isReplacedContent { false };
180 };
181
182 CurrentContent currentContent()
183 {
sihui_liu@apple.com0e39ec02020-06-09 06:54:20 +0000184 CurrentContent content = { m_node.copyRef(), m_text ? m_text.value() : Vector<String> { }, !!m_text };
rniwa@webkit.orgcffb7892020-03-07 01:14:23 +0000185 if (content.node) {
186 if (auto* renderer = content.node->renderer()) {
187 if (renderer->isRenderReplaced()) {
188 content.isTextContent = false;
189 content.isReplacedContent = true;
190 }
191 }
192 }
193 return content;
194 }
195
sihui_liu@apple.com12ab7a72020-06-01 23:54:34 +0000196 bool atEnd() const { return !m_text && m_iterator.atEnd() && m_node == m_pastEndNode; }
rniwa@webkit.orgcffb7892020-03-07 01:14:23 +0000197
198private:
commit-queue@webkit.org10778122020-07-08 18:30:44 +0000199 bool shouldAdvanceIteratorPastCurrentNode() const
200 {
201 if (m_iterator.atEnd())
202 return false;
203
204 auto* iteratorNode = m_iterator.node();
205 return !iteratorNode || iteratorNode == m_node;
206 }
sihui_liu@apple.com12ab7a72020-06-01 23:54:34 +0000207
208 void advanceNode()
rniwa@webkit.orgcffb7892020-03-07 01:14:23 +0000209 {
sihui_liu@apple.com12ab7a72020-06-01 23:54:34 +0000210 if (m_node == m_pastEndNode)
sihui_liu@apple.com5a2c1d32020-04-23 17:23:54 +0000211 return;
212
sihui_liu@apple.com12ab7a72020-06-01 23:54:34 +0000213 m_node = NodeTraversal::next(*m_node);
214 if (!m_node)
215 m_node = m_pastEndNode;
216 }
217
sihui_liu@apple.com0e39ec02020-06-09 06:54:20 +0000218 void appendToText(Vector<String>& text, StringBuilder& stringBuilder)
219 {
220 if (!stringBuilder.isEmpty()) {
221 text.append(stringBuilder.toString());
222 stringBuilder.clear();
223 }
224 }
225
sihui_liu@apple.com12ab7a72020-06-01 23:54:34 +0000226 void advanceIteratorNodeAndUpdateText()
227 {
228 ASSERT(shouldAdvanceIteratorPastCurrentNode());
229
230 StringBuilder stringBuilder;
sihui_liu@apple.com0e39ec02020-06-09 06:54:20 +0000231 Vector<String> text;
sihui_liu@apple.com12ab7a72020-06-01 23:54:34 +0000232 while (shouldAdvanceIteratorPastCurrentNode()) {
commit-queue@webkit.org10778122020-07-08 18:30:44 +0000233 auto iteratorText = m_iterator.text();
234 if (m_iterator.range().collapsed()) {
cdumez@apple.com28e0c9c2022-06-02 15:51:24 +0000235 if (iteratorText == "\n"_s) {
sihui_liu@apple.com0e39ec02020-06-09 06:54:20 +0000236 appendToText(text, stringBuilder);
237 text.append({ });
238 }
239 } else
commit-queue@webkit.org10778122020-07-08 18:30:44 +0000240 stringBuilder.append(iteratorText);
sihui_liu@apple.com0e39ec02020-06-09 06:54:20 +0000241
sihui_liu@apple.com12ab7a72020-06-01 23:54:34 +0000242 m_iterator.advance();
sihui_liu@apple.com12ab7a72020-06-01 23:54:34 +0000243 }
sihui_liu@apple.com0e39ec02020-06-09 06:54:20 +0000244 appendToText(text, stringBuilder);
245 m_text = text;
rniwa@webkit.orgcffb7892020-03-07 01:14:23 +0000246 }
247
248 TextIterator m_iterator;
sihui_liu@apple.com12ab7a72020-06-01 23:54:34 +0000249 RefPtr<Node> m_node;
rniwa@webkit.orgcffb7892020-03-07 01:14:23 +0000250 RefPtr<Node> m_pastEndNode;
darin@apple.coma4ddc782021-05-30 16:11:40 +0000251 std::optional<Vector<String>> m_text;
rniwa@webkit.orgcffb7892020-03-07 01:14:23 +0000252};
253
wenson_hsieh@apple.comf0ad7db2020-06-03 21:08:21 +0000254static bool shouldExtractValueForTextManipulation(const HTMLInputElement& input)
255{
aperez@igalia.comcb5f46712020-06-05 15:47:41 +0000256 if (input.isSearchField() || equalIgnoringASCIICase(input.attributeWithoutSynchronization(HTMLNames::typeAttr), InputTypeNames::text()))
wenson_hsieh@apple.comf0ad7db2020-06-03 21:08:21 +0000257 return !input.lastChangeWasUserEdit();
258
259 return input.isTextButton();
260}
261
262static bool isAttributeForTextManipulation(const QualifiedName& nameToCheck)
rniwa@webkit.orgc46d21d2019-12-16 22:36:55 +0000263{
rniwa@webkit.org640b0732020-03-08 03:52:06 +0000264 using namespace HTMLNames;
265 static const QualifiedName* const attributeNames[] = {
266 &titleAttr.get(),
267 &altAttr.get(),
268 &placeholderAttr.get(),
269 &aria_labelAttr.get(),
270 &aria_placeholderAttr.get(),
271 &aria_roledescriptionAttr.get(),
272 &aria_valuetextAttr.get(),
273 };
274 for (auto& entry : attributeNames) {
275 if (*entry == nameToCheck)
276 return true;
277 }
278 return false;
279}
280
wenson_hsieh@apple.com2d16b752020-04-20 20:19:54 +0000281static bool canPerformTextManipulationByReplacingEntireTextContent(const Element& element)
282{
283 return element.hasTagName(HTMLNames::titleTag) || element.hasTagName(HTMLNames::optionTag);
284}
285
sihui_liu@apple.comd8cc9942020-08-07 01:24:05 +0000286static bool areEqualIgnoringLeadingAndTrailingWhitespaces(const String& content, const String& originalContent)
287{
288 return content.stripWhiteSpace() == originalContent.stripWhiteSpace();
289}
290
darin@apple.coma4ddc782021-05-30 16:11:40 +0000291static std::optional<TextManipulationController::ManipulationTokenInfo> tokenInfo(Node* node)
wenson_hsieh@apple.comeeb352d2020-04-29 05:16:23 +0000292{
293 if (!node)
darin@apple.com7c840b62021-05-28 01:26:23 +0000294 return std::nullopt;
wenson_hsieh@apple.comeeb352d2020-04-29 05:16:23 +0000295
296 TextManipulationController::ManipulationTokenInfo result;
297 result.documentURL = node->document().url();
cdumez@apple.come1840be2021-09-20 21:45:40 +0000298 if (RefPtr element = is<Element>(node) ? downcast<Element>(node) : node->parentElement()) {
wenson_hsieh@apple.comeeb352d2020-04-29 05:16:23 +0000299 result.tagName = element->tagName();
300 if (element->hasAttributeWithoutSynchronization(HTMLNames::roleAttr))
301 result.roleAttribute = element->attributeWithoutSynchronization(HTMLNames::roleAttr);
cdumez@apple.come1840be2021-09-20 21:45:40 +0000302 if (RefPtr frame = node->document().frame(); frame && frame->view() && element->renderer()) {
wenson_hsieh@apple.com71594152020-09-13 15:43:58 +0000303 // FIXME: This doesn't account for overflow clip.
304 auto elementRect = element->renderer()->absoluteAnchorRect();
305 auto visibleContentRect = frame->view()->visibleContentRect();
306 result.isVisible = visibleContentRect.intersects(enclosingIntRect(elementRect));
307 }
wenson_hsieh@apple.comeeb352d2020-04-29 05:16:23 +0000308 }
309 return result;
310}
311
sihui_liu@apple.com12ab7a72020-06-01 23:54:34 +0000312static bool isEnclosingItemBoundaryElement(const Element& element)
313{
314 auto* renderer = element.renderer();
315 if (!renderer)
316 return false;
317
318 auto role = [](const Element& element) -> AccessibilityRole {
319 return AccessibilityObject::ariaRoleToWebCoreRole(element.attributeWithoutSynchronization(HTMLNames::roleAttr));
320 };
321
322 if (element.hasTagName(HTMLNames::buttonTag) || role(element) == AccessibilityRole::Button)
323 return true;
324
commit-queue@webkit.orgcd8dd152020-07-05 23:32:57 +0000325 auto displayType = renderer->style().display();
sihui_liu@apple.com12ab7a72020-06-01 23:54:34 +0000326 if (element.hasTagName(HTMLNames::liTag) || element.hasTagName(HTMLNames::aTag)) {
sihui_liu@apple.com12ab7a72020-06-01 23:54:34 +0000327 if (displayType == DisplayType::Block || displayType == DisplayType::InlineBlock)
328 return true;
329
cdumez@apple.come1840be2021-09-20 21:45:40 +0000330 for (RefPtr parent = element.parentElement(); parent; parent = parent->parentElement()) {
sihui_liu@apple.com12ab7a72020-06-01 23:54:34 +0000331 if (parent->hasTagName(HTMLNames::navTag) || role(*parent) == AccessibilityRole::LandmarkNavigation)
332 return true;
333 }
334 }
335
wenson_hsieh@apple.comb446e4b2020-08-12 20:11:37 +0000336 if (displayType == DisplayType::TableCell)
wenson_hsieh@apple.com08bdec52020-08-02 20:49:26 +0000337 return true;
338
commit-queue@webkit.orgcd8dd152020-07-05 23:32:57 +0000339 if (element.hasTagName(HTMLNames::spanTag) && displayType == DisplayType::InlineBlock)
340 return true;
341
wenson_hsieh@apple.com660e69c2020-07-22 22:10:20 +0000342 if (displayType == DisplayType::Block && (element.hasTagName(HTMLNames::h1Tag) || element.hasTagName(HTMLNames::h2Tag) || element.hasTagName(HTMLNames::h3Tag)
343 || element.hasTagName(HTMLNames::h4Tag) || element.hasTagName(HTMLNames::h5Tag) || element.hasTagName(HTMLNames::h6Tag)))
344 return true;
345
sihui_liu@apple.com12ab7a72020-06-01 23:54:34 +0000346 return false;
347}
348
sihui_liu@apple.com0e39ec02020-06-09 06:54:20 +0000349TextManipulationController::ManipulationUnit TextManipulationController::createUnit(const Vector<String>& text, Node& textNode)
wenson_hsieh@apple.com600d5b62020-06-05 19:59:00 +0000350{
sihui_liu@apple.com0e39ec02020-06-09 06:54:20 +0000351 ManipulationUnit unit = { textNode, { } };
352 for (auto& textEntry : text) {
353 if (!textEntry.isNull())
354 parse(unit, textEntry, textNode);
355 else {
356 if (unit.tokens.isEmpty())
357 unit.firstTokenContainsDelimiter = true;
358 unit.lastTokenContainsDelimiter = true;
359 }
360 }
361 return unit;
wenson_hsieh@apple.com600d5b62020-06-05 19:59:00 +0000362}
363
wenson_hsieh@apple.comf2cc5ed12020-06-25 20:37:34 +0000364bool TextManipulationController::shouldExcludeNodeBasedOnStyle(const Node& node)
365{
366 auto* style = node.renderStyle();
367 if (!style)
368 return false;
369
370 auto& font = style->fontCascade().primaryFont();
371 auto familyName = font.platformData().familyName();
372 if (familyName.isEmpty())
373 return false;
374
375 auto iter = m_cachedFontFamilyExclusionResults.find(familyName);
376 if (iter != m_cachedFontFamilyExclusionResults.end())
377 return iter->value;
378
379 // FIXME: We should reconsider whether a node should be excluded if the primary font
380 // used to render the node changes, since this "icon font" heuristic may return a
381 // different result.
382 bool result = font.isProbablyOnlyUsedToRenderIcons();
383 m_cachedFontFamilyExclusionResults.set(familyName, result);
384 return result;
385}
386
sihui_liu@apple.com0e39ec02020-06-09 06:54:20 +0000387void TextManipulationController::parse(ManipulationUnit& unit, const String& text, Node& textNode)
wenson_hsieh@apple.com600d5b62020-06-05 19:59:00 +0000388{
sihui_liu@apple.com12ab7a72020-06-01 23:54:34 +0000389 ExclusionRuleMatcher exclusionRuleMatcher(m_exclusionRules);
wenson_hsieh@apple.comf2cc5ed12020-06-25 20:37:34 +0000390 bool isNodeExcluded = exclusionRuleMatcher.isExcluded(&textNode) || shouldExcludeNodeBasedOnStyle(textNode);
Hironori.Fujii@sony.com6e2fa9f2021-10-26 07:23:01 +0000391 size_t positionOfLastNonHTMLSpace = notFound;
sihui_liu@apple.com12ab7a72020-06-01 23:54:34 +0000392 size_t startPositionOfCurrentToken = 0;
sihui_liu@apple.com12ab7a72020-06-01 23:54:34 +0000393 size_t index = 0;
394 for (; index < text.length(); ++index) {
395 auto character = text[index];
wenson_hsieh@apple.com600d5b62020-06-05 19:59:00 +0000396 if (isTokenDelimiter(character)) {
Hironori.Fujii@sony.com6e2fa9f2021-10-26 07:23:01 +0000397 if (positionOfLastNonHTMLSpace != notFound && startPositionOfCurrentToken <= positionOfLastNonHTMLSpace) {
sihui_liu@apple.com0e39ec02020-06-09 06:54:20 +0000398 auto stringForToken = text.substring(startPositionOfCurrentToken, positionOfLastNonHTMLSpace + 1 - startPositionOfCurrentToken);
399 unit.tokens.append(ManipulationToken { m_tokenIdentifier.generate(), stringForToken, tokenInfo(&textNode), isNodeExcluded });
sihui_liu@apple.com12ab7a72020-06-01 23:54:34 +0000400 startPositionOfCurrentToken = positionOfLastNonHTMLSpace + 1;
401 }
402
wenson_hsieh@apple.com600d5b62020-06-05 19:59:00 +0000403 while (index < text.length() && (isHTMLSpace(text[index]) || isInPrivateUseArea(text[index])))
sihui_liu@apple.com12ab7a72020-06-01 23:54:34 +0000404 ++index;
405
406 --index;
407
sihui_liu@apple.com0e39ec02020-06-09 06:54:20 +0000408 auto stringForToken = text.substring(startPositionOfCurrentToken, index + 1 - startPositionOfCurrentToken);
409 if (unit.tokens.isEmpty() && !unit.firstTokenContainsDelimiter)
410 unit.firstTokenContainsDelimiter = true;
411 unit.tokens.append(ManipulationToken { m_tokenIdentifier.generate(), stringForToken, tokenInfo(&textNode), true });
sihui_liu@apple.com12ab7a72020-06-01 23:54:34 +0000412 startPositionOfCurrentToken = index + 1;
sihui_liu@apple.com0e39ec02020-06-09 06:54:20 +0000413 unit.lastTokenContainsDelimiter = true;
commit-queue@webkit.org91b8d042020-07-27 22:02:03 +0000414 } else if (isNotSpace(character)) {
sihui_liu@apple.com0e39ec02020-06-09 06:54:20 +0000415 if (!isNodeExcluded)
416 unit.areAllTokensExcluded = false;
wenson_hsieh@apple.com600d5b62020-06-05 19:59:00 +0000417 positionOfLastNonHTMLSpace = index;
sihui_liu@apple.com12ab7a72020-06-01 23:54:34 +0000418 }
419 }
420
421 if (startPositionOfCurrentToken < text.length()) {
sihui_liu@apple.com0e39ec02020-06-09 06:54:20 +0000422 auto stringForToken = text.substring(startPositionOfCurrentToken, index + 1 - startPositionOfCurrentToken);
423 unit.tokens.append(ManipulationToken { m_tokenIdentifier.generate(), stringForToken, tokenInfo(&textNode), isNodeExcluded });
424 unit.lastTokenContainsDelimiter = false;
sihui_liu@apple.com12ab7a72020-06-01 23:54:34 +0000425 }
sihui_liu@apple.com33d45222020-06-05 06:13:30 +0000426}
427
428void TextManipulationController::addItemIfPossible(Vector<ManipulationUnit>&& units)
429{
430 if (units.isEmpty())
431 return;
432
433 size_t index = 0;
434 size_t end = units.size();
435 while (index < units.size() && units[index].areAllTokensExcluded)
436 ++index;
437
438 while (end > 0 && units[end - 1].areAllTokensExcluded)
439 --end;
440
441 if (index == end)
442 return;
443
sihui_liu@apple.coma6421802020-06-09 07:11:12 +0000444 ASSERT(end);
445 auto startPosition = firstPositionInOrBeforeNode(units[index].node.ptr());
446 auto endPosition = positionAfterNode(units[end - 1].node.ptr());
sihui_liu@apple.com33d45222020-06-05 06:13:30 +0000447 Vector<ManipulationToken> tokens;
448 for (; index < end; ++index)
449 tokens.appendVector(WTFMove(units[index].tokens));
450
451 addItem(ManipulationItemData { startPosition, endPosition, nullptr, nullQName(), WTFMove(tokens) });
sihui_liu@apple.com12ab7a72020-06-01 23:54:34 +0000452}
453
rniwa@webkit.org640b0732020-03-08 03:52:06 +0000454void TextManipulationController::observeParagraphs(const Position& start, const Position& end)
455{
wenson_hsieh@apple.comf0a232a2020-07-07 00:54:23 +0000456 if (start.isNull() || end.isNull() || start.isOrphan() || end.isOrphan())
wenson_hsieh@apple.comc626f5c2020-04-08 23:59:31 +0000457 return;
458
cdumez@apple.come1840be2021-09-20 21:45:40 +0000459 RefPtr document { start.document() };
rniwa@webkit.orgc46d21d2019-12-16 22:36:55 +0000460 ASSERT(document);
sihui_liu@apple.com12ab7a72020-06-01 23:54:34 +0000461 // TextIterator's constructor may have updated the layout and executed arbitrary scripts.
rniwa@webkit.org640b0732020-03-08 03:52:06 +0000462 if (document != start.document() || document != end.document())
sihui_liu@apple.com12ab7a72020-06-01 23:54:34 +0000463 return;
rniwa@webkit.orge9da7a82019-10-25 00:08:13 +0000464
sihui_liu@apple.com33d45222020-06-05 06:13:30 +0000465 Vector<ManipulationUnit> unitsInCurrentParagraph;
wenson_hsieh@apple.com5f606e42020-06-16 18:11:23 +0000466 Vector<Ref<Element>> enclosingItemBoundaryElements;
sihui_liu@apple.com33d45222020-06-05 06:13:30 +0000467 ParagraphContentIterator iterator { start, end };
rniwa@webkit.orgcffb7892020-03-07 01:14:23 +0000468 for (; !iterator.atEnd(); iterator.advance()) {
469 auto content = iterator.currentContent();
sihui_liu@apple.com12ab7a72020-06-01 23:54:34 +0000470 auto* contentNode = content.node.get();
471 ASSERT(contentNode);
wenson_hsieh@apple.comf9a72332020-04-24 23:18:05 +0000472
wenson_hsieh@apple.com5f606e42020-06-16 18:11:23 +0000473 while (!enclosingItemBoundaryElements.isEmpty() && !enclosingItemBoundaryElements.last()->contains(contentNode)) {
sihui_liu@apple.com33d45222020-06-05 06:13:30 +0000474 addItemIfPossible(std::exchange(unitsInCurrentParagraph, { }));
wenson_hsieh@apple.com5f606e42020-06-16 18:11:23 +0000475 enclosingItemBoundaryElements.removeLast();
sihui_liu@apple.com12ab7a72020-06-01 23:54:34 +0000476 }
rniwa@webkit.orgcffb7892020-03-07 01:14:23 +0000477
rniwa@webkit.org671571e2021-04-13 04:19:17 +0000478 if (m_manipulatedNodes.contains(*contentNode)) {
sihui_liu@apple.com65a25812020-06-26 16:41:15 +0000479 addItemIfPossible(std::exchange(unitsInCurrentParagraph, { }));
480 continue;
sihui_liu@apple.com12ab7a72020-06-01 23:54:34 +0000481 }
482
483 if (is<Element>(*contentNode)) {
484 auto& currentElement = downcast<Element>(*contentNode);
485 if (!content.isTextContent && canPerformTextManipulationByReplacingEntireTextContent(currentElement))
cdumez@apple.comf440d4c2021-10-13 15:37:52 +0000486 addItem(ManipulationItemData { Position(), Position(), currentElement, nullQName(), { ManipulationToken { m_tokenIdentifier.generate(), currentElement.textContent(), tokenInfo(&currentElement) } } });
sihui_liu@apple.com12ab7a72020-06-01 23:54:34 +0000487
488 if (currentElement.hasAttributes()) {
489 for (auto& attribute : currentElement.attributesIterator()) {
wenson_hsieh@apple.comf0ad7db2020-06-03 21:08:21 +0000490 if (isAttributeForTextManipulation(attribute.name()))
cdumez@apple.comf440d4c2021-10-13 15:37:52 +0000491 addItem(ManipulationItemData { Position(), Position(), currentElement, attribute.name(), { ManipulationToken { m_tokenIdentifier.generate(), attribute.value(), tokenInfo(&currentElement) } } });
rniwa@webkit.org640b0732020-03-08 03:52:06 +0000492 }
rniwa@webkit.org640b0732020-03-08 03:52:06 +0000493 }
wenson_hsieh@apple.comf0ad7db2020-06-03 21:08:21 +0000494
495 if (is<HTMLInputElement>(currentElement)) {
496 auto& input = downcast<HTMLInputElement>(currentElement);
497 if (shouldExtractValueForTextManipulation(input))
cdumez@apple.comf440d4c2021-10-13 15:37:52 +0000498 addItem(ManipulationItemData { { }, { }, currentElement, HTMLNames::valueAttr, { ManipulationToken { m_tokenIdentifier.generate(), input.value(), tokenInfo(&currentElement) } } });
wenson_hsieh@apple.comf0ad7db2020-06-03 21:08:21 +0000499 }
500
wenson_hsieh@apple.com5f606e42020-06-16 18:11:23 +0000501 if (isEnclosingItemBoundaryElement(currentElement)) {
502 addItemIfPossible(std::exchange(unitsInCurrentParagraph, { }));
503 enclosingItemBoundaryElements.append(currentElement);
504 }
rniwa@webkit.org95386982020-02-28 03:37:22 +0000505 }
506
rniwa@webkit.orgcffb7892020-03-07 01:14:23 +0000507 if (content.isReplacedContent) {
sihui_liu@apple.com33d45222020-06-05 06:13:30 +0000508 if (!unitsInCurrentParagraph.isEmpty())
cdumez@apple.comec2f5082022-03-28 21:20:09 +0000509 unitsInCurrentParagraph.append(ManipulationUnit { *contentNode, { ManipulationToken { m_tokenIdentifier.generate(), "[]"_s, tokenInfo(content.node.get()), true } } });
rniwa@webkit.orgcffb7892020-03-07 01:14:23 +0000510 continue;
511 }
512
513 if (!content.isTextContent)
514 continue;
515
sihui_liu@apple.com0e39ec02020-06-09 06:54:20 +0000516 auto currentUnit = createUnit(content.text, *contentNode);
517 if (currentUnit.firstTokenContainsDelimiter)
sihui_liu@apple.com33d45222020-06-05 06:13:30 +0000518 addItemIfPossible(std::exchange(unitsInCurrentParagraph, { }));
sihui_liu@apple.com5a2c1d32020-04-23 17:23:54 +0000519
sihui_liu@apple.com0e39ec02020-06-09 06:54:20 +0000520 if (unitsInCurrentParagraph.isEmpty() && currentUnit.areAllTokensExcluded)
521 continue;
rniwa@webkit.orge9da7a82019-10-25 00:08:13 +0000522
sihui_liu@apple.com0e39ec02020-06-09 06:54:20 +0000523 bool currentUnitEndsWithDelimiter = currentUnit.lastTokenContainsDelimiter;
524 unitsInCurrentParagraph.append(WTFMove(currentUnit));
sihui_liu@apple.com12ab7a72020-06-01 23:54:34 +0000525
sihui_liu@apple.com0e39ec02020-06-09 06:54:20 +0000526 if (currentUnitEndsWithDelimiter)
sihui_liu@apple.com33d45222020-06-05 06:13:30 +0000527 addItemIfPossible(std::exchange(unitsInCurrentParagraph, { }));
rniwa@webkit.orge9da7a82019-10-25 00:08:13 +0000528 }
529
sihui_liu@apple.com33d45222020-06-05 06:13:30 +0000530 addItemIfPossible(std::exchange(unitsInCurrentParagraph, { }));
rniwa@webkit.orge9da7a82019-10-25 00:08:13 +0000531}
532
sihui_liu@apple.com1f7fe992022-03-04 01:32:11 +0000533void TextManipulationController::didUpdateContentForNode(Node& node)
rniwa@webkit.orgc46d21d2019-12-16 22:36:55 +0000534{
sihui_liu@apple.com1f7fe992022-03-04 01:32:11 +0000535 if (!m_manipulatedNodes.contains(node))
wenson_hsieh@apple.com54632b72020-01-10 00:40:14 +0000536 return;
537
commit-queue@webkit.org3468c732020-07-13 17:36:21 +0000538 scheduleObservationUpdate();
rniwa@webkit.org900f5602019-12-21 02:09:50 +0000539
sihui_liu@apple.com1f7fe992022-03-04 01:32:11 +0000540 m_manipulatedNodesWithNewContent.add(node);
541}
542
543void TextManipulationController::didAddOrCreateRendererForNode(Node& node)
544{
545 if (m_manipulatedNodes.contains(node))
546 return;
547
548 scheduleObservationUpdate();
549
550 if (is<PseudoElement>(node)) {
551 if (auto* host = downcast<PseudoElement>(node).hostElement())
552 m_addedOrNewlyRenderedNodes.add(*host);
rniwa@webkit.org900f5602019-12-21 02:09:50 +0000553 } else
sihui_liu@apple.com1f7fe992022-03-04 01:32:11 +0000554 m_addedOrNewlyRenderedNodes.add(node);
commit-queue@webkit.org6d253ad2020-08-05 15:46:59 +0000555}
556
wenson_hsieh@apple.comd0ed2bd2020-04-24 00:07:25 +0000557void TextManipulationController::scheduleObservationUpdate()
rniwa@webkit.orgc46d21d2019-12-16 22:36:55 +0000558{
ggaren@apple.com991cea12020-12-09 00:03:34 +0000559 if (m_didScheduleObservationUpdate)
commit-queue@webkit.org3468c732020-07-13 17:36:21 +0000560 return;
561
rniwa@webkit.orgc46d21d2019-12-16 22:36:55 +0000562 if (!m_document)
563 return;
564
ggaren@apple.com991cea12020-12-09 00:03:34 +0000565 m_didScheduleObservationUpdate = true;
566
cdumez@apple.com7a1459d2021-10-12 20:09:19 +0000567 m_document->eventLoop().queueTask(TaskSource::InternalAsyncTask, [weakThis = WeakPtr { *this }] {
rniwa@webkit.orgc46d21d2019-12-16 22:36:55 +0000568 auto* controller = weakThis.get();
569 if (!controller)
570 return;
571
ggaren@apple.com991cea12020-12-09 00:03:34 +0000572 controller->m_didScheduleObservationUpdate = false;
573
commit-queue@webkit.org3468c732020-07-13 17:36:21 +0000574 HashSet<Ref<Node>> nodesToObserve;
sihui_liu@apple.com1f7fe992022-03-04 01:32:11 +0000575 for (auto& text : controller->m_manipulatedNodesWithNewContent) {
commit-queue@webkit.org3468c732020-07-13 17:36:21 +0000576 if (!controller->m_manipulatedNodes.contains(text))
577 continue;
578 controller->m_manipulatedNodes.remove(text);
rniwa@webkit.org671571e2021-04-13 04:19:17 +0000579 nodesToObserve.add(text);
commit-queue@webkit.org3468c732020-07-13 17:36:21 +0000580 }
sihui_liu@apple.com1f7fe992022-03-04 01:32:11 +0000581 controller->m_manipulatedNodesWithNewContent.clear();
commit-queue@webkit.org3468c732020-07-13 17:36:21 +0000582
sihui_liu@apple.com1f7fe992022-03-04 01:32:11 +0000583 for (auto& node : controller->m_addedOrNewlyRenderedNodes)
584 nodesToObserve.add(node);
585 controller->m_addedOrNewlyRenderedNodes.clear();
commit-queue@webkit.org6d253ad2020-08-05 15:46:59 +0000586
commit-queue@webkit.org3468c732020-07-13 17:36:21 +0000587 if (nodesToObserve.isEmpty())
sihui_liu@apple.com65a25812020-06-26 16:41:15 +0000588 return;
589
590 RefPtr<Node> commonAncestor;
commit-queue@webkit.org3468c732020-07-13 17:36:21 +0000591 for (auto& node : nodesToObserve) {
ggaren@apple.com991cea12020-12-09 00:03:34 +0000592 if (!node->isConnected())
593 continue;
wenson_hsieh@apple.comeff0fdf2021-01-07 23:09:29 +0000594
drousso@apple.comb706fa02021-09-24 18:04:35 +0000595 if (RefPtr host = node->shadowHost(); is<HTMLInputElement>(host) && downcast<HTMLInputElement>(*host).lastChangeWasUserEdit())
wenson_hsieh@apple.comeff0fdf2021-01-07 23:09:29 +0000596 continue;
597
sihui_liu@apple.com65a25812020-06-26 16:41:15 +0000598 if (!commonAncestor)
drousso@apple.comb706fa02021-09-24 18:04:35 +0000599 commonAncestor = is<ContainerNode>(node) ? node.ptr() : node->parentNode();
commit-queue@webkit.org3468c732020-07-13 17:36:21 +0000600 else if (!node->isDescendantOf(commonAncestor.get()))
darin@apple.comdc888502020-11-05 17:36:29 +0000601 commonAncestor = commonInclusiveAncestor<ComposedTree>(*commonAncestor, node.get());
rniwa@webkit.orgc46d21d2019-12-16 22:36:55 +0000602 }
commit-queue@webkit.org3468c732020-07-13 17:36:21 +0000603
sihui_liu@apple.com1f7fe992022-03-04 01:32:11 +0000604 Position start;
605 if (auto* element = downcast<Element>(commonAncestor.get())) {
606 // Ensure to include the element in the range.
607 if (canPerformTextManipulationByReplacingEntireTextContent(*element))
608 start = positionBeforeNode(commonAncestor.get());
609 }
610 if (start.isNull())
611 start = firstPositionInOrBeforeNode(commonAncestor.get());
612
sihui_liu@apple.com65a25812020-06-26 16:41:15 +0000613 auto end = lastPositionInOrAfterNode(commonAncestor.get());
614 controller->observeParagraphs(start, end);
rniwa@webkit.orgc46d21d2019-12-16 22:36:55 +0000615
ggaren@apple.com991cea12020-12-09 00:03:34 +0000616 if (controller->m_items.isEmpty() && commonAncestor) {
cdumez@apple.comaadf82b2021-10-14 01:46:22 +0000617 controller->m_manipulatedNodes.add(*commonAncestor);
sihui_liu@apple.com65a25812020-06-26 16:41:15 +0000618 return;
rniwa@webkit.orgc46d21d2019-12-16 22:36:55 +0000619 }
sihui_liu@apple.com65a25812020-06-26 16:41:15 +0000620
rniwa@webkit.orge44b91352020-03-04 05:22:14 +0000621 controller->flushPendingItemsForCallback();
rniwa@webkit.orgc46d21d2019-12-16 22:36:55 +0000622 });
623}
624
rniwa@webkit.org640b0732020-03-08 03:52:06 +0000625void TextManipulationController::addItem(ManipulationItemData&& itemData)
rniwa@webkit.orge9da7a82019-10-25 00:08:13 +0000626{
rniwa@webkit.orge44b91352020-03-04 05:22:14 +0000627 const unsigned itemCallbackBatchingSize = 128;
628
rniwa@webkit.orge9da7a82019-10-25 00:08:13 +0000629 ASSERT(m_document);
sihui_liu@apple.com12ab7a72020-06-01 23:54:34 +0000630 ASSERT(!itemData.tokens.isEmpty());
rniwa@webkit.orge44b91352020-03-04 05:22:14 +0000631 auto newID = m_itemIdentifier.generate();
632 m_pendingItemsForCallback.append(ManipulationItem {
633 newID,
rniwa@webkit.org640b0732020-03-08 03:52:06 +0000634 itemData.tokens.map([](auto& token) { return token; })
rniwa@webkit.orge44b91352020-03-04 05:22:14 +0000635 });
rniwa@webkit.org640b0732020-03-08 03:52:06 +0000636 m_items.add(newID, WTFMove(itemData));
rniwa@webkit.orge44b91352020-03-04 05:22:14 +0000637
638 if (m_pendingItemsForCallback.size() >= itemCallbackBatchingSize)
639 flushPendingItemsForCallback();
rniwa@webkit.orge9da7a82019-10-25 00:08:13 +0000640}
641
rniwa@webkit.orge44b91352020-03-04 05:22:14 +0000642void TextManipulationController::flushPendingItemsForCallback()
rniwa@webkit.orge9da7a82019-10-25 00:08:13 +0000643{
ggaren@apple.com991cea12020-12-09 00:03:34 +0000644 if (m_pendingItemsForCallback.isEmpty())
645 return;
646
rniwa@webkit.orge44b91352020-03-04 05:22:14 +0000647 m_callback(*m_document, m_pendingItemsForCallback);
648 m_pendingItemsForCallback.clear();
649}
rniwa@webkit.orge9da7a82019-10-25 00:08:13 +0000650
rniwa@webkit.orge44b91352020-03-04 05:22:14 +0000651auto TextManipulationController::completeManipulation(const Vector<WebCore::TextManipulationController::ManipulationItem>& completionItems) -> Vector<ManipulationFailure>
652{
653 Vector<ManipulationFailure> failures;
wenson_hsieh@apple.com3cfd1f12020-07-17 19:58:35 +0000654 HashSet<Ref<Node>> containersWithoutVisualOverflowBeforeReplacement;
rniwa@webkit.orge44b91352020-03-04 05:22:14 +0000655 for (unsigned i = 0; i < completionItems.size(); ++i) {
656 auto& itemToComplete = completionItems[i];
657 auto identifier = itemToComplete.identifier;
658 if (!identifier) {
659 failures.append(ManipulationFailure { identifier, i, ManipulationFailureType::InvalidItem });
660 continue;
661 }
rniwa@webkit.orge9da7a82019-10-25 00:08:13 +0000662
rniwa@webkit.orge44b91352020-03-04 05:22:14 +0000663 auto itemDataIterator = m_items.find(identifier);
664 if (itemDataIterator == m_items.end()) {
665 failures.append(ManipulationFailure { identifier, i, ManipulationFailureType::InvalidItem });
666 continue;
667 }
rniwa@webkit.orge9da7a82019-10-25 00:08:13 +0000668
rniwa@webkit.orge44b91352020-03-04 05:22:14 +0000669 ManipulationItemData itemData;
670 std::exchange(itemData, itemDataIterator->value);
671 m_items.remove(itemDataIterator);
672
wenson_hsieh@apple.com3cfd1f12020-07-17 19:58:35 +0000673 auto failureOrNullopt = replace(itemData, itemToComplete.tokens, containersWithoutVisualOverflowBeforeReplacement);
rniwa@webkit.orge44b91352020-03-04 05:22:14 +0000674 if (failureOrNullopt)
675 failures.append(ManipulationFailure { identifier, i, *failureOrNullopt });
676 }
wenson_hsieh@apple.com3cfd1f12020-07-17 19:58:35 +0000677
678 if (!containersWithoutVisualOverflowBeforeReplacement.isEmpty()) {
679 if (m_document)
680 m_document->updateLayoutIgnorePendingStylesheets();
681
682 for (auto& container : containersWithoutVisualOverflowBeforeReplacement) {
683 if (!is<StyledElement>(container))
684 continue;
685
686 auto& element = downcast<StyledElement>(container.get());
687 auto* box = element.renderBox();
688 if (!box || !box->hasVisualOverflow())
689 continue;
690
wenson_hsieh@apple.com7cf60fb2020-07-17 23:43:12 +0000691 auto& style = box->style();
692 if (style.width().isFixed() && style.height().isFixed() && !style.hasOutOfFlowPosition() && !style.hasClip()) {
693 element.setInlineStyleProperty(CSSPropertyOverflowX, CSSValueHidden);
694 element.setInlineStyleProperty(CSSPropertyOverflowY, CSSValueAuto);
695 }
wenson_hsieh@apple.com3cfd1f12020-07-17 19:58:35 +0000696 }
697 }
698
rniwa@webkit.orge44b91352020-03-04 05:22:14 +0000699 return failures;
rniwa@webkit.orge9da7a82019-10-25 00:08:13 +0000700}
701
rniwa@webkit.org900f5602019-12-21 02:09:50 +0000702struct TokenExchangeData {
703 RefPtr<Node> node;
704 String originalContent;
705 bool isExcluded { false };
706 bool isConsumed { false };
707};
708
709struct ReplacementData {
710 Ref<Node> originalNode;
rniwa@webkit.org7afdb0b2019-10-25 20:41:15 +0000711 String newData;
712};
713
sihui_liu@apple.com12ab7a72020-06-01 23:54:34 +0000714Vector<Ref<Node>> TextManipulationController::getPath(Node* ancestor, Node* node)
715{
716 Vector<Ref<Node>> path;
717 RefPtr<ContainerNode> containerNode = is<ContainerNode>(*node) ? &downcast<ContainerNode>(*node) : node->parentNode();
718 for (; containerNode && containerNode != ancestor; containerNode = containerNode->parentNode())
719 path.append(*containerNode);
720 path.reverse();
721 return path;
722}
723
sihui_liu@apple.com65a25812020-06-26 16:41:15 +0000724void TextManipulationController::updateInsertions(Vector<NodeEntry>& lastTopDownPath, const Vector<Ref<Node>>& currentTopDownPath, Node* currentNode, HashSet<Ref<Node>>& insertedNodes, Vector<NodeInsertion>& insertions)
sihui_liu@apple.com12ab7a72020-06-01 23:54:34 +0000725{
wenson_hsieh@apple.come0dde552020-06-17 01:34:01 +0000726 size_t i = 0;
sihui_liu@apple.com12ab7a72020-06-01 23:54:34 +0000727 while (i < lastTopDownPath.size() && i < currentTopDownPath.size() && lastTopDownPath[i].first.ptr() == currentTopDownPath[i].ptr())
728 ++i;
729
730 if (i != lastTopDownPath.size() || i != currentTopDownPath.size()) {
731 if (i < lastTopDownPath.size())
732 lastTopDownPath.shrink(i);
733
734 for (;i < currentTopDownPath.size(); ++i) {
735 Ref<Node> node = currentTopDownPath[i];
736 if (!insertedNodes.add(node.copyRef()).isNewEntry) {
737 auto clonedNode = node->cloneNodeInternal(node->document(), Node::CloningOperation::OnlySelf);
738 if (auto* data = node->eventTargetData())
739 data->eventListenerMap.copyEventListenersNotCreatedFromMarkupToTarget(clonedNode.ptr());
740 node = WTFMove(clonedNode);
741 }
742 insertions.append(NodeInsertion { lastTopDownPath.size() ? lastTopDownPath.last().second.ptr() : nullptr, node.copyRef() });
743 lastTopDownPath.append({ currentTopDownPath[i].copyRef(), WTFMove(node) });
744 }
745 }
746
747 if (currentNode)
sihui_liu@apple.com65a25812020-06-26 16:41:15 +0000748 insertions.append(NodeInsertion { lastTopDownPath.size() ? lastTopDownPath.last().second.ptr() : nullptr, *currentNode });
sihui_liu@apple.com12ab7a72020-06-01 23:54:34 +0000749}
rniwa@webkit.org900f5602019-12-21 02:09:50 +0000750
darin@apple.coma4ddc782021-05-30 16:11:40 +0000751auto TextManipulationController::replace(const ManipulationItemData& item, const Vector<ManipulationToken>& replacementTokens, HashSet<Ref<Node>>& containersWithoutVisualOverflowBeforeReplacement) -> std::optional<ManipulationFailureType>
rniwa@webkit.orge9da7a82019-10-25 00:08:13 +0000752{
rniwa@webkit.org900f5602019-12-21 02:09:50 +0000753 if (item.start.isOrphan() || item.end.isOrphan())
rniwa@webkit.orge44b91352020-03-04 05:22:14 +0000754 return ManipulationFailureType::ContentChanged;
rniwa@webkit.org900f5602019-12-21 02:09:50 +0000755
darin@apple.comd2f1a002020-03-16 23:02:10 +0000756 if (item.start.isNull() || item.end.isNull()) {
rniwa@webkit.org640b0732020-03-08 03:52:06 +0000757 RELEASE_ASSERT(item.tokens.size() == 1);
cdumez@apple.come1840be2021-09-20 21:45:40 +0000758 RefPtr element = { item.element.get() };
rniwa@webkit.org640b0732020-03-08 03:52:06 +0000759 if (!element)
760 return ManipulationFailureType::ContentChanged;
sihui_liu@apple.com33fbdee2020-06-03 19:04:28 +0000761 if (replacementTokens.size() > 1 && !canPerformTextManipulationByReplacingEntireTextContent(*element) && item.attributeName == nullQName())
rniwa@webkit.org640b0732020-03-08 03:52:06 +0000762 return ManipulationFailureType::InvalidToken;
wenson_hsieh@apple.com2d16b752020-04-20 20:19:54 +0000763 auto expectedTokenIdentifier = item.tokens[0].identifier;
764 StringBuilder newValue;
765 for (size_t i = 0; i < replacementTokens.size(); ++i) {
766 if (replacementTokens[i].identifier != expectedTokenIdentifier)
rniwa@webkit.org640b0732020-03-08 03:52:06 +0000767 return ManipulationFailureType::InvalidToken;
wenson_hsieh@apple.com2d16b752020-04-20 20:19:54 +0000768 if (i)
769 newValue.append(' ');
770 newValue.append(replacementTokens[i].content);
rniwa@webkit.org640b0732020-03-08 03:52:06 +0000771 }
772 if (item.attributeName == nullQName())
wenson_hsieh@apple.com2d16b752020-04-20 20:19:54 +0000773 element->setTextContent(newValue.toString());
aperez@igalia.comcb5f46712020-06-05 15:47:41 +0000774 else if (item.attributeName == HTMLNames::valueAttr && is<HTMLInputElement>(*element))
wenson_hsieh@apple.comf0ad7db2020-06-03 21:08:21 +0000775 downcast<HTMLInputElement>(*element).setValue(newValue.toString());
rniwa@webkit.org640b0732020-03-08 03:52:06 +0000776 else
cdumez@apple.comb5c30b72022-05-02 00:17:50 +0000777 element->setAttribute(item.attributeName, newValue.toAtomString());
sihui_liu@apple.com1f7fe992022-03-04 01:32:11 +0000778
779 m_manipulatedNodes.add(*element);
darin@apple.com7c840b62021-05-28 01:26:23 +0000780 return std::nullopt;
rniwa@webkit.org640b0732020-03-08 03:52:06 +0000781 }
782
sihui_liu@apple.com12ab7a72020-06-01 23:54:34 +0000783 size_t currentTokenIndex = 0;
784 HashMap<TokenIdentifier, TokenExchangeData> tokenExchangeMap;
rniwa@webkit.org900f5602019-12-21 02:09:50 +0000785 RefPtr<Node> commonAncestor;
rniwa@webkit.orgbf66d0a2020-03-12 23:29:31 +0000786 RefPtr<Node> firstContentNode;
sihui_liu@apple.com12ab7a72020-06-01 23:54:34 +0000787 RefPtr<Node> lastChildOfCommonAncestorInRange;
rniwa@webkit.orgbf66d0a2020-03-12 23:29:31 +0000788 HashSet<Ref<Node>> nodesToRemove;
antti@apple.com32faafd2021-09-28 05:24:42 +0000789
790 for (ParagraphContentIterator iterator { item.start, item.end }; !iterator.atEnd(); iterator.advance()) {
rniwa@webkit.orgcffb7892020-03-07 01:14:23 +0000791 auto content = iterator.currentContent();
sihui_liu@apple.com12ab7a72020-06-01 23:54:34 +0000792 ASSERT(content.node);
793
wenson_hsieh@apple.comd565ed42022-05-11 18:35:14 +0000794 bool isReplacedOrTextContent = content.isReplacedContent || content.isTextContent;
795 if (!isReplacedOrTextContent && is<ContainerNode>(*content.node) && !content.node->hasChildNodes() && content.text.isEmpty())
796 continue;
797
sihui_liu@apple.com12ab7a72020-06-01 23:54:34 +0000798 lastChildOfCommonAncestorInRange = content.node;
799 nodesToRemove.add(*content.node);
rniwa@webkit.orgcffb7892020-03-07 01:14:23 +0000800
wenson_hsieh@apple.comd565ed42022-05-11 18:35:14 +0000801 if (!isReplacedOrTextContent)
rniwa@webkit.orgcffb7892020-03-07 01:14:23 +0000802 continue;
803
sihui_liu@apple.com12ab7a72020-06-01 23:54:34 +0000804 Vector<ManipulationToken> tokensInCurrentNode;
sihui_liu@apple.com5f91c3b2020-06-04 23:58:32 +0000805 if (content.isReplacedContent) {
806 if (currentTokenIndex >= item.tokens.size())
807 return ManipulationFailureType::ContentChanged;
808
sihui_liu@apple.com12ab7a72020-06-01 23:54:34 +0000809 tokensInCurrentNode.append(item.tokens[currentTokenIndex]);
sihui_liu@apple.com5f91c3b2020-06-04 23:58:32 +0000810 } else
sihui_liu@apple.com0e39ec02020-06-09 06:54:20 +0000811 tokensInCurrentNode = createUnit(content.text, *content.node).tokens;
rniwa@webkit.orgcffb7892020-03-07 01:14:23 +0000812
sihui_liu@apple.com12ab7a72020-06-01 23:54:34 +0000813 bool isNodeIncluded = WTF::anyOf(tokensInCurrentNode, [] (auto& token) {
814 return !token.isExcluded;
815 });
816 for (auto& token : tokensInCurrentNode) {
sihui_liu@apple.com5f91c3b2020-06-04 23:58:32 +0000817 if (currentTokenIndex >= item.tokens.size())
sihui_liu@apple.com5a2c1d32020-04-23 17:23:54 +0000818 return ManipulationFailureType::ContentChanged;
sihui_liu@apple.com12ab7a72020-06-01 23:54:34 +0000819
820 auto& currentToken = item.tokens[currentTokenIndex++];
sihui_liu@apple.com1ef9b232021-03-11 17:31:32 +0000821 bool isContentUnchanged = areEqualIgnoringLeadingAndTrailingWhitespaces(currentToken.content, token.content);
sihui_liu@apple.comd8cc9942020-08-07 01:24:05 +0000822 if (!content.isReplacedContent && !isContentUnchanged)
sihui_liu@apple.com12ab7a72020-06-01 23:54:34 +0000823 return ManipulationFailureType::ContentChanged;
824
825 tokenExchangeMap.set(currentToken.identifier, TokenExchangeData { content.node.copyRef(), currentToken.content, !isNodeIncluded });
sihui_liu@apple.com5a2c1d32020-04-23 17:23:54 +0000826 }
827
rniwa@webkit.orgbf66d0a2020-03-12 23:29:31 +0000828 if (!firstContentNode)
829 firstContentNode = content.node;
rniwa@webkit.org900f5602019-12-21 02:09:50 +0000830
sihui_liu@apple.com12ab7a72020-06-01 23:54:34 +0000831 auto parentNode = content.node->parentNode();
832 if (!commonAncestor)
833 commonAncestor = parentNode;
834 else if (!parentNode->isDescendantOf(commonAncestor.get())) {
darin@apple.comdc888502020-11-05 17:36:29 +0000835 commonAncestor = commonInclusiveAncestor<ComposedTree>(*commonAncestor, *parentNode);
sihui_liu@apple.com12ab7a72020-06-01 23:54:34 +0000836 ASSERT(commonAncestor);
rniwa@webkit.org900f5602019-12-21 02:09:50 +0000837 }
rniwa@webkit.orge9da7a82019-10-25 00:08:13 +0000838 }
839
sihui_liu@apple.com083fe7e2020-09-22 22:21:43 +0000840 if (!firstContentNode)
841 return ManipulationFailureType::ContentChanged;
842
sihui_liu@apple.com12ab7a72020-06-01 23:54:34 +0000843 while (lastChildOfCommonAncestorInRange && lastChildOfCommonAncestorInRange->parentNode() != commonAncestor)
844 lastChildOfCommonAncestorInRange = lastChildOfCommonAncestorInRange->parentNode();
845
rniwa@webkit.orgbf66d0a2020-03-12 23:29:31 +0000846 for (auto node = commonAncestor; node; node = node->parentNode())
847 nodesToRemove.remove(*node);
rniwa@webkit.orge9da7a82019-10-25 00:08:13 +0000848
rniwa@webkit.org900f5602019-12-21 02:09:50 +0000849 HashSet<Ref<Node>> reusedOriginalNodes;
850 Vector<NodeInsertion> insertions;
sihui_liu@apple.com14513ed2020-08-24 18:06:01 +0000851 auto startTopDownPath = getPath(commonAncestor.get(), firstContentNode.get());
852 while (!startTopDownPath.isEmpty()) {
853 auto lastNode = startTopDownPath.last();
drousso@apple.comb706fa02021-09-24 18:04:35 +0000854 ASSERT(is<ContainerNode>(lastNode));
sihui_liu@apple.com14513ed2020-08-24 18:06:01 +0000855 if (!downcast<ContainerNode>(lastNode.get()).hasOneChild())
856 break;
857 nodesToRemove.add(startTopDownPath.takeLast());
858 }
859 auto lastTopDownPath = startTopDownPath.map([&](auto node) -> NodeEntry {
860 reusedOriginalNodes.add(node.copyRef());
861 return { node, node };
862 });
863
sihui_liu@apple.com12ab7a72020-06-01 23:54:34 +0000864 for (size_t index = 0; index < replacementTokens.size(); ++index) {
865 auto& replacementToken = replacementTokens[index];
866 auto it = tokenExchangeMap.find(replacementToken.identifier);
rniwa@webkit.org900f5602019-12-21 02:09:50 +0000867 if (it == tokenExchangeMap.end())
rniwa@webkit.orge44b91352020-03-04 05:22:14 +0000868 return ManipulationFailureType::InvalidToken;
rniwa@webkit.org900f5602019-12-21 02:09:50 +0000869
870 auto& exchangeData = it->value;
sihui_liu@apple.com12ab7a72020-06-01 23:54:34 +0000871 auto* originalNode = exchangeData.node.get();
872 ASSERT(originalNode);
873 auto replacementText = replacementToken.content;
rniwa@webkit.org900f5602019-12-21 02:09:50 +0000874
sihui_liu@apple.com12ab7a72020-06-01 23:54:34 +0000875 RefPtr<Node> replacementNode;
rniwa@webkit.org900f5602019-12-21 02:09:50 +0000876 if (exchangeData.isExcluded) {
877 if (exchangeData.isConsumed)
rniwa@webkit.orge44b91352020-03-04 05:22:14 +0000878 return ManipulationFailureType::ExclusionViolation;
rniwa@webkit.org900f5602019-12-21 02:09:50 +0000879 exchangeData.isConsumed = true;
sihui_liu@apple.com12ab7a72020-06-01 23:54:34 +0000880
881 if (!replacementToken.content.isNull() && replacementToken.content != exchangeData.originalContent)
rniwa@webkit.orge44b91352020-03-04 05:22:14 +0000882 return ManipulationFailureType::ExclusionViolation;
sihui_liu@apple.com12ab7a72020-06-01 23:54:34 +0000883
884 replacementNode = originalNode;
885 for (RefPtr<Node> descendentNode = NodeTraversal::next(*originalNode, originalNode); descendentNode; descendentNode = NodeTraversal::next(*descendentNode, originalNode))
886 nodesToRemove.remove(*descendentNode);
rniwa@webkit.org900f5602019-12-21 02:09:50 +0000887 } else
cdumez@apple.com50fe27c2022-04-20 03:05:47 +0000888 replacementNode = Text::create(commonAncestor->document(), WTFMove(replacementText));
rniwa@webkit.org900f5602019-12-21 02:09:50 +0000889
sihui_liu@apple.com12ab7a72020-06-01 23:54:34 +0000890 auto topDownPath = getPath(commonAncestor.get(), originalNode);
891 updateInsertions(lastTopDownPath, topDownPath, replacementNode.get(), reusedOriginalNodes, insertions);
rniwa@webkit.org900f5602019-12-21 02:09:50 +0000892 }
sihui_liu@apple.com12ab7a72020-06-01 23:54:34 +0000893
sihui_liu@apple.com12ab7a72020-06-01 23:54:34 +0000894 RefPtr<Node> node = item.end.firstNode();
sihui_liu@apple.com46240b42020-08-11 18:14:26 +0000895 if (node && lastChildOfCommonAncestorInRange->contains(node.get())) {
sihui_liu@apple.com12ab7a72020-06-01 23:54:34 +0000896 auto topDownPath = getPath(commonAncestor.get(), node->parentNode());
sihui_liu@apple.com65a25812020-06-26 16:41:15 +0000897 updateInsertions(lastTopDownPath, topDownPath, nullptr, reusedOriginalNodes, insertions);
sihui_liu@apple.com12ab7a72020-06-01 23:54:34 +0000898 }
sihui_liu@apple.com46240b42020-08-11 18:14:26 +0000899 while (lastChildOfCommonAncestorInRange->contains(node.get())) {
sihui_liu@apple.com12ab7a72020-06-01 23:54:34 +0000900 Ref<Node> parentNode = *node->parentNode();
901 while (!lastTopDownPath.isEmpty() && lastTopDownPath.last().first.ptr() != parentNode.ptr())
902 lastTopDownPath.removeLast();
903
sihui_liu@apple.com2016ded2020-06-15 17:51:38 +0000904 insertions.append(NodeInsertion { lastTopDownPath.size() ? lastTopDownPath.last().second.ptr() : nullptr, *node, IsNodeManipulated::No });
sihui_liu@apple.com12ab7a72020-06-01 23:54:34 +0000905 lastTopDownPath.append({ *node, *node });
906 node = NodeTraversal::next(*node);
907 }
rniwa@webkit.org900f5602019-12-21 02:09:50 +0000908
sihui_liu@apple.com46240b42020-08-11 18:14:26 +0000909 RefPtr<Node> insertionPointNode = lastChildOfCommonAncestorInRange->nextSibling();
rniwa@webkit.org900f5602019-12-21 02:09:50 +0000910
911 for (auto& node : nodesToRemove)
912 node->remove();
913
914 for (auto& insertion : insertions) {
wenson_hsieh@apple.com3cfd1f12020-07-17 19:58:35 +0000915 auto parentContainer = insertion.parentIfDifferentFromCommonAncestor;
sihui_liu@apple.com46240b42020-08-11 18:14:26 +0000916 if (!parentContainer) {
917 parentContainer = commonAncestor;
918 parentContainer->insertBefore(insertion.child, insertionPointNode.get());
sihui_liu@apple.com65a25812020-06-26 16:41:15 +0000919 } else
sihui_liu@apple.com46240b42020-08-11 18:14:26 +0000920 parentContainer->appendChild(insertion.child);
wenson_hsieh@apple.come0dde552020-06-17 01:34:01 +0000921
wenson_hsieh@apple.com3cfd1f12020-07-17 19:58:35 +0000922 if (auto* box = parentContainer->renderBox()) {
923 if (!box->hasVisualOverflow())
924 containersWithoutVisualOverflowBeforeReplacement.add(*parentContainer);
925 }
926
sihui_liu@apple.com65a25812020-06-26 16:41:15 +0000927 if (insertion.isChildManipulated == IsNodeManipulated::Yes)
rniwa@webkit.org671571e2021-04-13 04:19:17 +0000928 m_manipulatedNodes.add(insertion.child.get());
rniwa@webkit.org900f5602019-12-21 02:09:50 +0000929 }
rniwa@webkit.org7afdb0b2019-10-25 20:41:15 +0000930
darin@apple.com7c840b62021-05-28 01:26:23 +0000931 return std::nullopt;
rniwa@webkit.orge9da7a82019-10-25 00:08:13 +0000932}
933
rniwa@webkit.org671571e2021-04-13 04:19:17 +0000934void TextManipulationController::removeNode(Node& node)
sihui_liu@apple.com65a25812020-06-26 16:41:15 +0000935{
936 m_manipulatedNodes.remove(node);
commit-queue@webkit.org6d253ad2020-08-05 15:46:59 +0000937 m_textNodesWithNewRenderer.remove(node);
sihui_liu@apple.com65a25812020-06-26 16:41:15 +0000938}
939
rniwa@webkit.orge9da7a82019-10-25 00:08:13 +0000940} // namespace WebCore