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