<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.1//EN"
"http://www.w3.org/TR/xhtml11/DTD/xhtml11.dtd">
<html xmlns="http://www.w3.org/1999/xhtml">
<head><meta http-equiv="content-type" content="text/html; charset=utf-8" />
<title>[265361] trunk</title>
</head>
<body>

<style type="text/css"><!--
#msg dl.meta { border: 1px #006 solid; background: #369; padding: 6px; color: #fff; }
#msg dl.meta dt { float: left; width: 6em; font-weight: bold; }
#msg dt:after { content:':';}
#msg dl, #msg dt, #msg ul, #msg li, #header, #footer, #logmsg { font-family: verdana,arial,helvetica,sans-serif; font-size: 10pt;  }
#msg dl a { font-weight: bold}
#msg dl a:link    { color:#fc3; }
#msg dl a:active  { color:#ff0; }
#msg dl a:visited { color:#cc6; }
h3 { font-family: verdana,arial,helvetica,sans-serif; font-size: 10pt; font-weight: bold; }
#msg pre { overflow: auto; background: #ffc; border: 1px #fa0 solid; padding: 6px; }
#logmsg { background: #ffc; border: 1px #fa0 solid; padding: 1em 1em 0 1em; }
#logmsg p, #logmsg pre, #logmsg blockquote { margin: 0 0 1em 0; }
#logmsg p, #logmsg li, #logmsg dt, #logmsg dd { line-height: 14pt; }
#logmsg h1, #logmsg h2, #logmsg h3, #logmsg h4, #logmsg h5, #logmsg h6 { margin: .5em 0; }
#logmsg h1:first-child, #logmsg h2:first-child, #logmsg h3:first-child, #logmsg h4:first-child, #logmsg h5:first-child, #logmsg h6:first-child { margin-top: 0; }
#logmsg ul, #logmsg ol { padding: 0; list-style-position: inside; margin: 0 0 0 1em; }
#logmsg ul { text-indent: -1em; padding-left: 1em; }#logmsg ol { text-indent: -1.5em; padding-left: 1.5em; }
#logmsg > ul, #logmsg > ol { margin: 0 0 1em 0; }
#logmsg pre { background: #eee; padding: 1em; }
#logmsg blockquote { border: 1px solid #fa0; border-left-width: 10px; padding: 1em 1em 0 1em; background: white;}
#logmsg dl { margin: 0; }
#logmsg dt { font-weight: bold; }
#logmsg dd { margin: 0; padding: 0 0 0.5em 0; }
#logmsg dd:before { content:'\00bb';}
#logmsg table { border-spacing: 0px; border-collapse: collapse; border-top: 4px solid #fa0; border-bottom: 1px solid #fa0; background: #fff; }
#logmsg table th { text-align: left; font-weight: normal; padding: 0.2em 0.5em; border-top: 1px dotted #fa0; }
#logmsg table td { text-align: right; border-top: 1px dotted #fa0; padding: 0.2em 0.5em; }
#logmsg table thead th { text-align: center; border-bottom: 1px solid #fa0; }
#logmsg table th.Corner { text-align: left; }
#logmsg hr { border: none 0; border-top: 2px dashed #fa0; height: 1px; }
#header, #footer { color: #fff; background: #636; border: 1px #300 solid; padding: 6px; }
#patch { width: 100%; }
#patch h4 {font-family: verdana,arial,helvetica,sans-serif;font-size:10pt;padding:8px;background:#369;color:#fff;margin:0;}
#patch .propset h4, #patch .binary h4 {margin:0;}
#patch pre {padding:0;line-height:1.2em;margin:0;}
#patch .diff {width:100%;background:#eee;padding: 0 0 10px 0;overflow:auto;}
#patch .propset .diff, #patch .binary .diff  {padding:10px 0;}
#patch span {display:block;padding:0 10px;}
#patch .modfile, #patch .addfile, #patch .delfile, #patch .propset, #patch .binary, #patch .copfile {border:1px solid #ccc;margin:10px 0;}
#patch ins {background:#dfd;text-decoration:none;display:block;padding:0 10px;}
#patch del {background:#fdd;text-decoration:none;display:block;padding:0 10px;}
#patch .lines, .info {color:#888;background:#fff;}
--></style>
<div id="msg">
<dl class="meta">
<dt>Revision</dt> <dd><a href="http://trac.webkit.org/projects/webkit/changeset/265361">265361</a></dd>
<dt>Author</dt> <dd>sihui_liu@apple.com</dd>
<dt>Date</dt> <dd>2020-08-06 18:24:05 -0700 (Thu, 06 Aug 2020)</dd>
</dl>

