Changeset 238438 in webkit
- Timestamp:
- Nov 21, 2018 9:03:59 PM (5 years ago)
- Location:
- trunk
- Files:
-
- 15 added
- 27 edited
Legend:
- Unmodified
- Added
- Removed
-
trunk/LayoutTests/ChangeLog
r238430 r238438 1 2018-11-21 Wenson Hsieh <wenson_hsieh@apple.com> 2 3 [Cocoa] [WebKit2] Add support for replacing find-in-page text matches 4 https://bugs.webkit.org/show_bug.cgi?id=191786 5 <rdar://problem/45813871> 6 7 Reviewed by Ryosuke Niwa. 8 9 Introduce a `LayoutTests/editing/find` directory to contain tests around `FindController`, and add 7 new layout 10 tests. These are currently enabled only for WebKit2 on macOS and iOS. 11 12 * TestExpectations: 13 * editing/find/find-and-replace-adjacent-words-expected.txt: Added. 14 * editing/find/find-and-replace-adjacent-words.html: Added. 15 16 Test find-and-replace with adjacent words. 17 18 * editing/find/find-and-replace-at-editing-boundary-expected.txt: Added. 19 * editing/find/find-and-replace-at-editing-boundary.html: Added. 20 21 Test find-and-replace when one of the find matches straddles an editing boundary. In this case, we verify that 22 the replacement does not occur, since only part of the word would be replaced. 23 24 * editing/find/find-and-replace-basic-expected.txt: Added. 25 * editing/find/find-and-replace-basic.html: Added. 26 27 Add a basic test that exercises a single text replacement, and "replace all". 28 29 * editing/find/find-and-replace-in-subframes-expected.txt: Added. 30 * editing/find/find-and-replace-in-subframes.html: Added. 31 32 Test find-and-replace when some of the matches are in editable content in subframes. This test additionally 33 contains matches in shadow content (in this case, text fields) within both the main document and the subframe, 34 and verifies that text replacement reaches these elements as well. 35 36 * editing/find/find-and-replace-no-matches-expected.txt: Added. 37 * editing/find/find-and-replace-no-matches.html: Added. 38 39 Test find-and-replace when no replacement matches are specified. In this case, we fall back to inserting the 40 replacement text at the current selection. 41 42 * editing/find/find-and-replace-noneditable-matches-expected.txt: Added. 43 * editing/find/find-and-replace-noneditable-matches.html: Added. 44 45 Test find-and-replace when some of the matches to replace are noneditable, others are editable, and others are 46 editable but are nested within noneditable elements (i.e. `contenteditable=false`). In this case, "replace all" 47 should still replace all fully editable matches. 48 49 * editing/find/find-and-replace-replacement-text-input-events-expected.txt: Added. 50 * editing/find/find-and-replace-replacement-text-input-events.html: Added. 51 52 Tests that find-and-replace emits input events of `inputType` "insertReplacementText", except when inserting 53 replacement text at a caret selection. 54 55 * platform/ios-wk2/TestExpectations: 56 * platform/mac-wk2/TestExpectations: 57 1 58 2018-11-21 Zalan Bujtas <zalan@apple.com> 2 59 -
trunk/LayoutTests/TestExpectations
r238274 r238438 16 16 editing/mac [ Skip ] 17 17 editing/caret/ios [ Skip ] 18 editing/find [ Skip ] 18 19 editing/pasteboard/gtk [ Skip ] 19 20 editing/selection/ios [ Skip ] -
trunk/LayoutTests/platform/ios-wk2/TestExpectations
r238166 r238438 15 15 tiled-drawing/ios [ Pass ] 16 16 fast/web-share [ Pass ] 17 editing/find [ Pass ] 17 18 18 19 editing/selection/character-granularity-rect.html [ Failure ] -
trunk/LayoutTests/platform/mac-wk2/TestExpectations
r238375 r238438 11 11 swipe [ Pass ] 12 12 fast/web-share [ Pass ] 13 editing/find [ Pass ] 13 14 14 15 fast/events/autoscroll-when-zoomed.html [ Pass ] -
trunk/Source/WebCore/ChangeLog
r238434 r238438 1 2018-11-21 Wenson Hsieh <wenson_hsieh@apple.com> 2 3 [Cocoa] [WebKit2] Add support for replacing find-in-page text matches 4 https://bugs.webkit.org/show_bug.cgi?id=191786 5 <rdar://problem/45813871> 6 7 Reviewed by Ryosuke Niwa. 8 9 Add support for replacing Find-in-Page matches. See below for details. Covered by new layout tests as well as a 10 new API test. 11 12 Tests: editing/find/find-and-replace-adjacent-words.html 13 editing/find/find-and-replace-at-editing-boundary.html 14 editing/find/find-and-replace-basic.html 15 editing/find/find-and-replace-in-subframes.html 16 editing/find/find-and-replace-no-matches.html 17 editing/find/find-and-replace-noneditable-matches.html 18 editing/find/find-and-replace-replacement-text-input-events.html 19 20 API test: WebKit.FindAndReplace 21 22 * page/Page.cpp: 23 (WebCore::replaceRanges): 24 (WebCore::Page::replaceRangesWithText): 25 26 Add a helper that, given a list of Ranges, replaces each range with the given text. To do this, we first map 27 each Range to editing offsets within the topmost editable root for each Range. This results in a map of editable 28 root to list of editing offsets we need to replace. To apply the replacements, for each editable root in the 29 map, we iterate over each replacement range (i.e. an offset and length), set the current selection to contain 30 that replacement range, and use `Editor::replaceSelectionWithText`. To prevent prior text replacements from 31 clobbering the offsets of latter text replacement ranges, we also iterate backwards through text replacement 32 ranges when performing each replacement. 33 34 Likewise, we also apply text replacement to each editing container in backwards order: for nodes in the same 35 frame, we compare their position in the document, and for nodes in different frames, we instead compare their 36 frames in frame tree traversal order. 37 38 We map Ranges to editing offsets and back when performing text replacement because each text replacement may 39 split or merge text nodes, which causes adjacent Ranges to shrink or extend while replacing text. In an earlier 40 attempt to implement this, I simply iterated over each Range to replace and carried out text replacement for 41 each Range. This led to incorrect behavior in some cases, such as replacing adjacent matches. Thus, by computing 42 the set of text replacement offsets prior to replacing any text, we're able to target the correct ranges for 43 replacement. 44 45 (WebCore::Page::replaceSelectionWithText): 46 47 Add a helper method on Page to replace the current selection with some text. This simply calls out to 48 `Editor::replaceSelectionWithText`. 49 50 * page/Page.h: 51 1 52 2018-11-21 Andy Estes <aestes@apple.com> 2 53 -
trunk/Source/WebCore/PAL/ChangeLog
r238434 r238438 1 2018-11-21 Wenson Hsieh <wenson_hsieh@apple.com> 2 3 [Cocoa] [WebKit2] Add support for replacing find-in-page text matches 4 https://bugs.webkit.org/show_bug.cgi?id=191786 5 <rdar://problem/45813871> 6 7 Reviewed by Ryosuke Niwa. 8 9 Add `-replaceMatches:withString:inSelectionOnly:resultCollector:`. 10 11 * pal/spi/mac/NSTextFinderSPI.h: 12 1 13 2018-11-21 Andy Estes <aestes@apple.com> 2 14 -
trunk/Source/WebCore/PAL/pal/spi/mac/NSTextFinderSPI.h
r220979 r238438 52 52 - (void)getSelectedText:(void (^)(NSString *selectedTextString))completionHandler; 53 53 - (void)selectFindMatch:(id <NSTextFinderAsynchronousDocumentFindMatch>)findMatch completionHandler:(void (^)(void))completionHandler; 54 - (void)replaceMatches:(NSArray *)matches withString:(NSString *)replacementString inSelectionOnly:(BOOL)selectionOnly resultCollector:(void (^)(NSUInteger replacementCount))resultCollector; 54 55 55 56 @end -
trunk/Source/WebCore/page/Page.cpp
r238192 r238438 44 44 #include "DocumentTimeline.h" 45 45 #include "DragController.h" 46 #include "Editing.h" 46 47 #include "Editor.h" 47 48 #include "EditorClient.h" … … 110 111 #include "StyleScope.h" 111 112 #include "SubframeLoader.h" 113 #include "TextIterator.h" 112 114 #include "TextResourceDecoder.h" 113 115 #include "UserContentProvider.h" … … 763 765 { 764 766 return findMatchesForText(target, options, maxMatchCount, DoNotHighlightMatches, DoNotMarkMatches); 767 } 768 769 struct FindReplacementRange { 770 RefPtr<ContainerNode> root; 771 size_t location { notFound }; 772 size_t length { 0 }; 773 }; 774 775 static void replaceRanges(Page& page, Vector<FindReplacementRange>&& ranges, const String& replacementText) 776 { 777 HashMap<RefPtr<ContainerNode>, Vector<FindReplacementRange>> rangesByContainerNode; 778 for (auto& range : ranges) { 779 auto& rangeList = rangesByContainerNode.ensure(range.root, [] { 780 return Vector<FindReplacementRange> { }; 781 }).iterator->value; 782 783 // Ensure that ranges are sorted by their end offsets, per editing container. 784 auto endOffsetForRange = range.location + range.length; 785 auto insertionIndex = rangeList.size(); 786 for (auto iterator = rangeList.rbegin(); iterator != rangeList.rend(); ++iterator) { 787 auto endOffsetBeforeInsertionIndex = iterator->location + iterator->length; 788 if (endOffsetForRange >= endOffsetBeforeInsertionIndex) 789 break; 790 insertionIndex--; 791 } 792 rangeList.insert(insertionIndex, range); 793 } 794 795 HashMap<RefPtr<Frame>, unsigned> frameToTraversalIndexMap; 796 unsigned currentFrameTraversalIndex = 0; 797 for (Frame* frame = &page.mainFrame(); frame; frame = frame->tree().traverseNext()) 798 frameToTraversalIndexMap.set(frame, currentFrameTraversalIndex++); 799 800 // Likewise, iterate backwards (in document and frame order) through editing containers that contain text matches, 801 // so that we're consistent with our backwards iteration behavior per editing container when replacing text. 802 auto containerNodesInOrderOfReplacement = copyToVector(rangesByContainerNode.keys()); 803 std::sort(containerNodesInOrderOfReplacement.begin(), containerNodesInOrderOfReplacement.end(), [frameToTraversalIndexMap] (auto& firstNode, auto& secondNode) { 804 if (firstNode == secondNode) 805 return false; 806 807 auto firstFrame = makeRefPtr(firstNode->document().frame()); 808 if (!firstFrame) 809 return true; 810 811 auto secondFrame = makeRefPtr(secondNode->document().frame()); 812 if (!secondFrame) 813 return false; 814 815 if (firstFrame == secondFrame) { 816 // comparePositions is used here instead of Node::compareDocumentPosition because some editing roots may exist inside shadow roots. 817 return comparePositions({ firstNode.get(), Position::PositionIsBeforeChildren }, { secondNode.get(), Position::PositionIsBeforeChildren }) > 0; 818 } 819 return frameToTraversalIndexMap.get(firstFrame) > frameToTraversalIndexMap.get(secondFrame); 820 }); 821 822 for (auto container : containerNodesInOrderOfReplacement) { 823 auto frame = makeRefPtr(container->document().frame()); 824 if (!frame) 825 continue; 826 827 // Iterate backwards through ranges when replacing text, such that earlier text replacements don't clobber replacement ranges later on. 828 auto& ranges = rangesByContainerNode.find(container)->value; 829 for (auto iterator = ranges.rbegin(); iterator != ranges.rend(); ++iterator) { 830 auto range = TextIterator::rangeFromLocationAndLength(container.get(), iterator->location, iterator->length); 831 if (!range || range->collapsed()) 832 continue; 833 834 frame->selection().setSelectedRange(range.get(), DOWNSTREAM, true); 835 frame->editor().replaceSelectionWithText(replacementText, true, false, EditAction::InsertReplacement); 836 } 837 } 838 } 839 840 uint32_t Page::replaceRangesWithText(Vector<Ref<Range>>&& rangesToReplace, const String& replacementText, bool selectionOnly) 841 { 842 // FIXME: In the future, we should respect the `selectionOnly` flag by checking whether each range being replaced is 843 // contained within its frame's selection. 844 UNUSED_PARAM(selectionOnly); 845 846 Vector<FindReplacementRange> replacementRanges; 847 replacementRanges.reserveInitialCapacity(rangesToReplace.size()); 848 849 for (auto& range : rangesToReplace) { 850 auto highestRoot = makeRefPtr(highestEditableRoot(range->startPosition())); 851 if (!highestRoot || highestRoot != highestEditableRoot(range->endPosition())) 852 continue; 853 854 auto frame = makeRefPtr(highestRoot->document().frame()); 855 if (!frame) 856 continue; 857 858 size_t replacementLocation = notFound; 859 size_t replacementLength = 0; 860 if (!TextIterator::getLocationAndLengthFromRange(highestRoot.get(), range.ptr(), replacementLocation, replacementLength)) 861 continue; 862 863 if (replacementLocation == notFound || !replacementLength) 864 continue; 865 866 replacementRanges.append({ WTFMove(highestRoot), replacementLocation, replacementLength }); 867 } 868 869 replaceRanges(*this, WTFMove(replacementRanges), replacementText); 870 return rangesToReplace.size(); 871 } 872 873 uint32_t Page::replaceSelectionWithText(const String& replacementText) 874 { 875 auto frame = makeRef(focusController().focusedOrMainFrame()); 876 auto selection = frame->selection().selection(); 877 if (!selection.isContentEditable()) 878 return 0; 879 880 auto editAction = selection.isRange() ? EditAction::InsertReplacement : EditAction::Insert; 881 frame->editor().replaceSelectionWithText(replacementText, true, false, editAction); 882 return 1; 765 883 } 766 884 -
trunk/Source/WebCore/page/Page.h
r238049 r238438 279 279 280 280 WEBCORE_EXPORT bool findString(const String&, FindOptions, DidWrap* = nullptr); 281 WEBCORE_EXPORT uint32_t replaceRangesWithText(Vector<Ref<Range>>&& rangesToReplace, const String& replacementText, bool selectionOnly); 282 WEBCORE_EXPORT uint32_t replaceSelectionWithText(const String& replacementText); 281 283 282 284 WEBCORE_EXPORT RefPtr<Range> rangeOfString(const String&, Range*, FindOptions); -
trunk/Source/WebKit/ChangeLog
r238434 r238438 1 2018-11-21 Wenson Hsieh <wenson_hsieh@apple.com> 2 3 [Cocoa] [WebKit2] Add support for replacing find-in-page text matches 4 https://bugs.webkit.org/show_bug.cgi?id=191786 5 <rdar://problem/45813871> 6 7 Reviewed by Ryosuke Niwa. 8 9 * UIProcess/API/Cocoa/WKWebView.mm: 10 (-[WKWebView replaceMatches:withString:inSelectionOnly:resultCollector:]): 11 * UIProcess/WebPageProxy.cpp: 12 (WebKit::WebPageProxy::replaceMatches): 13 * UIProcess/WebPageProxy.h: 14 * UIProcess/mac/WKTextFinderClient.mm: 15 (-[WKTextFinderClient replaceMatches:withString:inSelectionOnly:resultCollector:]): 16 17 Implement this method to opt in to "Replace…" UI on macOS in the find bar. In this API, we're given a list of 18 matches to replace. We propagate the indices of each match to the web process, where FindController maps them to 19 corresponding replacement ranges. Currently, the given list of matches is only ever a list containing the first 20 match, or a list containing all matches. 21 22 * WebProcess/InjectedBundle/API/c/WKBundlePage.cpp: 23 (WKBundlePageFindStringMatches): 24 (WKBundlePageReplaceStringMatches): 25 * WebProcess/InjectedBundle/API/c/WKBundlePage.h: 26 * WebProcess/WebCoreSupport/WebEditorClient.cpp: 27 * WebProcess/WebPage/FindController.cpp: 28 (WebKit::FindController::replaceMatches): 29 30 Map match indices to Ranges, and then call into WebCore::Page to do the heavy lifting (see WebCore ChangeLog for 31 more details). Additionally add a hard find-and-replace limit here to prevent the web process from spinning 32 indefinitely if there are an enormous number of find matches. 33 34 * WebProcess/WebPage/FindController.h: 35 * WebProcess/WebPage/WebPage.cpp: 36 (WebKit::WebPage::findStringMatchesFromInjectedBundle): 37 (WebKit::WebPage::replaceStringMatchesFromInjectedBundle): 38 39 Add helpers to exercise find and replace in WebKit2. 40 41 (WebKit::WebPage::replaceMatches): 42 * WebProcess/WebPage/WebPage.h: 43 * WebProcess/WebPage/WebPage.messages.in: 44 1 45 2018-11-21 Andy Estes <aestes@apple.com> 2 46 -
trunk/Source/WebKit/UIProcess/API/Cocoa/WKWebView.mm
r238388 r238438 4090 4090 } 4091 4091 4092 - (void)replaceMatches:(NSArray *)matches withString:(NSString *)replacementString inSelectionOnly:(BOOL)selectionOnly resultCollector:(void (^)(NSUInteger replacementCount))resultCollector 4093 { 4094 [[self _ensureTextFinderClient] replaceMatches:matches withString:replacementString inSelectionOnly:selectionOnly resultCollector:resultCollector]; 4095 } 4096 4092 4097 - (NSView *)documentContainerView 4093 4098 { -
trunk/Source/WebKit/UIProcess/WebPageProxy.cpp
r238388 r238438 3309 3309 } 3310 3310 3311 void WebPageProxy::replaceMatches(Vector<uint32_t>&& matchIndices, const String& replacementText, bool selectionOnly, Function<void(uint64_t, CallbackBase::Error)>&& callback) 3312 { 3313 if (!isValid()) { 3314 callback(0, CallbackBase::Error::Unknown); 3315 return; 3316 } 3317 3318 auto callbackID = m_callbacks.put(WTFMove(callback), m_process->throttler().backgroundActivityToken()); 3319 m_process->send(Messages::WebPage::ReplaceMatches(WTFMove(matchIndices), replacementText, selectionOnly, callbackID), m_pageID); 3320 } 3321 3311 3322 void WebPageProxy::runJavaScriptInMainFrame(const String& script, bool forceUserGesture, WTF::Function<void (API::SerializedScriptValue*, bool hadException, const ExceptionDetails&, CallbackBase::Error)>&& callbackFunction) 3312 3323 { -
trunk/Source/WebKit/UIProcess/WebPageProxy.h
r238388 r238438 914 914 void hideFindUI(); 915 915 void countStringMatches(const String&, FindOptions, unsigned maxMatchCount); 916 void replaceMatches(Vector<uint32_t>&& matchIndices, const String& replacementText, bool selectionOnly, Function<void(uint64_t, CallbackBase::Error)>&&); 916 917 void didCountStringMatches(const String&, uint32_t matchCount); 917 918 void setTextIndicator(const WebCore::TextIndicatorData&, uint64_t /* WebCore::TextIndicatorWindowLifetime */ lifetime = 0 /* Permanent */); -
trunk/Source/WebKit/UIProcess/mac/WKTextFinderClient.mm
r235265 r238438 35 35 #import <algorithm> 36 36 #import <pal/spi/mac/NSTextFinderSPI.h> 37 #import <wtf/BlockPtr.h> 37 38 #import <wtf/Deque.h> 38 39 39 // FIXME: Implement support for replace.40 40 // FIXME: Implement scrollFindMatchToVisible. 41 41 // FIXME: The NSTextFinder overlay doesn't move with scrolling; we should have a mode where we manage the overlay. … … 171 171 172 172 #pragma mark - NSTextFinderClient SPI 173 174 - (void)replaceMatches:(NSArray *)matches withString:(NSString *)replacementText inSelectionOnly:(BOOL)selectionOnly resultCollector:(void (^)(NSUInteger replacementCount))resultCollector 175 { 176 Vector<uint32_t> matchIndices; 177 matchIndices.reserveCapacity(matches.count); 178 for (id match in matches) { 179 if ([match isKindOfClass:WKTextFinderMatch.class]) 180 matchIndices.uncheckedAppend([(WKTextFinderMatch *)match index]); 181 } 182 _page->replaceMatches(WTFMove(matchIndices), replacementText, selectionOnly, [collector = makeBlockPtr(resultCollector)] (uint64_t numberOfReplacements, auto error) { 183 collector(error == WebKit::CallbackBase::Error::None ? numberOfReplacements : 0); 184 }); 185 } 173 186 174 187 - (void)findMatchesForString:(NSString *)targetString relativeToMatch:(id <NSTextFinderAsynchronousDocumentFindMatch>)relativeMatch findOptions:(NSTextFinderAsynchronousDocumentFindOptions)findOptions maxResults:(NSUInteger)maxResults resultCollector:(void (^)(NSArray *matches, BOOL didWrap))resultCollector -
trunk/Source/WebKit/WebProcess/InjectedBundle/API/c/WKBundlePage.cpp
r237266 r238438 450 450 } 451 451 452 void WKBundlePageFindStringMatches(WKBundlePageRef pageRef, WKStringRef target, WKFindOptions findOptions) 453 { 454 toImpl(pageRef)->findStringMatchesFromInjectedBundle(toWTFString(target), toFindOptions(findOptions)); 455 } 456 457 void WKBundlePageReplaceStringMatches(WKBundlePageRef pageRef, WKArrayRef matchIndicesRef, WKStringRef replacementText, bool selectionOnly) 458 { 459 auto* matchIndices = toImpl(matchIndicesRef); 460 461 Vector<uint32_t> indices; 462 indices.reserveInitialCapacity(matchIndices->size()); 463 464 for (size_t arrayIndex = 0; arrayIndex < matchIndices->size(); ++arrayIndex) { 465 if (auto* indexAsObject = matchIndices->at<API::UInt64>(arrayIndex)) 466 indices.uncheckedAppend(indexAsObject->value()); 467 } 468 toImpl(pageRef)->replaceStringMatchesFromInjectedBundle(WTFMove(indices), toWTFString(replacementText), selectionOnly); 469 } 470 452 471 WKImageRef WKBundlePageCreateSnapshotWithOptions(WKBundlePageRef pageRef, WKRect rect, WKSnapshotOptions options) 453 472 { -
trunk/Source/WebKit/WebProcess/InjectedBundle/API/c/WKBundlePage.h
r237205 r238438 93 93 94 94 WK_EXPORT bool WKBundlePageFindString(WKBundlePageRef page, WKStringRef target, WKFindOptions findOptions); 95 WK_EXPORT void WKBundlePageFindStringMatches(WKBundlePageRef page, WKStringRef target, WKFindOptions findOptions); 96 WK_EXPORT void WKBundlePageReplaceStringMatches(WKBundlePageRef page, WKArrayRef matchIndices, WKStringRef replacementText, bool selectionOnly); 95 97 96 98 WK_EXPORT WKImageRef WKBundlePageCreateSnapshotWithOptions(WKBundlePageRef page, WKRect rect, WKSnapshotOptions options); -
trunk/Source/WebKit/WebProcess/WebPage/FindController.cpp
r237565 r238438 100 100 } 101 101 102 uint32_t FindController::replaceMatches(Vector<uint32_t>&& matchIndices, const String& replacementText, bool selectionOnly) 103 { 104 if (matchIndices.isEmpty()) 105 return m_webPage->corePage()->replaceSelectionWithText(replacementText); 106 107 // FIXME: This is an arbitrary cap on the maximum number of matches to try and replace, to prevent the web process from 108 // hanging while replacing an enormous amount of matches. In the future, we should handle replacement in batches, and 109 // periodically update an NSProgress in the UI process when a batch of find-in-page matches are replaced. 110 const uint32_t maximumNumberOfMatchesToReplace = 1000; 111 112 Vector<Ref<Range>> rangesToReplace; 113 rangesToReplace.reserveCapacity(std::min<uint32_t>(maximumNumberOfMatchesToReplace, matchIndices.size())); 114 for (auto index : matchIndices) { 115 if (index < m_findMatches.size()) 116 rangesToReplace.uncheckedAppend(*m_findMatches[index]); 117 if (rangesToReplace.size() >= maximumNumberOfMatchesToReplace) 118 break; 119 } 120 return m_webPage->corePage()->replaceRangesWithText(WTFMove(rangesToReplace), replacementText, selectionOnly); 121 } 122 102 123 static Frame* frameWithSelection(Page* page) 103 124 { -
trunk/Source/WebKit/WebProcess/WebPage/FindController.h
r237565 r238438 62 62 void hideFindUI(); 63 63 void countStringMatches(const String&, FindOptions, unsigned maxMatchCount); 64 uint32_t replaceMatches(Vector<uint32_t>&& matchIndices, const String& replacementText, bool selectionOnly); 64 65 65 66 void hideFindIndicator(); -
trunk/Source/WebKit/WebProcess/WebPage/WebPage.cpp
r238330 r238438 3792 3792 } 3793 3793 3794 void WebPage::findStringMatchesFromInjectedBundle(const String& target, FindOptions options) 3795 { 3796 findController().findStringMatches(target, options, 0); 3797 } 3798 3799 void WebPage::replaceStringMatchesFromInjectedBundle(Vector<uint32_t>&& matchIndices, const String& replacementText, bool selectionOnly) 3800 { 3801 findController().replaceMatches(WTFMove(matchIndices), replacementText, selectionOnly); 3802 } 3803 3794 3804 void WebPage::findString(const String& string, uint32_t options, uint32_t maxMatchCount) 3795 3805 { … … 3820 3830 { 3821 3831 findController().countStringMatches(string, static_cast<FindOptions>(options), maxMatchCount); 3832 } 3833 3834 void WebPage::replaceMatches(Vector<uint32_t>&& matchIndices, const String& replacementText, bool selectionOnly, CallbackID callbackID) 3835 { 3836 auto numberOfReplacements = findController().replaceMatches(WTFMove(matchIndices), replacementText, selectionOnly); 3837 send(Messages::WebPageProxy::UnsignedCallback(numberOfReplacements, callbackID)); 3822 3838 } 3823 3839 -
trunk/Source/WebKit/WebProcess/WebPage/WebPage.h
r238330 r238438 409 409 410 410 bool findStringFromInjectedBundle(const String&, FindOptions); 411 void findStringMatchesFromInjectedBundle(const String&, FindOptions); 412 void replaceStringMatchesFromInjectedBundle(Vector<uint32_t>&& matchIndices, const String& replacementText, bool selectionOnly); 411 413 412 414 WebFrame* mainWebFrame() const { return m_mainFrame.get(); } … … 1305 1307 void hideFindUI(); 1306 1308 void countStringMatches(const String&, uint32_t findOptions, uint32_t maxMatchCount); 1309 void replaceMatches(Vector<uint32_t>&& matchIndices, const String& replacementText, bool selectionOnly, CallbackID); 1307 1310 1308 1311 #if USE(COORDINATED_GRAPHICS) -
trunk/Source/WebKit/WebProcess/WebPage/WebPage.messages.in
r238330 r238438 267 267 HideFindUI() 268 268 CountStringMatches(String string, uint32_t findOptions, unsigned maxMatchCount) 269 ReplaceMatches(Vector<uint32_t> matchIndices, String replacementText, bool selectionOnly, WebKit::CallbackID callbackID) 269 270 270 271 AddMIMETypeWithCustomContentProvider(String mimeType) -
trunk/Tools/ChangeLog
r238430 r238438 1 2018-11-21 Wenson Hsieh <wenson_hsieh@apple.com> 2 3 [Cocoa] [WebKit2] Add support for replacing find-in-page text matches 4 https://bugs.webkit.org/show_bug.cgi?id=191786 5 <rdar://problem/45813871> 6 7 Reviewed by Ryosuke Niwa. 8 9 * MiniBrowser/mac/WK2BrowserWindowController.m: 10 (-[WK2BrowserWindowController setFindBarView:]): 11 12 Fix a bug in MiniBrowser that prevents AppKit from displaying the "All" button in the find bar after checking 13 the "Replace" option. 14 15 * TestWebKitAPI/Tests/WebKitCocoa/FindInPage.mm: 16 17 Add an API test to exercise find-and-replace API using WKWebView. 18 19 (replaceMatches): 20 (TEST): 21 * WebKitTestRunner/InjectedBundle/Bindings/TestRunner.idl: 22 * WebKitTestRunner/InjectedBundle/TestRunner.cpp: 23 (WTR::findOptionsFromArray): 24 (WTR::TestRunner::findString): 25 (WTR::TestRunner::findStringMatchesInPage): 26 (WTR::TestRunner::replaceFindMatchesAtIndices): 27 28 Add TestRunner hooks to simulate find-in-page and replace. 29 30 * WebKitTestRunner/InjectedBundle/TestRunner.h: 31 1 32 2018-11-21 Zalan Bujtas <zalan@apple.com> 2 33 -
trunk/Tools/MiniBrowser/mac/WK2BrowserWindowController.m
r236913 r238438 764 764 - (void)setFindBarView:(NSView *)findBarView 765 765 { 766 if (_textFindBarView)767 [_textFindBarView removeFromSuperview];768 766 _textFindBarView = findBarView; 769 767 _findBarVisible = YES; 770 [containerView addSubview:_textFindBarView];771 768 [_textFindBarView setFrame:NSMakeRect(0, 0, containerView.bounds.size.width, _textFindBarView.frame.size.height)]; 772 769 } -
trunk/Tools/TestWebKitAPI/Tests/WebKitCocoa/FindInPage.mm
r237266 r238438 28 28 #import "PlatformUtilities.h" 29 29 #import "TestNavigationDelegate.h" 30 #import "TestWKWebView.h" 30 31 #import <WebKit/WKWebViewPrivate.h> 31 32 #import <wtf/RetainPtr.h> … … 53 54 54 55 - (void)findMatchesForString:(NSString *)targetString relativeToMatch:(FindMatch)relativeMatch findOptions:(NSTextFinderAsynchronousDocumentFindOptions)findOptions maxResults:(NSUInteger)maxResults resultCollector:(void (^)(NSArray *matches, BOOL didWrap))resultCollector; 56 - (void)replaceMatches:(NSArray<FindMatch> *)matches withString:(NSString *)replacementString inSelectionOnly:(BOOL)selectionOnly resultCollector:(void (^)(NSUInteger replacementCount))resultCollector; 55 57 56 58 @end … … 74 76 TestWebKitAPI::Util::run(&done); 75 77 78 return result; 79 } 80 81 static NSUInteger replaceMatches(WKWebView *webView, NSArray<FindMatch> *matchesToReplace, NSString *replacementText) 82 { 83 __block NSUInteger result; 84 __block bool done = false; 85 86 [webView replaceMatches:matchesToReplace withString:replacementText inSelectionOnly:NO resultCollector:^(NSUInteger replacementCount) { 87 result = replacementCount; 88 done = true; 89 }]; 90 91 TestWebKitAPI::Util::run(&done); 76 92 return result; 77 93 } … … 206 222 } 207 223 208 #endif 224 TEST(WebKit, FindAndReplace) 225 { 226 auto webView = adoptNS([[TestWKWebView alloc] initWithFrame:NSMakeRect(0, 0, 400, 400)]); 227 [webView synchronouslyLoadHTMLString:@"<body contenteditable><input id='first' value='hello'>hello world<input id='second' value='world'></body>"]; 228 229 auto result = findMatches(webView.get(), @"hello"); 230 EXPECT_EQ(2U, [result.matches count]); 231 EXPECT_EQ(2U, replaceMatches(webView.get(), result.matches.get(), @"hi")); 232 EXPECT_WK_STREQ("hi", [webView stringByEvaluatingJavaScript:@"first.value"]); 233 EXPECT_WK_STREQ("world", [webView stringByEvaluatingJavaScript:@"second.value"]); 234 EXPECT_WK_STREQ("hi world", [webView stringByEvaluatingJavaScript:@"document.body.textContent"]); 235 236 result = findMatches(webView.get(), @"world"); 237 EXPECT_EQ(2U, [result.matches count]); 238 EXPECT_EQ(1U, replaceMatches(webView.get(), @[ [result.matches firstObject] ], @"hi")); 239 EXPECT_WK_STREQ("hi", [webView stringByEvaluatingJavaScript:@"first.value"]); 240 EXPECT_WK_STREQ("world", [webView stringByEvaluatingJavaScript:@"second.value"]); 241 EXPECT_WK_STREQ("hi hi", [webView stringByEvaluatingJavaScript:@"document.body.textContent"]); 242 243 result = findMatches(webView.get(), @"world"); 244 EXPECT_EQ(1U, [result.matches count]); 245 EXPECT_EQ(1U, replaceMatches(webView.get(), @[ [result.matches firstObject] ], @"hi")); 246 EXPECT_WK_STREQ("hi", [webView stringByEvaluatingJavaScript:@"first.value"]); 247 EXPECT_WK_STREQ("hi", [webView stringByEvaluatingJavaScript:@"second.value"]); 248 EXPECT_WK_STREQ("hi hi", [webView stringByEvaluatingJavaScript:@"document.body.textContent"]); 249 } 250 251 #endif // WK_API_ENABLED && !PLATFORM(IOS_FAMILY) -
trunk/Tools/WebKitTestRunner/InjectedBundle/Bindings/TestRunner.idl
r237905 r238438 147 147 // Text search testing. 148 148 boolean findString(DOMString target, object optionsArray); 149 void findStringMatchesInPage(DOMString target, object optionsArray); 150 void replaceFindMatchesAtIndices(object matchIndicesArray, DOMString replacementText, boolean selectionOnly); 149 151 150 152 // Evaluating script in a special context. -
trunk/Tools/WebKitTestRunner/InjectedBundle/TestRunner.cpp
r238166 r238438 46 46 #include <WebKit/WKBundleScriptWorld.h> 47 47 #include <WebKit/WKData.h> 48 #include <WebKit/WKNumber.h> 48 49 #include <WebKit/WKPagePrivate.h> 49 50 #include <WebKit/WKRetainPtr.h> … … 51 52 #include <WebKit/WebKit2_C.h> 52 53 #include <wtf/HashMap.h> 54 #include <wtf/Optional.h> 53 55 #include <wtf/StdLibExtras.h> 54 56 #include <wtf/text/CString.h> … … 300 302 } 301 303 302 bool TestRunner::findString(JSStringRef target, JSValueRef optionsArrayAsValue) 303 { 304 WKFindOptions options = 0; 305 304 static std::optional<WKFindOptions> findOptionsFromArray(JSValueRef optionsArrayAsValue) 305 { 306 306 auto& injectedBundle = InjectedBundle::singleton(); 307 307 WKBundleFrameRef mainFrame = WKBundlePageGetMainFrame(injectedBundle.page()->page()); … … 311 311 JSValueRef lengthValue = JSObjectGetProperty(context, optionsArray, lengthPropertyName.get(), 0); 312 312 if (!JSValueIsNumber(context, lengthValue)) 313 return false; 314 313 return std::nullopt; 314 315 WKFindOptions options = 0; 315 316 size_t length = static_cast<size_t>(JSValueToNumber(context, lengthValue, 0)); 316 317 for (size_t i = 0; i < length; ++i) { … … 335 336 } 336 337 } 337 338 return WKBundlePageFindString(injectedBundle.page()->page(), toWK(target).get(), options); 338 return options; 339 } 340 341 bool TestRunner::findString(JSStringRef target, JSValueRef optionsArrayAsValue) 342 { 343 if (auto options = findOptionsFromArray(optionsArrayAsValue)) 344 return WKBundlePageFindString(InjectedBundle::singleton().page()->page(), toWK(target).get(), *options); 345 346 return false; 347 } 348 349 void TestRunner::findStringMatchesInPage(JSStringRef target, JSValueRef optionsArrayAsValue) 350 { 351 if (auto options = findOptionsFromArray(optionsArrayAsValue)) 352 return WKBundlePageFindStringMatches(InjectedBundle::singleton().page()->page(), toWK(target).get(), *options); 353 } 354 355 void TestRunner::replaceFindMatchesAtIndices(JSValueRef matchIndicesAsValue, JSStringRef replacementText, bool selectionOnly) 356 { 357 auto& bundle = InjectedBundle::singleton(); 358 auto mainFrame = WKBundlePageGetMainFrame(bundle.page()->page()); 359 auto context = WKBundleFrameGetJavaScriptContext(mainFrame); 360 auto lengthPropertyName = adopt(JSStringCreateWithUTF8CString("length")); 361 auto matchIndicesObject = JSValueToObject(context, matchIndicesAsValue, 0); 362 auto lengthValue = JSObjectGetProperty(context, matchIndicesObject, lengthPropertyName.get(), 0); 363 if (!JSValueIsNumber(context, lengthValue)) 364 return; 365 366 auto indices = adoptWK(WKMutableArrayCreate()); 367 auto length = static_cast<size_t>(JSValueToNumber(context, lengthValue, 0)); 368 for (size_t i = 0; i < length; ++i) { 369 auto value = JSObjectGetPropertyAtIndex(context, matchIndicesObject, i, 0); 370 if (!JSValueIsNumber(context, value)) 371 continue; 372 373 auto index = adoptWK(WKUInt64Create(std::round(JSValueToNumber(context, value, nullptr)))); 374 WKArrayAppendItem(indices.get(), index.get()); 375 } 376 WKBundlePageReplaceStringMatches(bundle.page()->page(), indices.get(), toWK(replacementText).get(), selectionOnly); 339 377 } 340 378 -
trunk/Tools/WebKitTestRunner/InjectedBundle/TestRunner.h
r238098 r238438 155 155 // Text search testing. 156 156 bool findString(JSStringRef, JSValueRef optionsArray); 157 void findStringMatchesInPage(JSStringRef, JSValueRef optionsArray); 158 void replaceFindMatchesAtIndices(JSValueRef matchIndices, JSStringRef replacementText, bool selectionOnly); 157 159 158 160 // Local storage
Note: See TracChangeset
for help on using the changeset viewer.