REGRESSION(r251409) Service worker connection should not be established without servi...
[WebKit-https.git] / Source / WebCore / editing / TextManipulationController.cpp
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
29 #include "Editing.h"
30 #include "ScriptDisallowedScope.h"
31 #include "TextIterator.h"
32 #include "VisibleUnits.h"
33
34 namespace WebCore {
35
36 TextManipulationController::TextManipulationController(Document& document)
37     : m_document(makeWeakPtr(document))
38 {
39 }
40
41 void TextManipulationController::startObservingParagraphs(ManipulationItemCallback&& callback)
42 {
43     auto document = makeRefPtr(m_document.get());
44     if (!document)
45         return;
46
47     m_callback = WTFMove(callback);
48
49     VisiblePosition start = firstPositionInNode(m_document.get());
50     VisiblePosition end = lastPositionInNode(m_document.get());
51     TextIterator iterator { start.deepEquivalent(), end.deepEquivalent() };
52     if (!document)
53         return; // VisiblePosition or TextIterator's constructor may have updated the layout and executed arbitrary scripts.
54
55     Vector<ManipulationToken> tokensInCurrentParagraph;
56     Position startOfCurrentParagraph = start.deepEquivalent();
57     while (!iterator.atEnd()) {
58         StringView currentText = iterator.text();
59
60         if (startOfCurrentParagraph.isNull())
61             startOfCurrentParagraph = iterator.range()->startPosition();
62
63         size_t endOfLastNewLine = 0;
64         size_t offsetOfNextNewLine = 0;
65         while ((offsetOfNextNewLine = currentText.find('\n', endOfLastNewLine)) != notFound) {
66             if (endOfLastNewLine < offsetOfNextNewLine) {
67                 tokensInCurrentParagraph.append(ManipulationToken { m_tokenIdentifier.generate(),
68                     currentText.substring(endOfLastNewLine, offsetOfNextNewLine - endOfLastNewLine).toString() });
69             }
70
71             auto lastRange = iterator.range();
72             if (offsetOfNextNewLine < currentText.length()) {
73                 lastRange->setStart(firstPositionInOrBeforeNode(iterator.node())); // Move the start to the beginning of the current node.
74                 TextIterator::subrange(lastRange, 0, offsetOfNextNewLine);
75             }
76             Position endOfCurrentParagraph = lastRange->endPosition();
77
78             if (!tokensInCurrentParagraph.isEmpty())
79                 addItem(startOfCurrentParagraph, endOfCurrentParagraph, WTFMove(tokensInCurrentParagraph));
80             startOfCurrentParagraph.clear();
81             endOfLastNewLine = offsetOfNextNewLine + 1;
82         }
83
84         auto remainingText = currentText.substring(endOfLastNewLine);
85         if (remainingText.length())
86             tokensInCurrentParagraph.append(ManipulationToken { m_tokenIdentifier.generate(), remainingText.toString() });
87
88         iterator.advance();
89     }
90
91     if (!tokensInCurrentParagraph.isEmpty())
92         addItem(startOfCurrentParagraph, end.deepEquivalent(), WTFMove(tokensInCurrentParagraph));
93 }
94
95 void TextManipulationController::addItem(const Position& startOfParagraph, const Position& endOfParagraph, Vector<ManipulationToken>&& tokens)
96 {
97     ASSERT(m_document);
98     auto result = m_items.add(m_itemIdentifier.generate(), ManipulationItem { startOfParagraph, endOfParagraph, WTFMove(tokens) });
99     m_callback(*m_document, result.iterator->key, result.iterator->value.tokens);
100 }
101
102 bool TextManipulationController::completeManipulation(ItemIdentifier itemIdentifier, const Vector<ManipulationToken>& replacementTokens)
103 {
104     if (!itemIdentifier)
105         return false;
106
107     auto itemIterator = m_items.find(itemIdentifier);
108     if (itemIterator == m_items.end())
109         return false;
110
111     auto didReplace = replace(itemIterator->value, replacementTokens);
112
113     m_items.remove(itemIterator);
114
115     return didReplace;
116 }
117
118 bool TextManipulationController::replace(const ManipulationItem& item, const Vector<ManipulationToken>& replacementTokens)
119 {
120     TextIterator iterator { item.start, item.end };
121     size_t currentTokenIndex = 0;
122     HashMap<TokenIdentifier, Ref<Node>> tokenToNode;
123     while (!iterator.atEnd()) {
124         auto string = iterator.text().toString();
125         if (currentTokenIndex >= item.tokens.size())
126             return false;
127         auto& currentToken = item.tokens[currentTokenIndex];
128         if (iterator.text() != currentToken.content)
129             return false;
130         tokenToNode.add(currentToken.identifier, *iterator.node());
131         iterator.advance();
132         ++currentTokenIndex;
133     }
134
135     // FIXME: This doesn't preseve the order of the replacement at all.
136     for (auto& token : replacementTokens) {
137         auto* node = tokenToNode.get(token.identifier);
138         if (!node)
139             return false;
140         if (!is<CharacterData>(node))
141             continue;
142         // FIXME: It's not safe to update DOM while iterating over the tokens.
143         downcast<CharacterData>(node)->setData(token.content);
144     }
145
146     return true;
147 }
148
149 } // namespace WebCore