https://bugs.webkit.org/show_bug.cgi?id=66584
[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 "ShareableBitmap.h"
30 #include "WKPage.h"
31 #include "WebCoreArgumentCoders.h"
32 #include "WebPage.h"
33 #include "WebPageProxyMessages.h"
34 #include "WebProcess.h"
35 #include <WebCore/DocumentMarkerController.h>
36 #include <WebCore/FocusController.h>
37 #include <WebCore/Frame.h>
38 #include <WebCore/FrameView.h>
39 #include <WebCore/GraphicsContext.h>
40 #include <WebCore/Page.h>
41
42 using namespace std;
43 using namespace WebCore;
44
45 namespace WebKit {
46
47 static WebCore::FindOptions core(FindOptions options)
48 {
49     return (options & FindOptionsCaseInsensitive ? CaseInsensitive : 0)
50         | (options & FindOptionsAtWordStarts ? AtWordStarts : 0)
51         | (options & FindOptionsTreatMedialCapitalAsWordStart ? TreatMedialCapitalAsWordStart : 0)
52         | (options & FindOptionsBackwards ? Backwards : 0)
53         | (options & FindOptionsWrapAround ? WrapAround : 0);
54 }
55
56 FindController::FindController(WebPage* webPage)
57     : m_webPage(webPage)
58     , m_findPageOverlay(0)
59     , m_isShowingFindIndicator(false)
60 {
61 }
62
63 FindController::~FindController()
64 {
65 }
66
67 void FindController::countStringMatches(const String& string, FindOptions options, unsigned maxMatchCount)
68 {
69     if (maxMatchCount == numeric_limits<unsigned>::max())
70         --maxMatchCount;
71     
72     unsigned matchCount = m_webPage->corePage()->markAllMatchesForText(string, core(options), false, maxMatchCount + 1);
73     m_webPage->corePage()->unmarkAllTextMatches();
74
75     // Check if we have more matches than allowed.
76     if (matchCount > maxMatchCount)
77         matchCount = static_cast<unsigned>(kWKMoreThanMaximumMatchCount);
78     
79     m_webPage->send(Messages::WebPageProxy::DidCountStringMatches(string, matchCount));
80 }
81
82 static Frame* frameWithSelection(Page* page)
83 {
84     for (Frame* frame = page->mainFrame(); frame; frame = frame->tree()->traverseNext()) {
85         if (frame->selection()->isRange())
86             return frame;
87     }
88
89     return 0;
90 }
91
92 void FindController::findString(const String& string, FindOptions options, unsigned maxMatchCount)
93 {
94     m_webPage->corePage()->unmarkAllTextMatches();
95
96     bool found = m_webPage->corePage()->findString(string, core(options));
97
98     Frame* selectedFrame = frameWithSelection(m_webPage->corePage());
99
100     bool shouldShowOverlay = false;
101
102     if (!found) {
103         // Clear the selection.
104         if (selectedFrame)
105             selectedFrame->selection()->clear();
106
107         hideFindIndicator();
108
109         m_webPage->send(Messages::WebPageProxy::DidFailToFindString(string));
110     } else {
111         shouldShowOverlay = options & FindOptionsShowOverlay;
112
113         if (shouldShowOverlay) {
114             if (maxMatchCount == numeric_limits<unsigned>::max())
115                 --maxMatchCount;
116             
117             unsigned matchCount = m_webPage->corePage()->markAllMatchesForText(string, core(options), false, maxMatchCount + 1);
118
119             // Check if we have more matches than allowed.
120             if (matchCount > maxMatchCount) {
121                 shouldShowOverlay = false;
122                 matchCount = static_cast<unsigned>(kWKMoreThanMaximumMatchCount);
123             }
124
125             m_webPage->send(Messages::WebPageProxy::DidFindString(string, matchCount));
126         }
127
128         if (!(options & FindOptionsShowFindIndicator) || !updateFindIndicator(selectedFrame, shouldShowOverlay)) {
129             // Either we shouldn't show the find indicator, or we couldn't update it.
130             hideFindIndicator();
131         }
132     }
133
134     if (!shouldShowOverlay) {
135         if (m_findPageOverlay) {
136             // Get rid of the overlay.
137             m_webPage->uninstallPageOverlay(m_findPageOverlay, false);
138         }
139         
140         ASSERT(!m_findPageOverlay);
141         return;
142     }
143
144     if (!m_findPageOverlay) {
145         RefPtr<PageOverlay> findPageOverlay = PageOverlay::create(this);
146         m_findPageOverlay = findPageOverlay.get();
147         m_webPage->installPageOverlay(findPageOverlay.release());
148     } else {
149         // The page overlay needs to be repainted.
150         m_findPageOverlay->setNeedsDisplay();
151     }
152 }
153
154 void FindController::hideFindUI()
155 {
156     if (m_findPageOverlay)
157         m_webPage->uninstallPageOverlay(m_findPageOverlay, false);
158
159     hideFindIndicator();
160 }
161
162 bool FindController::updateFindIndicator(Frame* selectedFrame, bool isShowingOverlay, bool shouldAnimate)
163 {
164     if (!selectedFrame)
165         return false;
166
167     IntRect selectionRect = enclosingIntRect(selectedFrame->selection()->bounds());
168     
169     // Selection rect can be empty for matches that are currently obscured from view.
170     if (selectionRect.isEmpty())
171         return false;
172
173     // We want the selection rect in window coordinates.
174     IntRect selectionRectInWindowCoordinates = selectedFrame->view()->contentsToWindow(selectionRect);
175     
176     Vector<FloatRect> textRects;
177     selectedFrame->selection()->getClippedVisibleTextRectangles(textRects);
178
179     IntSize backingStoreSize = selectionRect.size();
180     backingStoreSize.scale(m_webPage->corePage()->deviceScaleFactor());
181
182     // Create a backing store and paint the find indicator text into it.
183     RefPtr<ShareableBitmap> findIndicatorTextBackingStore = ShareableBitmap::createShareable(backingStoreSize, ShareableBitmap::SupportsAlpha);
184     if (!findIndicatorTextBackingStore)
185         return false;
186     
187     OwnPtr<GraphicsContext> graphicsContext = findIndicatorTextBackingStore->createGraphicsContext();
188     graphicsContext->scale(FloatSize(m_webPage->corePage()->deviceScaleFactor(), m_webPage->corePage()->deviceScaleFactor()));
189
190     IntRect paintRect = selectionRect;
191     paintRect.move(selectedFrame->view()->frameRect().x(), selectedFrame->view()->frameRect().y());
192     paintRect.move(-selectedFrame->view()->scrollOffset());
193
194     graphicsContext->translate(-paintRect.x(), -paintRect.y());
195     selectedFrame->view()->setPaintBehavior(PaintBehaviorSelectionOnly | PaintBehaviorForceBlackText | PaintBehaviorFlattenCompositingLayers);
196     selectedFrame->document()->updateLayout();
197
198     selectedFrame->view()->paint(graphicsContext.get(), paintRect);
199     selectedFrame->view()->setPaintBehavior(PaintBehaviorNormal);
200     
201     ShareableBitmap::Handle handle;
202     if (!findIndicatorTextBackingStore->createHandle(handle))
203         return false;
204
205     // We want the text rects in selection rect coordinates.
206     Vector<FloatRect> textRectsInSelectionRectCoordinates;
207     
208     for (size_t i = 0; i < textRects.size(); ++i) {
209         IntRect textRectInSelectionRectCoordinates = selectedFrame->view()->contentsToWindow(enclosingIntRect(textRects[i]));
210         textRectInSelectionRectCoordinates.move(-selectionRectInWindowCoordinates.x(), -selectionRectInWindowCoordinates.y());
211
212         textRectsInSelectionRectCoordinates.append(textRectInSelectionRectCoordinates);
213     }            
214
215     m_webPage->send(Messages::WebPageProxy::SetFindIndicator(selectionRectInWindowCoordinates, textRectsInSelectionRectCoordinates, m_webPage->corePage()->deviceScaleFactor(), handle, !isShowingOverlay, shouldAnimate));
216     m_isShowingFindIndicator = true;
217
218     return true;
219 }
220
221 void FindController::hideFindIndicator()
222 {
223     if (!m_isShowingFindIndicator)
224         return;
225
226     ShareableBitmap::Handle handle;
227     m_webPage->send(Messages::WebPageProxy::SetFindIndicator(FloatRect(), Vector<FloatRect>(), m_webPage->corePage()->deviceScaleFactor(), handle, false, true));
228     m_isShowingFindIndicator = false;
229 }
230
231 void FindController::showFindIndicatorInSelection()
232 {
233     Frame* selectedFrame = m_webPage->corePage()->focusController()->focusedOrMainFrame();
234     if (!selectedFrame)
235         return;
236     
237     updateFindIndicator(selectedFrame, false);
238 }
239
240 void FindController::deviceScaleFactorDidChange()
241 {
242     ASSERT(isShowingOverlay());
243
244     Frame* selectedFrame = frameWithSelection(m_webPage->corePage());
245     if (!selectedFrame)
246         return;
247
248     updateFindIndicator(selectedFrame, true, false);
249 }
250
251 Vector<IntRect> FindController::rectsForTextMatches()
252 {
253     Vector<IntRect> rects;
254
255     for (Frame* frame = m_webPage->corePage()->mainFrame(); frame; frame = frame->tree()->traverseNext()) {
256         Document* document = frame->document();
257         if (!document)
258             continue;
259
260         IntRect visibleRect = frame->view()->visibleContentRect();
261         Vector<IntRect> frameRects = document->markers()->renderedRectsForMarkers(DocumentMarker::TextMatch);
262         IntPoint frameOffset(-frame->view()->scrollOffset().width(), -frame->view()->scrollOffset().height());
263         frameOffset = frame->view()->convertToContainingWindow(frameOffset);
264
265         for (Vector<IntRect>::iterator it = frameRects.begin(), end = frameRects.end(); it != end; ++it) {
266             it->intersect(visibleRect);
267             it->move(frameOffset.x(), frameOffset.y());
268             rects.append(*it);
269         }
270     }
271
272     return rects;
273 }
274
275 void FindController::pageOverlayDestroyed(PageOverlay*)
276 {
277 }
278
279 void FindController::willMoveToWebPage(PageOverlay*, WebPage* webPage)
280 {
281     if (webPage)
282         return;
283
284     // The page overlay is moving away from the web page, reset it.
285     ASSERT(m_findPageOverlay);
286     m_findPageOverlay = 0;
287 }
288     
289 void FindController::didMoveToWebPage(PageOverlay*, WebPage*)
290 {
291 }
292
293 static const float shadowOffsetX = 0.0;
294 static const float shadowOffsetY = 1.0;
295 static const float shadowBlurRadius = 2.0;
296 static const float whiteFrameThickness = 1.0;
297
298 static const float overlayBackgroundRed = 0.1;
299 static const float overlayBackgroundGreen = 0.1;
300 static const float overlayBackgroundBlue = 0.1;
301 static const float overlayBackgroundAlpha = 0.25;
302
303 static Color overlayBackgroundColor(float fractionFadedIn)
304 {
305     return Color(overlayBackgroundRed, overlayBackgroundGreen, overlayBackgroundBlue, overlayBackgroundAlpha * fractionFadedIn);
306 }
307
308 static Color holeShadowColor(float fractionFadedIn)
309 {
310     return Color(0.0f, 0.0f, 0.0f, fractionFadedIn);
311 }
312
313 static Color holeFillColor(float fractionFadedIn)
314 {
315     return Color(1.0f, 1.0f, 1.0f, fractionFadedIn);
316 }
317
318 void FindController::drawRect(PageOverlay* pageOverlay, GraphicsContext& graphicsContext, const IntRect& dirtyRect)
319 {
320     float fractionFadedIn = pageOverlay->fractionFadedIn();
321
322     Vector<IntRect> rects = rectsForTextMatches();
323
324     // Draw the background.
325     graphicsContext.fillRect(dirtyRect, overlayBackgroundColor(fractionFadedIn), ColorSpaceSRGB);
326
327     {
328         GraphicsContextStateSaver stateSaver(graphicsContext);
329
330         graphicsContext.setShadow(FloatSize(shadowOffsetX, shadowOffsetY), shadowBlurRadius, holeShadowColor(fractionFadedIn), ColorSpaceSRGB);
331         graphicsContext.setFillColor(holeFillColor(fractionFadedIn), ColorSpaceSRGB);
332
333         // Draw white frames around the holes.
334         for (size_t i = 0; i < rects.size(); ++i) {
335             IntRect whiteFrameRect = rects[i];
336             whiteFrameRect.inflate(1);
337
338             graphicsContext.fillRect(whiteFrameRect);
339         }
340     }
341
342     graphicsContext.setFillColor(Color::transparent, ColorSpaceSRGB);
343
344     // Clear out the holes.
345     for (size_t i = 0; i < rects.size(); ++i)
346         graphicsContext.fillRect(rects[i]);
347 }
348
349 bool FindController::mouseEvent(PageOverlay* pageOverlay, const WebMouseEvent& mouseEvent)
350 {
351     // If we get a mouse down event inside the page overlay we should hide the find UI.
352     if (mouseEvent.type() == WebEvent::MouseDown) {
353         // Dismiss the overlay.
354         hideFindUI();
355     }
356
357     return false;
358 }
359
360 } // namespace WebKit