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