<h3>Log Message</h3>
<pre>Text manipulation: leading and trailing spaces should be ignored when comparing content
https://bugs.webkit.org/show_bug.cgi?id=214878
<rdar://problem/63735024>

Reviewed by Ryosuke Niwa.

Source/WebCore:

TextIterator does not emit collapsed space if there is no text emitted before or the last emitted character is
collapsed space. When TextManipulationController starts observing paragraphs, it iterates the whole document and
the range of TextIterator is the range of document. For some text node A in the document, if TextIterator emits
text for some other text node B before it, the collapsed space at the beginning of A will be emitted, and
TextManipulationController would think the emitted space is part of A's content. When TextManipulationController
replaces content for A, and the range of TextIterator is set to the range of A, the collapsed space is not
emitted. The check to ensure A's content is unchanged would fail.

To solve this issue, for first and last token in the paragraph, TextManipulationController checks content after
removing leading and trailing spaces.

API test: TextManipulation.CompleteTextManipulationParagraphsContainCollapsedSpaces

* editing/TextManipulationController.cpp:
(WebCore::areEqualIgnoringLeadingAndTrailingWhitespaces):
(WebCore::TextManipulationController::replace):

Tools:

* TestWebKitAPI/Tests/WebKitCocoa/TextManipulation.mm:
(TestWebKitAPI::TEST):</pre>

<h3>Modified Paths</h3>
<ul>
<li><a href="#trunkSourceWebCoreChangeLog">trunk/Source/WebCore/ChangeLog</a></li>
<li><a href="#trunkSourceWebCoreeditingTextManipulationControllercpp">trunk/Source/WebCore/editing/TextManipulationController.cpp</a></li>
<li><a href="#trunkToolsChangeLog">trunk/Tools/ChangeLog</a></li>
<li><a href="#trunkToolsTestWebKitAPITestsWebKitCocoaTextManipulationmm">trunk/Tools/TestWebKitAPI/Tests/WebKitCocoa/TextManipulation.mm</a></li>
</ul>

</div>
<div id="patch">
<h3>Diff</h3>
<a id="trunkSourceWebCoreChangeLog"></a>
<div class="modfile"><h4>Modified: trunk/Source/WebCore/ChangeLog (265360 => 265361)</h4>
<pre class="diff"><span>
<span class="info">--- trunk/Source/WebCore/ChangeLog   2020-08-07 01:18:37 UTC (rev 265360)
+++ trunk/Source/WebCore/ChangeLog      2020-08-07 01:24:05 UTC (rev 265361)
</span><span class="lines">@@ -1,3 +1,28 @@
</span><ins>+2020-08-06  Sihui Liu  <sihui_liu@apple.com>
+
+        Text manipulation: leading and trailing spaces should be ignored when comparing content
+        https://bugs.webkit.org/show_bug.cgi?id=214878
+        <rdar://problem/63735024>
+
+        Reviewed by Ryosuke Niwa.
+
+        TextIterator does not emit collapsed space if there is no text emitted before or the last emitted character is 
+        collapsed space. When TextManipulationController starts observing paragraphs, it iterates the whole document and
+        the range of TextIterator is the range of document. For some text node A in the document, if TextIterator emits 
+        text for some other text node B before it, the collapsed space at the beginning of A will be emitted, and 
+        TextManipulationController would think the emitted space is part of A's content. When TextManipulationController
+        replaces content for A, and the range of TextIterator is set to the range of A, the collapsed space is not 
+        emitted. The check to ensure A's content is unchanged would fail.

