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