[Cocoa] [WebKit2] Add support for replacing find-in-page text matches
authorwenson_hsieh@apple.com <wenson_hsieh@apple.com@268f45cc-cd09-0410-ab3c-d52691b4dbfc>
Thu, 22 Nov 2018 05:03:59 +0000 (05:03 +0000)
committerwenson_hsieh@apple.com <wenson_hsieh@apple.com@268f45cc-cd09-0410-ab3c-d52691b4dbfc>
Thu, 22 Nov 2018 05:03:59 +0000 (05:03 +0000)
commit0be273d31c5527603d9b7986d72bea9f8f4afb72
tree82e530e11f1700fffa52589c12cde619362cd08c
parent94ce27eb660ea2d25e474febb1483966e0e7abe0
[Cocoa] [WebKit2] Add support for replacing find-in-page text matches
https://bugs.webkit.org/show_bug.cgi?id=191786
<rdar://problem/45813871>

Reviewed by Ryosuke Niwa.

Source/WebCore:

Add support for replacing Find-in-Page matches. See below for details. Covered by new layout tests as well as a
new API test.

Tests: editing/find/find-and-replace-adjacent-words.html
       editing/find/find-and-replace-at-editing-boundary.html
       editing/find/find-and-replace-basic.html
       editing/find/find-and-replace-in-subframes.html
       editing/find/find-and-replace-no-matches.html
       editing/find/find-and-replace-noneditable-matches.html
       editing/find/find-and-replace-replacement-text-input-events.html

API test: WebKit.FindAndReplace

* page/Page.cpp:
(WebCore::replaceRanges):
(WebCore::Page::replaceRangesWithText):

Add a helper that, given a list of Ranges, replaces each range with the given text. To do this, we first map
each Range to editing offsets within the topmost editable root for each Range. This results in a map of editable
root to list of editing offsets we need to replace. To apply the replacements, for each editable root in the
map, we iterate over each replacement range (i.e. an offset and length), set the current selection to contain
that replacement range, and use `Editor::replaceSelectionWithText`. To prevent prior text replacements from
clobbering the offsets of latter text replacement ranges, we also iterate backwards through text replacement
ranges when performing each replacement.

Likewise, we also apply text replacement to each editing container in backwards order: for nodes in the same
frame, we compare their position in the document, and for nodes in different frames, we instead compare their
frames in frame tree traversal order.

We map Ranges to editing offsets and back when performing text replacement because each text replacement may
split or merge text nodes, which causes adjacent Ranges to shrink or extend while replacing text. In an earlier
attempt to implement this, I simply iterated over each Range to replace and carried out text replacement for
each Range. This led to incorrect behavior in some cases, such as replacing adjacent matches. Thus, by computing
the set of text replacement offsets prior to replacing any text, we're able to target the correct ranges for
replacement.

(WebCore::Page::replaceSelectionWithText):

Add a helper method on Page to replace the current selection with some text. This simply calls out to
`Editor::replaceSelectionWithText`.

* page/Page.h:

Source/WebCore/PAL:

Add `-replaceMatches:withString:inSelectionOnly:resultCollector:`.

* pal/spi/mac/NSTextFinderSPI.h:

Source/WebKit:

* UIProcess/API/Cocoa/WKWebView.mm:
(-[WKWebView replaceMatches:withString:inSelectionOnly:resultCollector:]):
* UIProcess/WebPageProxy.cpp:
(WebKit::WebPageProxy::replaceMatches):
* UIProcess/WebPageProxy.h:
* UIProcess/mac/WKTextFinderClient.mm:
(-[WKTextFinderClient replaceMatches:withString:inSelectionOnly:resultCollector:]):

Implement this method to opt in to "Replaceā€¦" UI on macOS in the find bar. In this API, we're given a list of
matches to replace. We propagate the indices of each match to the web process, where FindController maps them to
corresponding replacement ranges. Currently, the given list of matches is only ever a list containing the first
match, or a list containing all matches.

* WebProcess/InjectedBundle/API/c/WKBundlePage.cpp:
(WKBundlePageFindStringMatches):
(WKBundlePageReplaceStringMatches):
* WebProcess/InjectedBundle/API/c/WKBundlePage.h:
* WebProcess/WebCoreSupport/WebEditorClient.cpp:
* WebProcess/WebPage/FindController.cpp:
(WebKit::FindController::replaceMatches):

Map match indices to Ranges, and then call into WebCore::Page to do the heavy lifting (see WebCore ChangeLog for
more details). Additionally add a hard find-and-replace limit here to prevent the web process from spinning
indefinitely if there are an enormous number of find matches.

* WebProcess/WebPage/FindController.h:
* WebProcess/WebPage/WebPage.cpp:
(WebKit::WebPage::findStringMatchesFromInjectedBundle):
(WebKit::WebPage::replaceStringMatchesFromInjectedBundle):

Add helpers to exercise find and replace in WebKit2.

(WebKit::WebPage::replaceMatches):
* WebProcess/WebPage/WebPage.h:
* WebProcess/WebPage/WebPage.messages.in:

Tools:

* MiniBrowser/mac/WK2BrowserWindowController.m:
(-[WK2BrowserWindowController setFindBarView:]):

Fix a bug in MiniBrowser that prevents AppKit from displaying the "All" button in the find bar after checking
the "Replace" option.

* TestWebKitAPI/Tests/WebKitCocoa/FindInPage.mm:

Add an API test to exercise find-and-replace API using WKWebView.

(replaceMatches):
(TEST):
* WebKitTestRunner/InjectedBundle/Bindings/TestRunner.idl:
* WebKitTestRunner/InjectedBundle/TestRunner.cpp:
(WTR::findOptionsFromArray):
(WTR::TestRunner::findString):
(WTR::TestRunner::findStringMatchesInPage):
(WTR::TestRunner::replaceFindMatchesAtIndices):