+        To solve this issue, for first and last token in the paragraph, TextManipulationController checks content after 
+        removing leading and trailing spaces.

+        API test: TextManipulation.CompleteTextManipulationParagraphsContainCollapsedSpaces
+
+        * editing/TextManipulationController.cpp:
+        (WebCore::areEqualIgnoringLeadingAndTrailingWhitespaces):
+        (WebCore::TextManipulationController::replace):
+
</ins><span class="cx"> 2020-08-06  Kenneth Russell  <kbr@chromium.org>
</span><span class="cx"> 
</span><span class="cx">         Implement createImageBitmap(ImageData)
</span></span></pre></div>
<a id="trunkSourceWebCoreeditingTextManipulationControllercpp"></a>
<div class="modfile"><h4>Modified: trunk/Source/WebCore/editing/TextManipulationController.cpp (265360 => 265361)</h4>
<pre class="diff"><span>
<span class="info">--- trunk/Source/WebCore/editing/TextManipulationController.cpp      2020-08-07 01:18:37 UTC (rev 265360)
+++ trunk/Source/WebCore/editing/TextManipulationController.cpp 2020-08-07 01:24:05 UTC (rev 265361)
</span><span class="lines">@@ -281,6 +281,11 @@
</span><span class="cx">     return element.hasTagName(HTMLNames::titleTag) || element.hasTagName(HTMLNames::optionTag);
</span><span class="cx"> }
</span><span class="cx"> 
</span><ins>+static bool areEqualIgnoringLeadingAndTrailingWhitespaces(const String& content, const String& originalContent)
+{
+    return content.stripWhiteSpace() == originalContent.stripWhiteSpace();
+}
+
</ins><span class="cx"> static Optional<TextManipulationController::ManipulationTokenInfo> tokenInfo(Node* node)
</span><span class="cx"> {
</span><span class="cx">     if (!node)
</span><span class="lines">@@ -793,7 +798,13 @@
</span><span class="cx">                 return ManipulationFailureType::ContentChanged;
</span><span class="cx"> 
</span><span class="cx">             auto& currentToken = item.tokens[currentTokenIndex++];
</span><del>-            if (!content.isReplacedContent && currentToken.content != token.content)
</del><ins>+            bool isContentUnchanged = currentToken.content == token.content;
+            if (!UNLIKELY(isContentUnchanged)) {
+                bool isFirstOrLastToken = currentTokenIndex == 1 || currentTokenIndex == item.tokens.size();
+                isContentUnchanged = isFirstOrLastToken && areEqualIgnoringLeadingAndTrailingWhitespaces(currentToken.content, token.content);
+            }
+
+            if (!content.isReplacedContent && !isContentUnchanged)
</ins><span class="cx">                 return ManipulationFailureType::ContentChanged;
</span><span class="cx"> 
</span><span class="cx">             tokenExchangeMap.set(currentToken.identifier, TokenExchangeData { content.node.copyRef(), currentToken.content, !isNodeIncluded });
</span></span></pre></div>
<a id="trunkToolsChangeLog"></a>
<div class="modfile"><h4>Modified: trunk/Tools/ChangeLog (265360 => 265361)</h4>
<pre class="diff"><span>
<span class="info">--- trunk/Tools/ChangeLog    2020-08-07 01:18:37 UTC (rev 265360)
+++ trunk/Tools/ChangeLog       2020-08-07 01:24:05 UTC (rev 265361)
</span><span class="lines">@@ -1,3 +1,14 @@
</span><ins>+2020-08-06  Sihui Liu  <sihui_liu@apple.com>
+
+        Text manipulation: leading and trailing spaces should be ignored when comparing content
+        https://bugs.webkit.org/show_bug.cgi?id=214878
+        <rdar://problem/63735024>
+
+        Reviewed by Ryosuke Niwa.
+
+        * TestWebKitAPI/Tests/WebKitCocoa/TextManipulation.mm:
+        (TestWebKitAPI::TEST):
+
</ins><span class="cx"> 2020-08-06  Jonathan Bedard  <jbedard@apple.com>
</span><span class="cx"> 
</span><span class="cx">         [webkitcorepy] Standardize setuptools version
</span></span></pre></div>
<a id="trunkToolsTestWebKitAPITestsWebKitCocoaTextManipulationmm"></a>
<div class="modfile"><h4>Modified: trunk/Tools/TestWebKitAPI/Tests/WebKitCocoa/TextManipulation.mm (265360 => 265361)</h4>
<pre class="diff"><span>
<span class="info">--- trunk/Tools/TestWebKitAPI/Tests/WebKitCocoa/TextManipulation.mm  2020-08-07 01:18:37 UTC (rev 265360)
+++ trunk/Tools/TestWebKitAPI/Tests/WebKitCocoa/TextManipulation.mm     2020-08-07 01:24:05 UTC (rev 265361)
</span><span class="lines">@@ -3040,6 +3040,48 @@
</span><span class="cx">     EXPECT_WK_STREQ("auto", [webView stringByEvaluatingJavaScript:@"getComputedStyle(document.querySelector('span')).overflowY"]);
</span><span class="cx"> }
</span><span class="cx"> 
</span><ins>+TEST(TextManipulation, CompleteTextManipulationParagraphsContainCollapsedSpaces)
+{
+    auto delegate = adoptNS([[TextManipulationDelegate alloc] init]);
+    auto webView = adoptNS([[TestWKWebView alloc] initWithFrame:NSMakeRect(0, 0, 400, 400)]);
+    [webView _setTextManipulationDelegate:delegate.get()];
+    [webView synchronouslyLoadHTMLString:@"<!DOCTYPE html>"
+        "<head>"
+        "<style>"
+        "span { display: inline-block; }"
+        "</style>"
+        "</head>"
+        "<body>"
+        "<span>  hello</span>"
+        "<span>  world</span>"
+        "</body>"];
+
+    done = false;
+    [webView _startTextManipulationsWithConfiguration:nil completion:^{
+        done = true;
+    }];
+    TestWebKitAPI::Util::run(&done);
+
+    auto items = [delegate items];
+    EXPECT_EQ(items.count, 2UL);
+    EXPECT_EQ(items[0].tokens.count, 1UL);
+    EXPECT_WK_STREQ("hello", items[0].tokens[0].content);
+    EXPECT_EQ(items[1].tokens.count, 1UL);
+    EXPECT_WK_STREQ(" world", items[1].tokens[0].content);
+
+    done = false;
+    [webView _completeTextManipulationForItems:@[
+        createItem(items[0].identifier, {{ items[0].tokens[0].identifier, @"Hello" }}).get(),
+        createItem(items[1].identifier, {{ items[1].tokens[0].identifier, @"World" }}).get(),
+    ] completion:^(NSArray<NSError *> *errors) {
+        EXPECT_EQ(errors, nil);
+        done = true;
+    }];
+    TestWebKitAPI::Util::run(&done);
+
+    EXPECT_WK_STREQ("<span>Hello</span><span>World</span>", [webView stringByEvaluatingJavaScript:@"document.body.innerHTML"]);
+}
+
</ins><span class="cx"> TEST(TextManipulation, TextManipulationTokenDebugDescription)
</span><span class="cx"> {
</span><span class="cx">     auto token = adoptNS([[_WKTextManipulationToken alloc] init]);
</span></span></pre>
</div>
</div>

</body>
</html>