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
41using namespace std;
42using namespace WebCore;
43
44namespace WebKit {
45
46static 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
55FindController::FindController(WebPage* webPage)
56    : m_webPage(webPage)
57    , m_findPageOverlay(0)
58    , m_isShowingFindIndicator(false)
59{
60}
61
62FindController::~FindController()
63{
64}
65
66void 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
81static 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
91void 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
153void FindController::hideFindUI()
154{
155    if (m_findPageOverlay)
156        m_webPage->uninstallPageOverlay(m_findPageOverlay, true);
157
158    hideFindIndicator();
159}
160
161bool 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
216void 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
226Vector<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
250void FindController::pageOverlayDestroyed(PageOverlay*)
251{
252}
253
254void 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
264void FindController::didMoveToWebPage(PageOverlay*, WebPage*)
265{
266}
267
268static const float shadowOffsetX = 0.0;
269static const float shadowOffsetY = 1.0;
270static const float shadowBlurRadius = 2.0;
271static const float whiteFrameThickness = 1.0;
272
273static const float overlayBackgroundRed = 0.1;
274static const float overlayBackgroundGreen = 0.1;
275static const float overlayBackgroundBlue = 0.1;
276static const float overlayBackgroundAlpha = 0.25;
277
278static Color overlayBackgroundColor(float fractionFadedIn)
279{
280    return Color(overlayBackgroundRed, overlayBackgroundGreen, overlayBackgroundBlue, overlayBackgroundAlpha * fractionFadedIn);
281}
282
283static Color holeShadowColor(float fractionFadedIn)
284{
285    return Color(0.0f, 0.0f, 0.0f, fractionFadedIn);
286}
287
288static Color holeFillColor(float fractionFadedIn)
289{
290    return Color(1.0f, 1.0f, 1.0f, fractionFadedIn);
291}
292
293void 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    graphicsContext.save();
303    graphicsContext.setShadow(FloatSize(shadowOffsetX, shadowOffsetY), shadowBlurRadius, holeShadowColor(fractionFadedIn), ColorSpaceSRGB);
304
305    graphicsContext.setFillColor(holeFillColor(fractionFadedIn), ColorSpaceSRGB);
306
307    // Draw white frames around the holes.
308    for (size_t i = 0; i < rects.size(); ++i) {
309        IntRect whiteFrameRect = rects[i];
310        whiteFrameRect.inflate(1);
311
312        graphicsContext.fillRect(whiteFrameRect);
313    }
314
315    graphicsContext.restore();
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
324bool 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