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