Add TestRunner hooks to simulate find-in-page and replace.

* WebKitTestRunner/InjectedBundle/TestRunner.h:

LayoutTests:

Introduce a `LayoutTests/editing/find` directory to contain tests around `FindController`, and add 7 new layout
tests. These are currently enabled only for WebKit2 on macOS and iOS.

* TestExpectations:
* editing/find/find-and-replace-adjacent-words-expected.txt: Added.
* editing/find/find-and-replace-adjacent-words.html: Added.

Test find-and-replace with adjacent words.

* editing/find/find-and-replace-at-editing-boundary-expected.txt: Added.
* editing/find/find-and-replace-at-editing-boundary.html: Added.

Test find-and-replace when one of the find matches straddles an editing boundary. In this case, we verify that
the replacement does not occur, since only part of the word would be replaced.

* editing/find/find-and-replace-basic-expected.txt: Added.
* editing/find/find-and-replace-basic.html: Added.

Add a basic test that exercises a single text replacement, and "replace all".

* editing/find/find-and-replace-in-subframes-expected.txt: Added.
* editing/find/find-and-replace-in-subframes.html: Added.

Test find-and-replace when some of the matches are in editable content in subframes. This test additionally
contains matches in shadow content (in this case, text fields) within both the main document and the subframe,
and verifies that text replacement reaches these elements as well.

* editing/find/find-and-replace-no-matches-expected.txt: Added.
* editing/find/find-and-replace-no-matches.html: Added.

Test find-and-replace when no replacement matches are specified. In this case, we fall back to inserting the
replacement text at the current selection.

* editing/find/find-and-replace-noneditable-matches-expected.txt: Added.
* editing/find/find-and-replace-noneditable-matches.html: Added.

Test find-and-replace when some of the matches to replace are noneditable, others are editable, and others are
editable but are nested within noneditable elements (i.e. `contenteditable=false`). In this case, "replace all"
should still replace all fully editable matches.

* editing/find/find-and-replace-replacement-text-input-events-expected.txt: Added.
* editing/find/find-and-replace-replacement-text-input-events.html: Added.

Tests that find-and-replace emits input events of `inputType` "insertReplacementText", except when inserting
replacement text at a caret selection.

* platform/ios-wk2/TestExpectations:
* platform/mac-wk2/TestExpectations:

git-svn-id: https://svn.webkit.org/repository/webkit/trunk@238438 268f45cc-cd09-0410-ab3c-d52691b4dbfc
41 files changed:
LayoutTests/ChangeLog
LayoutTests/TestExpectations
LayoutTests/editing/find/find-and-replace-adjacent-words-expected.txt [new file with mode: 0644]
LayoutTests/editing/find/find-and-replace-adjacent-words.html [new file with mode: 0644]
LayoutTests/editing/find/find-and-replace-at-editing-boundary-expected.txt [new file with mode: 0644]
LayoutTests/editing/find/find-and-replace-at-editing-boundary.html [new file with mode: 0644]
LayoutTests/editing/find/find-and-replace-basic-expected.txt [new file with mode: 0644]
LayoutTests/editing/find/find-and-replace-basic.html [new file with mode: 0644]
LayoutTests/editing/find/find-and-replace-in-subframes-expected.txt [new file with mode: 0644]
LayoutTests/editing/find/find-and-replace-in-subframes.html [new file with mode: 0644]
LayoutTests/editing/find/find-and-replace-no-matches-expected.txt [new file with mode: 0644]
LayoutTests/editing/find/find-and-replace-no-matches.html [new file with mode: 0644]
LayoutTests/editing/find/find-and-replace-noneditable-matches-expected.txt [new file with mode: 0644]
LayoutTests/editing/find/find-and-replace-noneditable-matches.html [new file with mode: 0644]
LayoutTests/editing/find/find-and-replace-replacement-text-input-events-expected.txt [new file with mode: 0644]
LayoutTests/editing/find/find-and-replace-replacement-text-input-events.html [new file with mode: 0644]
LayoutTests/platform/ios-wk2/TestExpectations
LayoutTests/platform/mac-wk2/TestExpectations
Source/WebCore/ChangeLog
Source/WebCore/PAL/ChangeLog
Source/WebCore/PAL/pal/spi/mac/NSTextFinderSPI.h
Source/WebCore/page/Page.cpp
Source/WebCore/page/Page.h
Source/WebKit/ChangeLog
Source/WebKit/UIProcess/API/Cocoa/WKWebView.mm
Source/WebKit/UIProcess/WebPageProxy.cpp
Source/WebKit/UIProcess/WebPageProxy.h
Source/WebKit/UIProcess/mac/WKTextFinderClient.mm
Source/WebKit/WebProcess/InjectedBundle/API/c/WKBundlePage.cpp
Source/WebKit/WebProcess/InjectedBundle/API/c/WKBundlePage.h
Source/WebKit/WebProcess/WebPage/FindController.cpp
Source/WebKit/WebProcess/WebPage/FindController.h
Source/WebKit/WebProcess/WebPage/WebPage.cpp
Source/WebKit/WebProcess/WebPage/WebPage.h
Source/WebKit/WebProcess/WebPage/WebPage.messages.in
Tools/ChangeLog
Tools/MiniBrowser/mac/WK2BrowserWindowController.m
Tools/TestWebKitAPI/Tests/WebKitCocoa/FindInPage.mm
Tools/WebKitTestRunner/InjectedBundle/Bindings/TestRunner.idl
Tools/WebKitTestRunner/InjectedBundle/TestRunner.cpp
Tools/WebKitTestRunner/InjectedBundle/TestRunner.h