d797c2efde943c7516019c6d8bc47a2359948bc8
[WebKit-https.git] / Source / WebKit / WebProcess / WebPage / FindController.cpp
1 /*
2  * Copyright (C) 2010, 2015 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 "FindController.h"
28
29 #include "DrawingArea.h"
30 #include "PluginView.h"
31 #include "ShareableBitmap.h"
32 #include "WKPage.h"
33 #include "WebCoreArgumentCoders.h"
34 #include "WebPage.h"
35 #include "WebPageProxyMessages.h"
36 #include <WebCore/DocumentMarkerController.h>
37 #include <WebCore/FloatQuad.h>
38 #include <WebCore/FocusController.h>
39 #include <WebCore/Frame.h>
40 #include <WebCore/FrameSelection.h>
41 #include <WebCore/FrameView.h>
42 #include <WebCore/GraphicsContext.h>
43 #include <WebCore/Page.h>
44 #include <WebCore/PageOverlayController.h>
45 #include <WebCore/PathUtilities.h>
46 #include <WebCore/PlatformMouseEvent.h>
47 #include <WebCore/PluginDocument.h>
48
49 #if PLATFORM(COCOA)
50 #include <WebCore/TextIndicatorWindow.h>
51 #endif
52
53 namespace WebKit {
54 using namespace WebCore;
55
56 WebCore::FindOptions core(FindOptions options)
57 {
58     WebCore::FindOptions result;
59     if (options & FindOptionsCaseInsensitive)
60         result.add(WebCore::CaseInsensitive);
61     if (options & FindOptionsAtWordStarts)
62         result.add(WebCore::AtWordStarts);
63     if (options & FindOptionsTreatMedialCapitalAsWordStart)
64         result.add(WebCore::TreatMedialCapitalAsWordStart);
65     if (options & FindOptionsBackwards)
66         result.add(WebCore::Backwards);
67     if (options & FindOptionsWrapAround)
68         result.add(WebCore::WrapAround);
69     return result;
70 }
71
72 FindController::FindController(WebPage* webPage)
73     : m_webPage(webPage)
74 {
75 }
76
77 FindController::~FindController()
78 {
79 }
80
81 void FindController::countStringMatches(const String& string, FindOptions options, unsigned maxMatchCount)
82 {
83     if (maxMatchCount == std::numeric_limits<unsigned>::max())
84         --maxMatchCount;
85     
86     auto* pluginView = WebPage::pluginViewForFrame(m_webPage->mainFrame());
87     
88     unsigned matchCount;
89     if (pluginView)
90         matchCount = pluginView->countFindMatches(string, core(options), maxMatchCount + 1);
91     else {
92         matchCount = m_webPage->corePage()->countFindMatches(string, core(options), maxMatchCount + 1);
93         m_webPage->corePage()->unmarkAllTextMatches();
94     }
95
96     if (matchCount > maxMatchCount)
97         matchCount = static_cast<unsigned>(kWKMoreThanMaximumMatchCount);
98     
99     m_webPage->send(Messages::WebPageProxy::DidCountStringMatches(string, matchCount));
100 }
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
123 static Frame* frameWithSelection(Page* page)
124 {
125     for (Frame* frame = &page->mainFrame(); frame; frame = frame->tree().traverseNext()) {
126         if (frame->selection().isRange())
127             return frame;
128     }
129
130     return 0;
131 }
132
133 void FindController::updateFindUIAfterPageScroll(bool found, const String& string, FindOptions options, unsigned maxMatchCount, DidWrap didWrap)
134 {
135     Frame* selectedFrame = frameWithSelection(m_webPage->corePage());
136     
137     auto* pluginView = WebPage::pluginViewForFrame(m_webPage->mainFrame());
138
139     bool shouldShowOverlay = false;
140
141     if (!found) {
142         if (!pluginView)
143             m_webPage->corePage()->unmarkAllTextMatches();
144
145         if (selectedFrame)
146             selectedFrame->selection().clear();
147
148         hideFindIndicator();
149         didFailToFindString();
150
151         m_webPage->send(Messages::WebPageProxy::DidFailToFindString(string));
152     } else {
153         shouldShowOverlay = options & FindOptionsShowOverlay;
154         bool shouldShowHighlight = options & FindOptionsShowHighlight;
155         bool shouldDetermineMatchIndex = options & FindOptionsDetermineMatchIndex;
156         unsigned matchCount = 1;
157
158         if (shouldDetermineMatchIndex) {
159             if (pluginView)
160                 matchCount = pluginView->countFindMatches(string, core(options), maxMatchCount + 1);
161             else
162                 matchCount = m_webPage->corePage()->countFindMatches(string, core(options), maxMatchCount + 1);
163         }
164
165         if (shouldShowOverlay || shouldShowHighlight) {
166             if (maxMatchCount == std::numeric_limits<unsigned>::max())
167                 --maxMatchCount;
168
169             if (pluginView) {
170                 if (!shouldDetermineMatchIndex)
171                     matchCount = pluginView->countFindMatches(string, core(options), maxMatchCount + 1);
172                 shouldShowOverlay = false;
173             } else {
174                 m_webPage->corePage()->unmarkAllTextMatches();
175                 matchCount = m_webPage->corePage()->markAllMatchesForText(string, core(options), shouldShowHighlight, maxMatchCount + 1);
176             }
177
178             // If we have a large number of matches, we don't want to take the time to paint the overlay.
179             if (matchCount > maxMatchCount) {
180                 shouldShowOverlay = false;
181                 matchCount = static_cast<unsigned>(kWKMoreThanMaximumMatchCount);
182             }
183         }
184         if (matchCount == static_cast<unsigned>(kWKMoreThanMaximumMatchCount))
185             m_foundStringMatchIndex = -1;
186         else {
187             if (m_foundStringMatchIndex < 0)
188                 m_foundStringMatchIndex += matchCount; // FIXME: Shouldn't this just be "="? Why is it correct to add to -1 here?
189             if (m_foundStringMatchIndex >= (int) matchCount)
190                 m_foundStringMatchIndex -= matchCount;
191         }
192
193         m_findMatches.clear();
194         Vector<IntRect> matchRects;
195         if (auto range = m_webPage->corePage()->selection().firstRange()) {
196             range->absoluteTextRects(matchRects);
197             m_findMatches.append(range);
198         }
199
200         m_webPage->send(Messages::WebPageProxy::DidFindString(string, matchRects, matchCount, m_foundStringMatchIndex, didWrap == DidWrap::Yes));
201     }
202
203     if (!shouldShowOverlay) {
204         if (m_findPageOverlay)
205             m_webPage->corePage()->pageOverlayController().uninstallPageOverlay(*m_findPageOverlay, PageOverlay::FadeMode::Fade);
206     } else {
207         if (!m_findPageOverlay) {
208             auto findPageOverlay = PageOverlay::create(*this, PageOverlay::OverlayType::Document);
209             m_findPageOverlay = findPageOverlay.ptr();
210             m_webPage->corePage()->pageOverlayController().installPageOverlay(WTFMove(findPageOverlay), PageOverlay::FadeMode::Fade);
211         }
212         m_findPageOverlay->setNeedsDisplay();
213     }
214     
215     if (found && (!(options & FindOptionsShowFindIndicator) || !selectedFrame || !updateFindIndicator(*selectedFrame, shouldShowOverlay)))
216         hideFindIndicator();
217 }
218
219 void FindController::findString(const String& string, FindOptions options, unsigned maxMatchCount)
220 {
221     auto* pluginView = WebPage::pluginViewForFrame(m_webPage->mainFrame());
222
223     WebCore::FindOptions coreOptions = core(options);
224
225     // iOS will reveal the selection through a different mechanism, and
226     // we need to avoid sending the non-painted selection change to the UI process
227     // so that it does not clear the selection out from under us.
228 #if PLATFORM(IOS_FAMILY)
229     coreOptions.add(DoNotRevealSelection);
230 #endif
231
232     willFindString();
233
234     bool foundStringStartsAfterSelection = false;
235     if (!pluginView) {
236         if (Frame* selectedFrame = frameWithSelection(m_webPage->corePage())) {
237             FrameSelection& fs = selectedFrame->selection();
238             if (fs.selectionBounds().isEmpty()) {
239                 m_findMatches.clear();
240                 int indexForSelection;
241                 m_webPage->corePage()->findStringMatchingRanges(string, coreOptions, maxMatchCount, m_findMatches, indexForSelection);
242                 m_foundStringMatchIndex = indexForSelection;
243                 foundStringStartsAfterSelection = true;
244             }
245         }
246     }
247
248     m_findMatches.clear();
249
250     bool found;
251     DidWrap didWrap = DidWrap::No;
252     if (pluginView)
253         found = pluginView->findString(string, coreOptions, maxMatchCount);
254     else
255         found = m_webPage->corePage()->findString(string, coreOptions, &didWrap);
256
257     if (found) {
258         didFindString();
259
260         if (!foundStringStartsAfterSelection) {
261             if (options & FindOptionsBackwards)
262                 m_foundStringMatchIndex--;
263             else
264                 m_foundStringMatchIndex++;
265         }
266     }
267
268     RefPtr<WebPage> protectedWebPage = m_webPage;
269     m_webPage->drawingArea()->dispatchAfterEnsuringUpdatedScrollPosition([protectedWebPage, found, string, options, maxMatchCount, didWrap] () {
270         protectedWebPage->findController().updateFindUIAfterPageScroll(found, string, options, maxMatchCount, didWrap);
271     });
272 }
273
274 void FindController::findStringMatches(const String& string, FindOptions options, unsigned maxMatchCount)
275 {
276     m_findMatches.clear();
277     int indexForSelection;
278
279     m_webPage->corePage()->findStringMatchingRanges(string, core(options), maxMatchCount, m_findMatches, indexForSelection);
280
281     Vector<Vector<IntRect>> matchRects;
282     for (size_t i = 0; i < m_findMatches.size(); ++i) {
283         Vector<IntRect> rects;
284         m_findMatches[i]->absoluteTextRects(rects);
285         matchRects.append(WTFMove(rects));
286     }
287
288     m_webPage->send(Messages::WebPageProxy::DidFindStringMatches(string, matchRects, indexForSelection));
289 }
290
291 void FindController::getImageForFindMatch(uint32_t matchIndex)
292 {
293     if (matchIndex >= m_findMatches.size())
294         return;
295     Frame* frame = m_findMatches[matchIndex]->startContainer().document().frame();
296     if (!frame)
297         return;
298
299     VisibleSelection oldSelection = frame->selection().selection();
300     frame->selection().setSelection(VisibleSelection(*m_findMatches[matchIndex]));
301
302     RefPtr<ShareableBitmap> selectionSnapshot = WebFrame::fromCoreFrame(*frame)->createSelectionSnapshot();
303
304     frame->selection().setSelection(oldSelection);
305
306     if (!selectionSnapshot)
307         return;
308
309     ShareableBitmap::Handle handle;
310     selectionSnapshot->createHandle(handle);
311
312     if (handle.isNull())
313         return;
314
315     m_webPage->send(Messages::WebPageProxy::DidGetImageForFindMatch(handle, matchIndex));
316 }
317
318 void FindController::selectFindMatch(uint32_t matchIndex)
319 {
320     if (matchIndex >= m_findMatches.size())
321         return;
322     Frame* frame = m_findMatches[matchIndex]->startContainer().document().frame();
323     if (!frame)
324         return;
325     frame->selection().setSelection(VisibleSelection(*m_findMatches[matchIndex]));
326 }
327
328 void FindController::hideFindUI()
329 {
330     m_findMatches.clear();
331     if (m_findPageOverlay)
332         m_webPage->corePage()->pageOverlayController().uninstallPageOverlay(*m_findPageOverlay, PageOverlay::FadeMode::Fade);
333
334     if (auto* pluginView = WebPage::pluginViewForFrame(m_webPage->mainFrame()))
335         pluginView->findString(emptyString(), { }, 0);
336     else
337         m_webPage->corePage()->unmarkAllTextMatches();
338     
339     hideFindIndicator();
340 }
341
342 #if !PLATFORM(IOS_FAMILY)
343
344 bool FindController::updateFindIndicator(Frame& selectedFrame, bool isShowingOverlay, bool shouldAnimate)
345 {
346     RefPtr<TextIndicator> indicator = TextIndicator::createWithSelectionInFrame(selectedFrame, TextIndicatorOptionIncludeMarginIfRangeMatchesSelection, shouldAnimate ? TextIndicatorPresentationTransition::Bounce : TextIndicatorPresentationTransition::None);
347     if (!indicator)
348         return false;
349
350     m_findIndicatorRect = enclosingIntRect(indicator->selectionRectInRootViewCoordinates());
351 #if PLATFORM(COCOA)
352     m_webPage->send(Messages::WebPageProxy::SetTextIndicator(indicator->data(), static_cast<uint64_t>(isShowingOverlay ? TextIndicatorWindowLifetime::Permanent : TextIndicatorWindowLifetime::Temporary)));
353 #endif
354     m_isShowingFindIndicator = true;
355
356     return true;
357 }
358
359 void FindController::hideFindIndicator()
360 {
361     if (!m_isShowingFindIndicator)
362         return;
363
364     m_webPage->send(Messages::WebPageProxy::ClearTextIndicator());
365     m_isShowingFindIndicator = false;
366     m_foundStringMatchIndex = -1;
367     didHideFindIndicator();
368 }
369
370 void FindController::willFindString()
371 {
372 }
373
374 void FindController::didFindString()
375 {
376 }
377
378 void FindController::didFailToFindString()
379 {
380 }
381
382 void FindController::didHideFindIndicator()
383 {
384 }
385     
386 unsigned FindController::findIndicatorRadius() const
387 {
388     return 0;
389 }
390     
391 bool FindController::shouldHideFindIndicatorOnScroll() const
392 {
393     return true;
394 }
395
396 #endif
397
398 void FindController::showFindIndicatorInSelection()
399 {
400     Frame& selectedFrame = m_webPage->corePage()->focusController().focusedOrMainFrame();
401     updateFindIndicator(selectedFrame, false);
402 }
403
404 void FindController::deviceScaleFactorDidChange()
405 {
406     ASSERT(isShowingOverlay());
407
408     Frame* selectedFrame = frameWithSelection(m_webPage->corePage());
409     if (!selectedFrame)
410         return;
411
412     updateFindIndicator(*selectedFrame, true, false);
413 }
414
415 void FindController::redraw()
416 {
417     if (!m_isShowingFindIndicator)
418         return;
419
420     Frame* selectedFrame = frameWithSelection(m_webPage->corePage());
421     if (!selectedFrame)
422         return;
423
424     updateFindIndicator(*selectedFrame, isShowingOverlay(), false);
425 }
426
427 Vector<FloatRect> FindController::rectsForTextMatchesInRect(IntRect clipRect)
428 {
429     Vector<FloatRect> rects;
430
431     FrameView* mainFrameView = m_webPage->corePage()->mainFrame().view();
432
433     for (Frame* frame = &m_webPage->corePage()->mainFrame(); frame; frame = frame->tree().traverseNext()) {
434         Document* document = frame->document();
435         if (!document)
436             continue;
437
438         for (FloatRect rect : document->markers().renderedRectsForMarkers(DocumentMarker::TextMatch)) {
439             if (!frame->isMainFrame())
440                 rect = mainFrameView->windowToContents(frame->view()->contentsToWindow(enclosingIntRect(rect)));
441
442             if (rect.isEmpty() || !rect.intersects(clipRect))
443                 continue;
444
445             rects.append(rect);
446         }
447     }
448
449     return rects;
450 }
451
452 void FindController::willMoveToPage(PageOverlay&, Page* page)
453 {
454     if (page)
455         return;
456
457     ASSERT(m_findPageOverlay);
458     m_findPageOverlay = 0;
459 }
460     
461 void FindController::didMoveToPage(PageOverlay&, Page*)
462 {
463 }
464
465 const float shadowOffsetX = 0;
466 const float shadowOffsetY = 0;
467 const float shadowBlurRadius = 1;
468
469 void FindController::drawRect(PageOverlay&, GraphicsContext& graphicsContext, const IntRect& dirtyRect)
470 {
471     const int borderWidth = 1;
472
473     Color overlayBackgroundColor(0.1f, 0.1f, 0.1f, 0.25f);
474     Color shadowColor(0.0f, 0.0f, 0.0f, 0.5f);
475
476     IntRect borderInflatedDirtyRect = dirtyRect;
477     borderInflatedDirtyRect.inflate(borderWidth);
478     Vector<FloatRect> rects = rectsForTextMatchesInRect(borderInflatedDirtyRect);
479
480     // Draw the background.
481     graphicsContext.fillRect(dirtyRect, overlayBackgroundColor);
482
483     Vector<Path> whiteFramePaths = PathUtilities::pathsWithShrinkWrappedRects(rects, findIndicatorRadius());
484
485     GraphicsContextStateSaver stateSaver(graphicsContext);
486
487     // Draw white frames around the holes.
488     // We double the thickness because half of the stroke will be erased when we clear the holes.
489     graphicsContext.setShadow(FloatSize(shadowOffsetX, shadowOffsetY), shadowBlurRadius, shadowColor);
490     graphicsContext.setStrokeColor(Color::white);
491     graphicsContext.setStrokeThickness(borderWidth * 2);
492     for (auto& path : whiteFramePaths)
493         graphicsContext.strokePath(path);
494
495     graphicsContext.clearShadow();
496
497     // Clear out the holes.
498     graphicsContext.setCompositeOperation(CompositeClear);
499     for (auto& path : whiteFramePaths)
500         graphicsContext.fillPath(path);
501
502     if (!m_isShowingFindIndicator || !shouldHideFindIndicatorOnScroll())
503         return;
504
505     if (Frame* selectedFrame = frameWithSelection(m_webPage->corePage())) {
506         IntRect findIndicatorRect = selectedFrame->view()->contentsToRootView(enclosingIntRect(selectedFrame->selection().selectionBounds()));
507
508         if (findIndicatorRect != m_findIndicatorRect)
509             hideFindIndicator();
510     }
511 }
512
513 bool FindController::mouseEvent(PageOverlay&, const PlatformMouseEvent& mouseEvent)
514 {
515     if (mouseEvent.type() == PlatformEvent::MousePressed)
516         hideFindUI();
517
518     return false;
519 }
520
521 void FindController::didInvalidateDocumentMarkerRects()
522 {
523     if (m_findPageOverlay)
524         m_findPageOverlay->setNeedsDisplay();
525 }
526
527 } // namespace WebKit