rniwa@webkit.org | e9da7a8 | 2019-10-25 00:08:13 +0000 | [diff] [blame] | 1 | /* |
| 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 | |
rniwa@webkit.org | 7afdb0b | 2019-10-25 20:41:15 +0000 | [diff] [blame] | 29 | #include "CharacterData.h" |
rniwa@webkit.org | e9da7a8 | 2019-10-25 00:08:13 +0000 | [diff] [blame] | 30 | #include "Editing.h" |
rniwa@webkit.org | 7afdb0b | 2019-10-25 20:41:15 +0000 | [diff] [blame] | 31 | #include "ElementAncestorIterator.h" |
rniwa@webkit.org | c46d21d | 2019-12-16 22:36:55 +0000 | [diff] [blame] | 32 | #include "EventLoop.h" |
rniwa@webkit.org | 900f560 | 2019-12-21 02:09:50 +0000 | [diff] [blame] | 33 | #include "NodeTraversal.h" |
| 34 | #include "PseudoElement.h" |
| 35 | #include "Range.h" |
rniwa@webkit.org | e9da7a8 | 2019-10-25 00:08:13 +0000 | [diff] [blame] | 36 | #include "ScriptDisallowedScope.h" |
rniwa@webkit.org | 900f560 | 2019-12-21 02:09:50 +0000 | [diff] [blame] | 37 | #include "Text.h" |
rniwa@webkit.org | e9da7a8 | 2019-10-25 00:08:13 +0000 | [diff] [blame] | 38 | #include "TextIterator.h" |
| 39 | #include "VisibleUnits.h" |
| 40 | |
| 41 | namespace WebCore { |
| 42 | |
rniwa@webkit.org | 7afdb0b | 2019-10-25 20:41:15 +0000 | [diff] [blame] | 43 | inline bool TextManipulationController::ExclusionRule::match(const Element& element) const |
| 44 | { |
| 45 | return switchOn(rule, [&element] (ElementRule rule) { |
| 46 | return rule.localName == element.localName(); |
| 47 | }, [&element] (AttributeRule rule) { |
| 48 | return equalIgnoringASCIICase(element.getAttribute(rule.name), rule.value); |
commit-queue@webkit.org | d873075 | 2019-12-04 18:36:15 +0000 | [diff] [blame] | 49 | }, [&element] (ClassRule rule) { |
| 50 | return element.hasClass() && element.classNames().contains(rule.className); |
rniwa@webkit.org | 7afdb0b | 2019-10-25 20:41:15 +0000 | [diff] [blame] | 51 | }); |
| 52 | } |
| 53 | |
| 54 | class ExclusionRuleMatcher { |
| 55 | public: |
| 56 | using ExclusionRule = TextManipulationController::ExclusionRule; |
| 57 | using Type = TextManipulationController::ExclusionRule::Type; |
| 58 | |
| 59 | ExclusionRuleMatcher(const Vector<ExclusionRule>& rules) |
| 60 | : m_rules(rules) |
| 61 | { } |
| 62 | |
| 63 | bool isExcluded(Node* node) |
| 64 | { |
| 65 | if (!node) |
| 66 | return false; |
| 67 | |
| 68 | RefPtr<Element> startingElement = is<Element>(*node) ? downcast<Element>(node) : node->parentElement(); |
| 69 | if (!startingElement) |
| 70 | return false; |
| 71 | |
| 72 | Type type = Type::Include; |
| 73 | RefPtr<Element> matchingElement; |
darin@apple.com | 25c0c84 | 2020-02-23 04:56:03 +0000 | [diff] [blame] | 74 | for (auto& element : lineageOfType<Element>(*startingElement)) { |
rniwa@webkit.org | 7afdb0b | 2019-10-25 20:41:15 +0000 | [diff] [blame] | 75 | if (auto typeOrNullopt = typeForElement(element)) { |
| 76 | type = *typeOrNullopt; |
| 77 | matchingElement = &element; |
| 78 | break; |
| 79 | } |
| 80 | } |
| 81 | |
darin@apple.com | 25c0c84 | 2020-02-23 04:56:03 +0000 | [diff] [blame] | 82 | for (auto& element : lineageOfType<Element>(*startingElement)) { |
rniwa@webkit.org | 7afdb0b | 2019-10-25 20:41:15 +0000 | [diff] [blame] | 83 | m_cache.set(element, type); |
| 84 | if (&element == matchingElement) |
| 85 | break; |
| 86 | } |
| 87 | |
| 88 | return type == Type::Exclude; |
| 89 | } |
| 90 | |
| 91 | Optional<Type> typeForElement(Element& element) |
| 92 | { |
| 93 | auto it = m_cache.find(element); |
| 94 | if (it != m_cache.end()) |
| 95 | return it->value; |
| 96 | |
| 97 | for (auto& rule : m_rules) { |
| 98 | if (rule.match(element)) |
| 99 | return rule.type; |
| 100 | } |
| 101 | |
| 102 | return WTF::nullopt; |
| 103 | } |
| 104 | |
| 105 | private: |
| 106 | const Vector<ExclusionRule>& m_rules; |
| 107 | HashMap<Ref<Element>, ExclusionRule::Type> m_cache; |
| 108 | }; |
| 109 | |
rniwa@webkit.org | e9da7a8 | 2019-10-25 00:08:13 +0000 | [diff] [blame] | 110 | TextManipulationController::TextManipulationController(Document& document) |
| 111 | : m_document(makeWeakPtr(document)) |
| 112 | { |
| 113 | } |
| 114 | |
rniwa@webkit.org | 9538698 | 2020-02-28 03:37:22 +0000 | [diff] [blame] | 115 | bool TextManipulationController::isInManipulatedElement(Element& element) |
| 116 | { |
| 117 | if (!m_manipulatedElements.capacity()) |
| 118 | return false; // Fast path for startObservingParagraphs. |
| 119 | for (auto& ancestorOrSelf : lineageOfType<Element>(element)) { |
| 120 | if (m_manipulatedElements.contains(ancestorOrSelf)) |
| 121 | return true; |
| 122 | } |
| 123 | return false; |
| 124 | } |
| 125 | |
rniwa@webkit.org | 7afdb0b | 2019-10-25 20:41:15 +0000 | [diff] [blame] | 126 | void TextManipulationController::startObservingParagraphs(ManipulationItemCallback&& callback, Vector<ExclusionRule>&& exclusionRules) |
rniwa@webkit.org | e9da7a8 | 2019-10-25 00:08:13 +0000 | [diff] [blame] | 127 | { |
| 128 | auto document = makeRefPtr(m_document.get()); |
| 129 | if (!document) |
| 130 | return; |
| 131 | |
| 132 | m_callback = WTFMove(callback); |
rniwa@webkit.org | 7afdb0b | 2019-10-25 20:41:15 +0000 | [diff] [blame] | 133 | m_exclusionRules = WTFMove(exclusionRules); |
rniwa@webkit.org | e9da7a8 | 2019-10-25 00:08:13 +0000 | [diff] [blame] | 134 | |
| 135 | VisiblePosition start = firstPositionInNode(m_document.get()); |
| 136 | VisiblePosition end = lastPositionInNode(m_document.get()); |
rniwa@webkit.org | c46d21d | 2019-12-16 22:36:55 +0000 | [diff] [blame] | 137 | |
| 138 | observeParagraphs(start, end); |
rniwa@webkit.org | e44b9135 | 2020-03-04 05:22:14 +0000 | [diff] [blame] | 139 | flushPendingItemsForCallback(); |
rniwa@webkit.org | c46d21d | 2019-12-16 22:36:55 +0000 | [diff] [blame] | 140 | } |
| 141 | |
rniwa@webkit.org | cffb789 | 2020-03-07 01:14:23 +0000 | [diff] [blame] | 142 | class ParagraphContentIterator { |
| 143 | public: |
| 144 | ParagraphContentIterator(const Position& start, const Position& end) |
| 145 | : m_iterator(start, end) |
| 146 | , m_iteratorNode(m_iterator.atEnd() ? nullptr : m_iterator.range()->firstNode()) |
| 147 | , m_currentNodeForFindingInvisibleContent(start.firstNode()) |
| 148 | , m_pastEndNode(end.firstNode()) |
| 149 | { |
| 150 | } |
| 151 | |
| 152 | void advance() |
| 153 | { |
| 154 | // FIXME: Add a node callback to TextIterator instead of traversing the node tree twice like this. |
| 155 | if (m_currentNodeForFindingInvisibleContent != m_iteratorNode && m_currentNodeForFindingInvisibleContent != m_pastEndNode) { |
| 156 | moveCurrentNodeForward(); |
| 157 | return; |
| 158 | } |
| 159 | |
| 160 | if (m_iterator.atEnd()) |
| 161 | return; |
| 162 | |
| 163 | auto previousIteratorNode = m_iteratorNode; |
| 164 | |
| 165 | m_iterator.advance(); |
| 166 | m_iteratorNode = m_iterator.atEnd() ? nullptr : m_iterator.range()->firstNode(); |
| 167 | if (previousIteratorNode != m_iteratorNode) |
| 168 | moveCurrentNodeForward(); |
| 169 | } |
| 170 | |
| 171 | struct CurrentContent { |
| 172 | RefPtr<Node> node; |
| 173 | StringView text; |
| 174 | bool isTextContent { false }; |
| 175 | bool isReplacedContent { false }; |
| 176 | }; |
| 177 | |
| 178 | CurrentContent currentContent() |
| 179 | { |
| 180 | CurrentContent content; |
| 181 | if (m_currentNodeForFindingInvisibleContent && m_currentNodeForFindingInvisibleContent != m_iteratorNode) |
| 182 | content = { m_currentNodeForFindingInvisibleContent.copyRef(), StringView { } }; |
| 183 | else |
| 184 | content = { m_iterator.node(), m_iterator.text(), true }; |
| 185 | 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 | |
| 196 | Position startPosition() |
| 197 | { |
| 198 | return m_iterator.range()->startPosition(); |
| 199 | } |
| 200 | |
| 201 | Position endPosition() |
| 202 | { |
| 203 | return m_iterator.range()->endPosition(); |
| 204 | } |
| 205 | |
| 206 | bool atEnd() const { return m_iterator.atEnd() && m_currentNodeForFindingInvisibleContent == m_pastEndNode; } |
| 207 | |
| 208 | private: |
| 209 | void moveCurrentNodeForward() |
| 210 | { |
| 211 | m_currentNodeForFindingInvisibleContent = NodeTraversal::next(*m_currentNodeForFindingInvisibleContent); |
| 212 | if (!m_currentNodeForFindingInvisibleContent) |
| 213 | m_currentNodeForFindingInvisibleContent = m_pastEndNode; |
| 214 | } |
| 215 | |
| 216 | TextIterator m_iterator; |
| 217 | RefPtr<Node> m_iteratorNode; |
| 218 | RefPtr<Node> m_currentNodeForFindingInvisibleContent; |
| 219 | RefPtr<Node> m_pastEndNode; |
| 220 | }; |
| 221 | |
rniwa@webkit.org | c46d21d | 2019-12-16 22:36:55 +0000 | [diff] [blame] | 222 | void TextManipulationController::observeParagraphs(VisiblePosition& start, VisiblePosition& end) |
| 223 | { |
| 224 | auto document = makeRefPtr(start.deepEquivalent().document()); |
| 225 | ASSERT(document); |
rniwa@webkit.org | cffb789 | 2020-03-07 01:14:23 +0000 | [diff] [blame] | 226 | ParagraphContentIterator iterator { start.deepEquivalent(), end.deepEquivalent() }; |
rniwa@webkit.org | c46d21d | 2019-12-16 22:36:55 +0000 | [diff] [blame] | 227 | if (document != start.deepEquivalent().document() || document != end.deepEquivalent().document()) |
| 228 | return; // TextIterator's constructor may have updated the layout and executed arbitrary scripts. |
rniwa@webkit.org | e9da7a8 | 2019-10-25 00:08:13 +0000 | [diff] [blame] | 229 | |
rniwa@webkit.org | 7afdb0b | 2019-10-25 20:41:15 +0000 | [diff] [blame] | 230 | ExclusionRuleMatcher exclusionRuleMatcher(m_exclusionRules); |
rniwa@webkit.org | e9da7a8 | 2019-10-25 00:08:13 +0000 | [diff] [blame] | 231 | Vector<ManipulationToken> tokensInCurrentParagraph; |
| 232 | Position startOfCurrentParagraph = start.deepEquivalent(); |
rniwa@webkit.org | cffb789 | 2020-03-07 01:14:23 +0000 | [diff] [blame] | 233 | for (; !iterator.atEnd(); iterator.advance()) { |
| 234 | auto content = iterator.currentContent(); |
| 235 | if (content.node) { |
| 236 | if (RefPtr<Element> currentElementAncestor = is<Element>(*content.node) ? downcast<Element>(content.node.get()) : content.node->parentOrShadowHostElement()) { |
rniwa@webkit.org | 9538698 | 2020-02-28 03:37:22 +0000 | [diff] [blame] | 237 | if (isInManipulatedElement(*currentElementAncestor)) |
| 238 | return; // We can exit early here because scheduleObservartionUpdate calls this function on each paragraph separately. |
| 239 | } |
rniwa@webkit.org | cffb789 | 2020-03-07 01:14:23 +0000 | [diff] [blame] | 240 | |
| 241 | if (startOfCurrentParagraph.isNull()) |
| 242 | startOfCurrentParagraph = iterator.startPosition(); |
rniwa@webkit.org | 9538698 | 2020-02-28 03:37:22 +0000 | [diff] [blame] | 243 | } |
| 244 | |
rniwa@webkit.org | cffb789 | 2020-03-07 01:14:23 +0000 | [diff] [blame] | 245 | if (content.isReplacedContent) { |
| 246 | tokensInCurrentParagraph.append(ManipulationToken { m_tokenIdentifier.generate(), "[]", true /* isExcluded */}); |
| 247 | continue; |
| 248 | } |
| 249 | |
| 250 | if (!content.isTextContent) |
| 251 | continue; |
| 252 | |
| 253 | size_t startOfCurrentLine = 0; |
rniwa@webkit.org | e9da7a8 | 2019-10-25 00:08:13 +0000 | [diff] [blame] | 254 | size_t offsetOfNextNewLine = 0; |
rniwa@webkit.org | cffb789 | 2020-03-07 01:14:23 +0000 | [diff] [blame] | 255 | StringView currentText = content.text; |
| 256 | while ((offsetOfNextNewLine = currentText.find('\n', startOfCurrentLine)) != notFound) { |
| 257 | if (startOfCurrentLine < offsetOfNextNewLine) { |
| 258 | auto stringUntilEndOfLine = currentText.substring(startOfCurrentLine, offsetOfNextNewLine - startOfCurrentLine).toString(); |
| 259 | tokensInCurrentParagraph.append(ManipulationToken { m_tokenIdentifier.generate(), stringUntilEndOfLine, exclusionRuleMatcher.isExcluded(content.node.get()) }); |
rniwa@webkit.org | e9da7a8 | 2019-10-25 00:08:13 +0000 | [diff] [blame] | 260 | } |
| 261 | |
rniwa@webkit.org | cffb789 | 2020-03-07 01:14:23 +0000 | [diff] [blame] | 262 | if (!tokensInCurrentParagraph.isEmpty()) { |
| 263 | Position endOfCurrentParagraph = iterator.endPosition(); |
| 264 | if (is<Text>(content.node)) { |
| 265 | auto& textNode = downcast<Text>(*content.node); |
| 266 | endOfCurrentParagraph = Position(&textNode, offsetOfNextNewLine); |
| 267 | startOfCurrentParagraph = Position(&textNode, offsetOfNextNewLine + 1); |
| 268 | } |
rniwa@webkit.org | e9da7a8 | 2019-10-25 00:08:13 +0000 | [diff] [blame] | 269 | addItem(startOfCurrentParagraph, endOfCurrentParagraph, WTFMove(tokensInCurrentParagraph)); |
rniwa@webkit.org | cffb789 | 2020-03-07 01:14:23 +0000 | [diff] [blame] | 270 | startOfCurrentParagraph.clear(); |
| 271 | } |
| 272 | startOfCurrentLine = offsetOfNextNewLine + 1; |
rniwa@webkit.org | e9da7a8 | 2019-10-25 00:08:13 +0000 | [diff] [blame] | 273 | } |
| 274 | |
rniwa@webkit.org | cffb789 | 2020-03-07 01:14:23 +0000 | [diff] [blame] | 275 | auto remainingText = currentText.substring(startOfCurrentLine); |
rniwa@webkit.org | e9da7a8 | 2019-10-25 00:08:13 +0000 | [diff] [blame] | 276 | if (remainingText.length()) |
rniwa@webkit.org | cffb789 | 2020-03-07 01:14:23 +0000 | [diff] [blame] | 277 | tokensInCurrentParagraph.append(ManipulationToken { m_tokenIdentifier.generate(), remainingText.toString(), exclusionRuleMatcher.isExcluded(content.node.get()) }); |
rniwa@webkit.org | e9da7a8 | 2019-10-25 00:08:13 +0000 | [diff] [blame] | 278 | } |
| 279 | |
| 280 | if (!tokensInCurrentParagraph.isEmpty()) |
| 281 | addItem(startOfCurrentParagraph, end.deepEquivalent(), WTFMove(tokensInCurrentParagraph)); |
| 282 | } |
| 283 | |
rniwa@webkit.org | c46d21d | 2019-12-16 22:36:55 +0000 | [diff] [blame] | 284 | void TextManipulationController::didCreateRendererForElement(Element& element) |
| 285 | { |
rniwa@webkit.org | 9538698 | 2020-02-28 03:37:22 +0000 | [diff] [blame] | 286 | if (isInManipulatedElement(element)) |
wenson_hsieh@apple.com | 54632b7 | 2020-01-10 00:40:14 +0000 | [diff] [blame] | 287 | return; |
| 288 | |
rniwa@webkit.org | 9538698 | 2020-02-28 03:37:22 +0000 | [diff] [blame] | 289 | if (m_elementsWithNewRenderer.computesEmpty()) |
rniwa@webkit.org | c46d21d | 2019-12-16 22:36:55 +0000 | [diff] [blame] | 290 | scheduleObservartionUpdate(); |
rniwa@webkit.org | 900f560 | 2019-12-21 02:09:50 +0000 | [diff] [blame] | 291 | |
| 292 | if (is<PseudoElement>(element)) { |
| 293 | if (auto* host = downcast<PseudoElement>(element).hostElement()) |
rniwa@webkit.org | 9538698 | 2020-02-28 03:37:22 +0000 | [diff] [blame] | 294 | m_elementsWithNewRenderer.add(*host); |
rniwa@webkit.org | 900f560 | 2019-12-21 02:09:50 +0000 | [diff] [blame] | 295 | } else |
rniwa@webkit.org | 9538698 | 2020-02-28 03:37:22 +0000 | [diff] [blame] | 296 | m_elementsWithNewRenderer.add(element); |
rniwa@webkit.org | c46d21d | 2019-12-16 22:36:55 +0000 | [diff] [blame] | 297 | } |
| 298 | |
| 299 | using PositionTuple = std::tuple<RefPtr<Node>, unsigned, unsigned>; |
| 300 | static const PositionTuple makePositionTuple(const Position& position) |
| 301 | { |
| 302 | return { position.anchorNode(), static_cast<unsigned>(position.anchorType()), position.anchorType() == Position::PositionIsOffsetInAnchor ? position.offsetInContainerNode() : 0 }; |
| 303 | } |
| 304 | |
| 305 | static const std::pair<PositionTuple, PositionTuple> makeHashablePositionRange(const VisiblePosition& start, const VisiblePosition& end) |
| 306 | { |
| 307 | return { makePositionTuple(start.deepEquivalent()), makePositionTuple(end.deepEquivalent()) }; |
| 308 | } |
| 309 | |
| 310 | void TextManipulationController::scheduleObservartionUpdate() |
| 311 | { |
| 312 | if (!m_document) |
| 313 | return; |
| 314 | |
| 315 | m_document->eventLoop().queueTask(TaskSource::InternalAsyncTask, [weakThis = makeWeakPtr(*this)] { |
| 316 | auto* controller = weakThis.get(); |
| 317 | if (!controller) |
| 318 | return; |
| 319 | |
| 320 | HashSet<Ref<Element>> mutatedElements; |
rniwa@webkit.org | 9538698 | 2020-02-28 03:37:22 +0000 | [diff] [blame] | 321 | for (auto& weakElement : controller->m_elementsWithNewRenderer) |
rniwa@webkit.org | c46d21d | 2019-12-16 22:36:55 +0000 | [diff] [blame] | 322 | mutatedElements.add(weakElement); |
rniwa@webkit.org | 9538698 | 2020-02-28 03:37:22 +0000 | [diff] [blame] | 323 | controller->m_elementsWithNewRenderer.clear(); |
rniwa@webkit.org | c46d21d | 2019-12-16 22:36:55 +0000 | [diff] [blame] | 324 | |
| 325 | HashSet<Ref<Element>> filteredElements; |
| 326 | for (auto& element : mutatedElements) { |
| 327 | auto* parentElement = element->parentElement(); |
| 328 | if (!parentElement || !mutatedElements.contains(parentElement)) |
| 329 | filteredElements.add(element.copyRef()); |
| 330 | } |
| 331 | mutatedElements.clear(); |
| 332 | |
| 333 | HashSet<std::pair<PositionTuple, PositionTuple>> paragraphSets; |
| 334 | for (auto& element : filteredElements) { |
| 335 | auto start = startOfParagraph(firstPositionInOrBeforeNode(element.ptr())); |
| 336 | auto end = endOfParagraph(lastPositionInOrAfterNode(element.ptr())); |
| 337 | |
| 338 | auto key = makeHashablePositionRange(start, end); |
| 339 | if (!paragraphSets.add(key).isNewEntry) |
| 340 | continue; |
| 341 | |
| 342 | auto* controller = weakThis.get(); |
| 343 | if (!controller) |
| 344 | return; // Finding the start/end of paragraph may have updated layout & executed arbitrary scripts. |
| 345 | |
| 346 | controller->observeParagraphs(start, end); |
| 347 | } |
rniwa@webkit.org | e44b9135 | 2020-03-04 05:22:14 +0000 | [diff] [blame] | 348 | controller->flushPendingItemsForCallback(); |
rniwa@webkit.org | c46d21d | 2019-12-16 22:36:55 +0000 | [diff] [blame] | 349 | }); |
| 350 | } |
| 351 | |
rniwa@webkit.org | e9da7a8 | 2019-10-25 00:08:13 +0000 | [diff] [blame] | 352 | void TextManipulationController::addItem(const Position& startOfParagraph, const Position& endOfParagraph, Vector<ManipulationToken>&& tokens) |
| 353 | { |
rniwa@webkit.org | e44b9135 | 2020-03-04 05:22:14 +0000 | [diff] [blame] | 354 | const unsigned itemCallbackBatchingSize = 128; |
| 355 | |
rniwa@webkit.org | e9da7a8 | 2019-10-25 00:08:13 +0000 | [diff] [blame] | 356 | ASSERT(m_document); |
rniwa@webkit.org | e44b9135 | 2020-03-04 05:22:14 +0000 | [diff] [blame] | 357 | auto newID = m_itemIdentifier.generate(); |
| 358 | m_pendingItemsForCallback.append(ManipulationItem { |
| 359 | newID, |
| 360 | tokens.map([](auto& token) { return token; }) |
| 361 | }); |
| 362 | m_items.add(newID, ManipulationItemData { startOfParagraph, endOfParagraph, WTFMove(tokens) }); |
| 363 | |
| 364 | if (m_pendingItemsForCallback.size() >= itemCallbackBatchingSize) |
| 365 | flushPendingItemsForCallback(); |
rniwa@webkit.org | e9da7a8 | 2019-10-25 00:08:13 +0000 | [diff] [blame] | 366 | } |
| 367 | |
rniwa@webkit.org | e44b9135 | 2020-03-04 05:22:14 +0000 | [diff] [blame] | 368 | void TextManipulationController::flushPendingItemsForCallback() |
rniwa@webkit.org | e9da7a8 | 2019-10-25 00:08:13 +0000 | [diff] [blame] | 369 | { |
rniwa@webkit.org | e44b9135 | 2020-03-04 05:22:14 +0000 | [diff] [blame] | 370 | m_callback(*m_document, m_pendingItemsForCallback); |
| 371 | m_pendingItemsForCallback.clear(); |
| 372 | } |
rniwa@webkit.org | e9da7a8 | 2019-10-25 00:08:13 +0000 | [diff] [blame] | 373 | |
rniwa@webkit.org | e44b9135 | 2020-03-04 05:22:14 +0000 | [diff] [blame] | 374 | auto TextManipulationController::completeManipulation(const Vector<WebCore::TextManipulationController::ManipulationItem>& completionItems) -> Vector<ManipulationFailure> |
| 375 | { |
| 376 | Vector<ManipulationFailure> failures; |
| 377 | for (unsigned i = 0; i < completionItems.size(); ++i) { |
| 378 | auto& itemToComplete = completionItems[i]; |
| 379 | auto identifier = itemToComplete.identifier; |
| 380 | if (!identifier) { |
| 381 | failures.append(ManipulationFailure { identifier, i, ManipulationFailureType::InvalidItem }); |
| 382 | continue; |
| 383 | } |
rniwa@webkit.org | e9da7a8 | 2019-10-25 00:08:13 +0000 | [diff] [blame] | 384 | |
rniwa@webkit.org | e44b9135 | 2020-03-04 05:22:14 +0000 | [diff] [blame] | 385 | auto itemDataIterator = m_items.find(identifier); |
| 386 | if (itemDataIterator == m_items.end()) { |
| 387 | failures.append(ManipulationFailure { identifier, i, ManipulationFailureType::InvalidItem }); |
| 388 | continue; |
| 389 | } |
rniwa@webkit.org | e9da7a8 | 2019-10-25 00:08:13 +0000 | [diff] [blame] | 390 | |
rniwa@webkit.org | e44b9135 | 2020-03-04 05:22:14 +0000 | [diff] [blame] | 391 | ManipulationItemData itemData; |
| 392 | std::exchange(itemData, itemDataIterator->value); |
| 393 | m_items.remove(itemDataIterator); |
| 394 | |
| 395 | auto failureOrNullopt = replace(itemData, itemToComplete.tokens); |
| 396 | if (failureOrNullopt) |
| 397 | failures.append(ManipulationFailure { identifier, i, *failureOrNullopt }); |
| 398 | } |
| 399 | return failures; |
rniwa@webkit.org | e9da7a8 | 2019-10-25 00:08:13 +0000 | [diff] [blame] | 400 | } |
| 401 | |
rniwa@webkit.org | 900f560 | 2019-12-21 02:09:50 +0000 | [diff] [blame] | 402 | struct TokenExchangeData { |
| 403 | RefPtr<Node> node; |
| 404 | String originalContent; |
| 405 | bool isExcluded { false }; |
| 406 | bool isConsumed { false }; |
| 407 | }; |
| 408 | |
| 409 | struct ReplacementData { |
| 410 | Ref<Node> originalNode; |
rniwa@webkit.org | 7afdb0b | 2019-10-25 20:41:15 +0000 | [diff] [blame] | 411 | String newData; |
| 412 | }; |
| 413 | |
rniwa@webkit.org | 900f560 | 2019-12-21 02:09:50 +0000 | [diff] [blame] | 414 | struct NodeInsertion { |
| 415 | RefPtr<Node> parentIfDifferentFromCommonAncestor; |
| 416 | Ref<Node> child; |
| 417 | }; |
| 418 | |
rniwa@webkit.org | e44b9135 | 2020-03-04 05:22:14 +0000 | [diff] [blame] | 419 | auto TextManipulationController::replace(const ManipulationItemData& item, const Vector<ManipulationToken>& replacementTokens) -> Optional<ManipulationFailureType> |
rniwa@webkit.org | e9da7a8 | 2019-10-25 00:08:13 +0000 | [diff] [blame] | 420 | { |
rniwa@webkit.org | 900f560 | 2019-12-21 02:09:50 +0000 | [diff] [blame] | 421 | if (item.start.isOrphan() || item.end.isOrphan()) |
rniwa@webkit.org | e44b9135 | 2020-03-04 05:22:14 +0000 | [diff] [blame] | 422 | return ManipulationFailureType::ContentChanged; |
rniwa@webkit.org | 900f560 | 2019-12-21 02:09:50 +0000 | [diff] [blame] | 423 | |
rniwa@webkit.org | e9da7a8 | 2019-10-25 00:08:13 +0000 | [diff] [blame] | 424 | size_t currentTokenIndex = 0; |
rniwa@webkit.org | 900f560 | 2019-12-21 02:09:50 +0000 | [diff] [blame] | 425 | HashMap<TokenIdentifier, TokenExchangeData> tokenExchangeMap; |
rniwa@webkit.org | 7afdb0b | 2019-10-25 20:41:15 +0000 | [diff] [blame] | 426 | |
rniwa@webkit.org | 900f560 | 2019-12-21 02:09:50 +0000 | [diff] [blame] | 427 | RefPtr<Node> commonAncestor; |
rniwa@webkit.org | cffb789 | 2020-03-07 01:14:23 +0000 | [diff] [blame] | 428 | ParagraphContentIterator iterator { item.start, item.end }; |
| 429 | HashSet<Ref<Node>> excludedNodes; |
| 430 | for (; !iterator.atEnd(); iterator.advance()) { |
| 431 | auto content = iterator.currentContent(); |
| 432 | |
| 433 | if (!content.isReplacedContent && !content.isTextContent) |
| 434 | continue; |
| 435 | |
rniwa@webkit.org | e9da7a8 | 2019-10-25 00:08:13 +0000 | [diff] [blame] | 436 | if (currentTokenIndex >= item.tokens.size()) |
rniwa@webkit.org | e44b9135 | 2020-03-04 05:22:14 +0000 | [diff] [blame] | 437 | return ManipulationFailureType::ContentChanged; |
rniwa@webkit.org | cffb789 | 2020-03-07 01:14:23 +0000 | [diff] [blame] | 438 | |
rniwa@webkit.org | e9da7a8 | 2019-10-25 00:08:13 +0000 | [diff] [blame] | 439 | auto& currentToken = item.tokens[currentTokenIndex]; |
rniwa@webkit.org | cffb789 | 2020-03-07 01:14:23 +0000 | [diff] [blame] | 440 | if (!content.isReplacedContent && content.text != currentToken.content) |
rniwa@webkit.org | e44b9135 | 2020-03-04 05:22:14 +0000 | [diff] [blame] | 441 | return ManipulationFailureType::ContentChanged; |
rniwa@webkit.org | 900f560 | 2019-12-21 02:09:50 +0000 | [diff] [blame] | 442 | |
rniwa@webkit.org | cffb789 | 2020-03-07 01:14:23 +0000 | [diff] [blame] | 443 | tokenExchangeMap.set(currentToken.identifier, TokenExchangeData { content.node.copyRef(), currentToken.content, currentToken.isExcluded }); |
| 444 | ++currentTokenIndex; |
rniwa@webkit.org | 900f560 | 2019-12-21 02:09:50 +0000 | [diff] [blame] | 445 | |
rniwa@webkit.org | cffb789 | 2020-03-07 01:14:23 +0000 | [diff] [blame] | 446 | // FIXME: Take care of when currentNode is nullptr. |
| 447 | if (content.node) { |
rniwa@webkit.org | 900f560 | 2019-12-21 02:09:50 +0000 | [diff] [blame] | 448 | if (!commonAncestor) |
rniwa@webkit.org | cffb789 | 2020-03-07 01:14:23 +0000 | [diff] [blame] | 449 | commonAncestor = content.node; |
| 450 | else if (!content.node->isDescendantOf(commonAncestor.get())) { |
| 451 | commonAncestor = Range::commonAncestorContainer(commonAncestor.get(), content.node.get()); |
rniwa@webkit.org | 900f560 | 2019-12-21 02:09:50 +0000 | [diff] [blame] | 452 | ASSERT(commonAncestor); |
| 453 | } |
| 454 | } |
rniwa@webkit.org | e9da7a8 | 2019-10-25 00:08:13 +0000 | [diff] [blame] | 455 | } |
| 456 | |
rniwa@webkit.org | 900f560 | 2019-12-21 02:09:50 +0000 | [diff] [blame] | 457 | RefPtr<Node> nodeAfterStart = item.start.computeNodeAfterPosition(); |
| 458 | if (!nodeAfterStart) |
| 459 | nodeAfterStart = item.start.containerNode(); |
| 460 | |
| 461 | RefPtr<Node> nodeAfterEnd = item.end.computeNodeAfterPosition(); |
| 462 | if (!nodeAfterEnd) |
| 463 | nodeAfterEnd = NodeTraversal::nextSkippingChildren(*item.end.containerNode()); |
| 464 | |
| 465 | HashSet<Ref<Node>> nodesToRemove; |
| 466 | for (RefPtr<Node> currentNode = nodeAfterStart; currentNode && currentNode != nodeAfterEnd; currentNode = NodeTraversal::next(*currentNode)) { |
| 467 | if (commonAncestor == currentNode) |
| 468 | commonAncestor = currentNode->parentNode(); |
| 469 | nodesToRemove.add(*currentNode); |
rniwa@webkit.org | e9da7a8 | 2019-10-25 00:08:13 +0000 | [diff] [blame] | 470 | } |
| 471 | |
rniwa@webkit.org | 900f560 | 2019-12-21 02:09:50 +0000 | [diff] [blame] | 472 | Vector<Ref<Node>> currentElementStack; |
| 473 | HashSet<Ref<Node>> reusedOriginalNodes; |
| 474 | Vector<NodeInsertion> insertions; |
| 475 | for (auto& newToken : replacementTokens) { |
| 476 | auto it = tokenExchangeMap.find(newToken.identifier); |
| 477 | if (it == tokenExchangeMap.end()) |
rniwa@webkit.org | e44b9135 | 2020-03-04 05:22:14 +0000 | [diff] [blame] | 478 | return ManipulationFailureType::InvalidToken; |
rniwa@webkit.org | 900f560 | 2019-12-21 02:09:50 +0000 | [diff] [blame] | 479 | |
| 480 | auto& exchangeData = it->value; |
| 481 | |
| 482 | RefPtr<Node> contentNode; |
| 483 | if (exchangeData.isExcluded) { |
| 484 | if (exchangeData.isConsumed) |
rniwa@webkit.org | e44b9135 | 2020-03-04 05:22:14 +0000 | [diff] [blame] | 485 | return ManipulationFailureType::ExclusionViolation; |
rniwa@webkit.org | 900f560 | 2019-12-21 02:09:50 +0000 | [diff] [blame] | 486 | exchangeData.isConsumed = true; |
| 487 | if (!newToken.content.isNull() && newToken.content != exchangeData.originalContent) |
rniwa@webkit.org | e44b9135 | 2020-03-04 05:22:14 +0000 | [diff] [blame] | 488 | return ManipulationFailureType::ExclusionViolation; |
rniwa@webkit.org | cffb789 | 2020-03-07 01:14:23 +0000 | [diff] [blame] | 489 | contentNode = exchangeData.node; |
| 490 | for (RefPtr<Node> currentDescendentNode = NodeTraversal::next(*contentNode, contentNode.get()); currentDescendentNode; currentDescendentNode = NodeTraversal::next(*currentDescendentNode, contentNode.get())) |
| 491 | nodesToRemove.remove(*currentDescendentNode); |
rniwa@webkit.org | 900f560 | 2019-12-21 02:09:50 +0000 | [diff] [blame] | 492 | } else |
| 493 | contentNode = Text::create(commonAncestor->document(), newToken.content); |
| 494 | |
| 495 | auto& originalNode = exchangeData.node ? *exchangeData.node : *commonAncestor; |
| 496 | RefPtr<ContainerNode> currentNode = is<ContainerNode>(originalNode) ? &downcast<ContainerNode>(originalNode) : originalNode.parentNode(); |
| 497 | |
| 498 | Vector<Ref<Node>> currentAncestors; |
| 499 | for (; currentNode && currentNode != commonAncestor; currentNode = currentNode->parentNode()) |
| 500 | currentAncestors.append(*currentNode); |
| 501 | currentAncestors.reverse(); |
| 502 | |
| 503 | size_t i =0; |
| 504 | while (i < currentElementStack.size() && i < currentAncestors.size() && currentElementStack[i].ptr() == currentAncestors[i].ptr()) |
| 505 | ++i; |
| 506 | |
| 507 | if (i == currentElementStack.size() && i == currentAncestors.size()) |
| 508 | insertions.append(NodeInsertion { currentElementStack.size() ? currentElementStack.last().ptr() : nullptr, contentNode.releaseNonNull() }); |
| 509 | else { |
| 510 | if (i < currentElementStack.size()) |
| 511 | currentElementStack.shrink(i); |
| 512 | for (;i < currentAncestors.size(); ++i) { |
| 513 | Ref<Node> currentNode = currentAncestors[i].copyRef(); |
| 514 | if (!reusedOriginalNodes.add(currentNode.copyRef()).isNewEntry) { |
| 515 | auto clonedNode = currentNode->cloneNodeInternal(currentNode->document(), Node::CloningOperation::OnlySelf); |
| 516 | if (auto* data = currentNode->eventTargetData()) |
| 517 | data->eventListenerMap.copyEventListenersNotCreatedFromMarkupToTarget(clonedNode.ptr()); |
| 518 | currentNode = WTFMove(clonedNode); |
| 519 | } |
| 520 | |
| 521 | insertions.append(NodeInsertion { currentElementStack.size() ? currentElementStack.last().ptr() : nullptr, currentNode.copyRef() }); |
| 522 | currentElementStack.append(WTFMove(currentNode)); |
| 523 | } |
| 524 | insertions.append(NodeInsertion { currentElementStack.size() ? currentElementStack.last().ptr() : nullptr, contentNode.releaseNonNull() }); |
| 525 | } |
| 526 | } |
| 527 | |
| 528 | Position insertionPoint = item.start; |
rniwa@webkit.org | cffb789 | 2020-03-07 01:14:23 +0000 | [diff] [blame] | 529 | if (insertionPoint.anchorNode() != insertionPoint.containerNode()) |
| 530 | insertionPoint = insertionPoint.parentAnchoredEquivalent(); |
rniwa@webkit.org | 900f560 | 2019-12-21 02:09:50 +0000 | [diff] [blame] | 531 | while (insertionPoint.containerNode() != commonAncestor) |
| 532 | insertionPoint = positionInParentBeforeNode(insertionPoint.containerNode()); |
| 533 | ASSERT(!insertionPoint.isNull()); |
rniwa@webkit.org | cffb789 | 2020-03-07 01:14:23 +0000 | [diff] [blame] | 534 | ASSERT(!insertionPoint.isOrphan()); |
rniwa@webkit.org | 900f560 | 2019-12-21 02:09:50 +0000 | [diff] [blame] | 535 | |
| 536 | for (auto& node : nodesToRemove) |
| 537 | node->remove(); |
| 538 | |
| 539 | for (auto& insertion : insertions) { |
rniwa@webkit.org | cffb789 | 2020-03-07 01:14:23 +0000 | [diff] [blame] | 540 | if (!insertion.parentIfDifferentFromCommonAncestor) { |
| 541 | insertionPoint.containerNode()->insertBefore(insertion.child, insertionPoint.computeNodeAfterPosition()); |
| 542 | insertionPoint = positionInParentAfterNode(insertion.child.ptr()); |
| 543 | } else |
rniwa@webkit.org | 900f560 | 2019-12-21 02:09:50 +0000 | [diff] [blame] | 544 | insertion.parentIfDifferentFromCommonAncestor->appendChild(insertion.child); |
wenson_hsieh@apple.com | 54632b7 | 2020-01-10 00:40:14 +0000 | [diff] [blame] | 545 | if (is<Element>(insertion.child.get())) |
rniwa@webkit.org | 9538698 | 2020-02-28 03:37:22 +0000 | [diff] [blame] | 546 | m_manipulatedElements.add(downcast<Element>(insertion.child.get())); |
rniwa@webkit.org | 900f560 | 2019-12-21 02:09:50 +0000 | [diff] [blame] | 547 | } |
rniwa@webkit.org | 7afdb0b | 2019-10-25 20:41:15 +0000 | [diff] [blame] | 548 | |
rniwa@webkit.org | e44b9135 | 2020-03-04 05:22:14 +0000 | [diff] [blame] | 549 | return WTF::nullopt; |
rniwa@webkit.org | e9da7a8 | 2019-10-25 00:08:13 +0000 | [diff] [blame] | 550 | } |
| 551 | |
| 552 | } // namespace WebCore |