blob: 27b9c6333023ba897af6815f4b2671ca9458173d [file] [log] [blame]
Torne (Richard Coles)d5428f32014-03-18 10:21:16 +00001/*
2 * Copyright (C) 2009 Google 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 are
6 * met:
7 *
8 * * Redistributions of source code must retain the above copyright
9 * notice, this list of conditions and the following disclaimer.
10 * * Redistributions in binary form must reproduce the above
11 * copyright notice, this list of conditions and the following disclaimer
12 * in the documentation and/or other materials provided with the
13 * distribution.
14 * * Neither the name of Google Inc. nor the names of its
15 * contributors may be used to endorse or promote products derived from
16 * this software without specific prior written permission.
17 *
18 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
19 * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
20 * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
21 * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
22 * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
23 * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
24 * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
25 * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
26 * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
27 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
28 * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
29 */
30
Torne (Richard Coles)d5428f32014-03-18 10:21:16 +000031
32#include "config.h"
33#include "TextFinder.h"
34
Torne (Richard Coles)d5428f32014-03-18 10:21:16 +000035#include "FindInPageCoordinates.h"
Torne (Richard Coles)d5428f32014-03-18 10:21:16 +000036#include "WebFindOptions.h"
Torne (Richard Coles)d5428f32014-03-18 10:21:16 +000037#include "WebFrameClient.h"
Ben Murdoch07a852d2014-03-31 11:51:52 +010038#include "WebFrameImpl.h"
39#include "WebViewClient.h"
Torne (Richard Coles)d5428f32014-03-18 10:21:16 +000040#include "WebViewImpl.h"
Torne (Richard Coles)d5428f32014-03-18 10:21:16 +000041#include "core/dom/DocumentMarker.h"
42#include "core/dom/DocumentMarkerController.h"
Ben Murdoch07a852d2014-03-31 11:51:52 +010043#include "core/dom/Range.h"
Torne (Richard Coles)d5428f32014-03-18 10:21:16 +000044#include "core/dom/shadow/ShadowRoot.h"
45#include "core/editing/Editor.h"
Torne (Richard Coles)d5428f32014-03-18 10:21:16 +000046#include "core/editing/TextIterator.h"
Ben Murdoch07a852d2014-03-31 11:51:52 +010047#include "core/editing/VisibleSelection.h"
Torne (Richard Coles)d5428f32014-03-18 10:21:16 +000048#include "core/frame/FrameView.h"
Ben Murdoch07a852d2014-03-31 11:51:52 +010049#include "platform/Timer.h"
Torne (Richard Coles)d5428f32014-03-18 10:21:16 +000050#include "public/platform/WebVector.h"
51#include "wtf/CurrentTime.h"
Torne (Richard Coles)d5428f32014-03-18 10:21:16 +000052
53using namespace WebCore;
54
55namespace blink {
56
Ben Murdoch07a852d2014-03-31 11:51:52 +010057TextFinder::FindMatch::FindMatch(PassRefPtr<Range> range, int ordinal)
Torne (Richard Coles)d5428f32014-03-18 10:21:16 +000058 : m_range(range)
59 , m_ordinal(ordinal)
60{
61}
62
Ben Murdoch07a852d2014-03-31 11:51:52 +010063class TextFinder::DeferredScopeStringMatches {
Torne (Richard Coles)d5428f32014-03-18 10:21:16 +000064public:
Ben Murdoch07a852d2014-03-31 11:51:52 +010065 DeferredScopeStringMatches(TextFinder* textFinder, int identifier, const WebString& searchText, const WebFindOptions& options, bool reset)
Torne (Richard Coles)d5428f32014-03-18 10:21:16 +000066 : m_timer(this, &DeferredScopeStringMatches::doTimeout)
Ben Murdoch07a852d2014-03-31 11:51:52 +010067 , m_textFinder(textFinder)
Torne (Richard Coles)d5428f32014-03-18 10:21:16 +000068 , m_identifier(identifier)
69 , m_searchText(searchText)
70 , m_options(options)
71 , m_reset(reset)
72 {
Ben Murdoch07a852d2014-03-31 11:51:52 +010073 m_timer.startOneShot(0.0, FROM_HERE);
Torne (Richard Coles)d5428f32014-03-18 10:21:16 +000074 }
75
76private:
77 void doTimeout(Timer<DeferredScopeStringMatches>*)
78 {
Ben Murdoch07a852d2014-03-31 11:51:52 +010079 m_textFinder->callScopeStringMatches(this, m_identifier, m_searchText, m_options, m_reset);
Torne (Richard Coles)d5428f32014-03-18 10:21:16 +000080 }
81
82 Timer<DeferredScopeStringMatches> m_timer;
Ben Murdoch07a852d2014-03-31 11:51:52 +010083 TextFinder* m_textFinder;
84 const int m_identifier;
85 const WebString m_searchText;
86 const WebFindOptions m_options;
87 const bool m_reset;
Torne (Richard Coles)d5428f32014-03-18 10:21:16 +000088};
89
Ben Murdoch07a852d2014-03-31 11:51:52 +010090bool TextFinder::find(int identifier, const WebString& searchText, const WebFindOptions& options, bool wrapWithinFrame, WebRect* selectionRect)
Torne (Richard Coles)d5428f32014-03-18 10:21:16 +000091{
Ben Murdoch07a852d2014-03-31 11:51:52 +010092 if (!m_ownerFrame.frame() || !m_ownerFrame.frame()->page())
Torne (Richard Coles)d5428f32014-03-18 10:21:16 +000093 return false;
94
Ben Murdoch07a852d2014-03-31 11:51:52 +010095 WebFrameImpl* mainFrameImpl = m_ownerFrame.viewImpl()->mainFrameImpl();
Torne (Richard Coles)d5428f32014-03-18 10:21:16 +000096
97 if (!options.findNext)
Ben Murdoch07a852d2014-03-31 11:51:52 +010098 m_ownerFrame.frame()->page()->unmarkAllTextMatches();
Torne (Richard Coles)d5428f32014-03-18 10:21:16 +000099 else
100 setMarkerActive(m_activeMatch.get(), false);
101
Ben Murdoch07a852d2014-03-31 11:51:52 +0100102 if (m_activeMatch && &m_activeMatch->ownerDocument() != m_ownerFrame.frame()->document())
Torne (Richard Coles)d5428f32014-03-18 10:21:16 +0000103 m_activeMatch = nullptr;
104
105 // If the user has selected something since the last Find operation we want
106 // to start from there. Otherwise, we start searching from where the last Find
107 // operation left off (either a Find or a FindNext operation).
Ben Murdoch07a852d2014-03-31 11:51:52 +0100108 VisibleSelection selection(m_ownerFrame.frame()->selection().selection());
Torne (Richard Coles)d5428f32014-03-18 10:21:16 +0000109 bool activeSelection = !selection.isNone();
110 if (activeSelection) {
111 m_activeMatch = selection.firstRange().get();
Ben Murdoch07a852d2014-03-31 11:51:52 +0100112 m_ownerFrame.frame()->selection().clear();
Torne (Richard Coles)d5428f32014-03-18 10:21:16 +0000113 }
114
Ben Murdoch07a852d2014-03-31 11:51:52 +0100115 ASSERT(m_ownerFrame.frame() && m_ownerFrame.frame()->view());
Torne (Richard Coles)d5428f32014-03-18 10:21:16 +0000116 const FindOptions findOptions = (options.forward ? 0 : Backwards)
117 | (options.matchCase ? 0 : CaseInsensitive)
118 | (wrapWithinFrame ? WrapAround : 0)
119 | (options.wordStart ? AtWordStarts : 0)
120 | (options.medialCapitalAsWordStart ? TreatMedialCapitalAsWordStart : 0)
121 | (options.findNext ? 0 : StartInSelection);
Ben Murdoch07a852d2014-03-31 11:51:52 +0100122 m_activeMatch = m_ownerFrame.frame()->editor().findStringAndScrollToVisible(searchText, m_activeMatch.get(), findOptions);
Torne (Richard Coles)d5428f32014-03-18 10:21:16 +0000123
124 if (!m_activeMatch) {
125 // If we're finding next the next active match might not be in the current frame.
126 // In this case we don't want to clear the matches cache.
127 if (!options.findNext)
128 clearFindMatchesCache();
Ben Murdoch07a852d2014-03-31 11:51:52 +0100129
130 m_ownerFrame.invalidateAll();
Torne (Richard Coles)d5428f32014-03-18 10:21:16 +0000131 return false;
132 }
133
134#if OS(ANDROID)
Ben Murdoch07a852d2014-03-31 11:51:52 +0100135 m_ownerFrame.viewImpl()->zoomToFindInPageRect(m_ownerFrame.frameView()->contentsToWindow(enclosingIntRect(RenderObject::absoluteBoundingBoxRectForRange(m_activeMatch.get()))));
Torne (Richard Coles)d5428f32014-03-18 10:21:16 +0000136#endif
137
138 setMarkerActive(m_activeMatch.get(), true);
Ben Murdoch07a852d2014-03-31 11:51:52 +0100139 WebFrameImpl* oldActiveFrame = mainFrameImpl->ensureTextFinder().m_currentActiveMatchFrame;
140 mainFrameImpl->ensureTextFinder().m_currentActiveMatchFrame = &m_ownerFrame;
Torne (Richard Coles)d5428f32014-03-18 10:21:16 +0000141
142 // Make sure no node is focused. See http://crbug.com/38700.
Ben Murdoch07a852d2014-03-31 11:51:52 +0100143 m_ownerFrame.frame()->document()->setFocusedElement(nullptr);
Torne (Richard Coles)d5428f32014-03-18 10:21:16 +0000144
145 if (!options.findNext || activeSelection) {
146 // This is either a Find operation or a Find-next from a new start point
147 // due to a selection, so we set the flag to ask the scoping effort
148 // to find the active rect for us and report it back to the UI.
149 m_locatingActiveRect = true;
150 } else {
Ben Murdoch07a852d2014-03-31 11:51:52 +0100151 if (oldActiveFrame != &m_ownerFrame) {
Torne (Richard Coles)d5428f32014-03-18 10:21:16 +0000152 if (options.forward)
153 m_activeMatchIndexInCurrentFrame = 0;
154 else
155 m_activeMatchIndexInCurrentFrame = m_lastMatchCount - 1;
156 } else {
157 if (options.forward)
158 ++m_activeMatchIndexInCurrentFrame;
159 else
160 --m_activeMatchIndexInCurrentFrame;
161
162 if (m_activeMatchIndexInCurrentFrame + 1 > m_lastMatchCount)
163 m_activeMatchIndexInCurrentFrame = 0;
164 if (m_activeMatchIndexInCurrentFrame == -1)
165 m_activeMatchIndexInCurrentFrame = m_lastMatchCount - 1;
166 }
167 if (selectionRect) {
Ben Murdoch07a852d2014-03-31 11:51:52 +0100168 *selectionRect = m_ownerFrame.frameView()->contentsToWindow(m_activeMatch->boundingBox());
Torne (Richard Coles)d5428f32014-03-18 10:21:16 +0000169 reportFindInPageSelection(*selectionRect, m_activeMatchIndexInCurrentFrame + 1, identifier);
170 }
171 }
172
173 return true;
174}
175
Ben Murdoch07a852d2014-03-31 11:51:52 +0100176void TextFinder::stopFindingAndClearSelection()
Torne (Richard Coles)d5428f32014-03-18 10:21:16 +0000177{
Torne (Richard Coles)d5428f32014-03-18 10:21:16 +0000178 cancelPendingScopingEffort();
179
180 // Remove all markers for matches found and turn off the highlighting.
Ben Murdoch07a852d2014-03-31 11:51:52 +0100181 m_ownerFrame.frame()->document()->markers().removeMarkers(DocumentMarker::TextMatch);
182 m_ownerFrame.frame()->editor().setMarkedTextMatchesAreHighlighted(false);
Torne (Richard Coles)d5428f32014-03-18 10:21:16 +0000183 clearFindMatchesCache();
184
185 // Let the frame know that we don't want tickmarks or highlighting anymore.
Ben Murdoch07a852d2014-03-31 11:51:52 +0100186 m_ownerFrame.invalidateAll();
Torne (Richard Coles)d5428f32014-03-18 10:21:16 +0000187}
188
Ben Murdoch07a852d2014-03-31 11:51:52 +0100189void TextFinder::scopeStringMatches(int identifier, const WebString& searchText, const WebFindOptions& options, bool reset)
Torne (Richard Coles)d5428f32014-03-18 10:21:16 +0000190{
191 if (reset) {
192 // This is a brand new search, so we need to reset everything.
193 // Scoping is just about to begin.
194 m_scopingInProgress = true;
195
196 // Need to keep the current identifier locally in order to finish the
197 // request in case the frame is detached during the process.
198 m_findRequestIdentifier = identifier;
199
200 // Clear highlighting for this frame.
Ben Murdoch07a852d2014-03-31 11:51:52 +0100201 LocalFrame* frame = m_ownerFrame.frame();
202 if (frame && frame->page() && frame->editor().markedTextMatchesAreHighlighted())
203 frame->page()->unmarkAllTextMatches();
Torne (Richard Coles)d5428f32014-03-18 10:21:16 +0000204
205 // Clear the tickmarks and results cache.
206 clearFindMatchesCache();
207
208 // Clear the counters from last operation.
209 m_lastMatchCount = 0;
210 m_nextInvalidateAfter = 0;
Torne (Richard Coles)d5428f32014-03-18 10:21:16 +0000211 m_resumeScopingFromRange = nullptr;
212
213 // The view might be null on detached frames.
Ben Murdoch07a852d2014-03-31 11:51:52 +0100214 if (frame && frame->page())
215 m_ownerFrame.viewImpl()->mainFrameImpl()->ensureTextFinder().m_framesScopingCount++;
Torne (Richard Coles)d5428f32014-03-18 10:21:16 +0000216
217 // Now, defer scoping until later to allow find operation to finish quickly.
218 scopeStringMatchesSoon(identifier, searchText, options, false); // false means just reset, so don't do it again.
219 return;
220 }
221
222 if (!shouldScopeMatches(searchText)) {
223 // Note that we want to defer the final update when resetting even if shouldScopeMatches returns false.
224 // This is done in order to prevent sending a final message based only on the results of the first frame
225 // since m_framesScopingCount would be 0 as other frames have yet to reset.
226 finishCurrentScopingEffort(identifier);
227 return;
228 }
229
Ben Murdoch07a852d2014-03-31 11:51:52 +0100230 WebFrameImpl* mainFrameImpl = m_ownerFrame.viewImpl()->mainFrameImpl();
231 RefPtr<Range> searchRange(rangeOfContents(m_ownerFrame.frame()->document()));
Torne (Richard Coles)d5428f32014-03-18 10:21:16 +0000232
233 Node* originalEndContainer = searchRange->endContainer();
234 int originalEndOffset = searchRange->endOffset();
235
236 TrackExceptionState exceptionState, exceptionState2;
237 if (m_resumeScopingFromRange) {
238 // This is a continuation of a scoping operation that timed out and didn't
239 // complete last time around, so we should start from where we left off.
240 searchRange->setStart(m_resumeScopingFromRange->startContainer(), m_resumeScopingFromRange->startOffset(exceptionState2) + 1, exceptionState);
241 if (exceptionState.hadException() || exceptionState2.hadException()) {
242 if (exceptionState2.hadException()) // A non-zero |exceptionState| happens when navigating during search.
243 ASSERT_NOT_REACHED();
244 return;
245 }
246 }
247
Ben Murdoch07a852d2014-03-31 11:51:52 +0100248 // This timeout controls how long we scope before releasing control. This
Torne (Richard Coles)d5428f32014-03-18 10:21:16 +0000249 // value does not prevent us from running for longer than this, but it is
250 // periodically checked to see if we have exceeded our allocated time.
251 const double maxScopingDuration = 0.1; // seconds
252
253 int matchCount = 0;
254 bool timedOut = false;
255 double startTime = currentTime();
256 do {
257 // Find next occurrence of the search string.
258 // FIXME: (http://b/1088245) This WebKit operation may run for longer
259 // than the timeout value, and is not interruptible as it is currently
260 // written. We may need to rewrite it with interruptibility in mind, or
261 // find an alternative.
Ben Murdoch07a852d2014-03-31 11:51:52 +0100262 RefPtr<Range> resultRange(findPlainText(
263 searchRange.get(), searchText, options.matchCase ? 0 : CaseInsensitive));
Torne (Richard Coles)d5428f32014-03-18 10:21:16 +0000264 if (resultRange->collapsed(exceptionState)) {
265 if (!resultRange->startContainer()->isInShadowTree())
266 break;
267
268 searchRange->setStartAfter(
269 resultRange->startContainer()->deprecatedShadowAncestorNode(), exceptionState);
270 searchRange->setEnd(originalEndContainer, originalEndOffset, exceptionState);
271 continue;
272 }
273
274 ++matchCount;
275
276 // Catch a special case where Find found something but doesn't know what
277 // the bounding box for it is. In this case we set the first match we find
278 // as the active rect.
279 IntRect resultBounds = resultRange->boundingBox();
280 IntRect activeSelectionRect;
281 if (m_locatingActiveRect) {
282 activeSelectionRect = m_activeMatch.get() ?
283 m_activeMatch->boundingBox() : resultBounds;
284 }
285
286 // If the Find function found a match it will have stored where the
287 // match was found in m_activeSelectionRect on the current frame. If we
288 // find this rect during scoping it means we have found the active
289 // tickmark.
290 bool foundActiveMatch = false;
291 if (m_locatingActiveRect && (activeSelectionRect == resultBounds)) {
292 // We have found the active tickmark frame.
Ben Murdoch07a852d2014-03-31 11:51:52 +0100293 mainFrameImpl->ensureTextFinder().m_currentActiveMatchFrame = &m_ownerFrame;
Torne (Richard Coles)d5428f32014-03-18 10:21:16 +0000294 foundActiveMatch = true;
295 // We also know which tickmark is active now.
296 m_activeMatchIndexInCurrentFrame = matchCount - 1;
297 // To stop looking for the active tickmark, we set this flag.
298 m_locatingActiveRect = false;
299
300 // Notify browser of new location for the selected rectangle.
301 reportFindInPageSelection(
Ben Murdoch07a852d2014-03-31 11:51:52 +0100302 m_ownerFrame.frameView()->contentsToWindow(resultBounds),
Torne (Richard Coles)d5428f32014-03-18 10:21:16 +0000303 m_activeMatchIndexInCurrentFrame + 1,
304 identifier);
305 }
306
307 addMarker(resultRange.get(), foundActiveMatch);
308
309 m_findMatchesCache.append(FindMatch(resultRange.get(), m_lastMatchCount + matchCount));
310
311 // Set the new start for the search range to be the end of the previous
312 // result range. There is no need to use a VisiblePosition here,
313 // since findPlainText will use a TextIterator to go over the visible
314 // text nodes.
315 searchRange->setStart(resultRange->endContainer(exceptionState), resultRange->endOffset(exceptionState), exceptionState);
316
317 Node* shadowTreeRoot = searchRange->shadowRoot();
318 if (searchRange->collapsed(exceptionState) && shadowTreeRoot)
319 searchRange->setEnd(shadowTreeRoot, shadowTreeRoot->countChildren(), exceptionState);
320
321 m_resumeScopingFromRange = resultRange;
322 timedOut = (currentTime() - startTime) >= maxScopingDuration;
323 } while (!timedOut);
324
325 // Remember what we search for last time, so we can skip searching if more
326 // letters are added to the search string (and last outcome was 0).
327 m_lastSearchString = searchText;
328
329 if (matchCount > 0) {
Ben Murdoch07a852d2014-03-31 11:51:52 +0100330 m_ownerFrame.frame()->editor().setMarkedTextMatchesAreHighlighted(true);
Torne (Richard Coles)d5428f32014-03-18 10:21:16 +0000331
332 m_lastMatchCount += matchCount;
333
334 // Let the mainframe know how much we found during this pass.
335 mainFrameImpl->increaseMatchCount(matchCount, identifier);
336 }
337
338 if (timedOut) {
339 // If we found anything during this pass, we should redraw. However, we
340 // don't want to spam too much if the page is extremely long, so if we
341 // reach a certain point we start throttling the redraw requests.
342 if (matchCount > 0)
343 invalidateIfNecessary();
344
345 // Scoping effort ran out of time, lets ask for another time-slice.
346 scopeStringMatchesSoon(
347 identifier,
348 searchText,
349 options,
350 false); // don't reset.
351 return; // Done for now, resume work later.
352 }
353
354 finishCurrentScopingEffort(identifier);
355}
356
Ben Murdoch07a852d2014-03-31 11:51:52 +0100357void TextFinder::flushCurrentScopingEffort(int identifier)
Torne (Richard Coles)d5428f32014-03-18 10:21:16 +0000358{
Ben Murdoch07a852d2014-03-31 11:51:52 +0100359 if (!m_ownerFrame.frame() || !m_ownerFrame.frame()->page())
Torne (Richard Coles)d5428f32014-03-18 10:21:16 +0000360 return;
361
Ben Murdoch07a852d2014-03-31 11:51:52 +0100362 WebFrameImpl* mainFrameImpl = m_ownerFrame.viewImpl()->mainFrameImpl();
363 mainFrameImpl->ensureTextFinder().decrementFramesScopingCount(identifier);
Torne (Richard Coles)d5428f32014-03-18 10:21:16 +0000364}
365
Ben Murdoch07a852d2014-03-31 11:51:52 +0100366void TextFinder::finishCurrentScopingEffort(int identifier)
Torne (Richard Coles)d5428f32014-03-18 10:21:16 +0000367{
368 flushCurrentScopingEffort(identifier);
369
370 m_scopingInProgress = false;
371 m_lastFindRequestCompletedWithNoMatches = !m_lastMatchCount;
372
373 // This frame is done, so show any scrollbar tickmarks we haven't drawn yet.
Ben Murdoch07a852d2014-03-31 11:51:52 +0100374 m_ownerFrame.invalidateScrollbar();
Torne (Richard Coles)d5428f32014-03-18 10:21:16 +0000375}
376
Ben Murdoch07a852d2014-03-31 11:51:52 +0100377void TextFinder::cancelPendingScopingEffort()
Torne (Richard Coles)d5428f32014-03-18 10:21:16 +0000378{
379 deleteAllValues(m_deferredScopingWork);
380 m_deferredScopingWork.clear();
381
382 m_activeMatchIndexInCurrentFrame = -1;
383
384 // Last request didn't complete.
385 if (m_scopingInProgress)
386 m_lastFindRequestCompletedWithNoMatches = false;
387
388 m_scopingInProgress = false;
389}
390
Ben Murdoch07a852d2014-03-31 11:51:52 +0100391void TextFinder::increaseMatchCount(int identifier, int count)
Torne (Richard Coles)d5428f32014-03-18 10:21:16 +0000392{
Torne (Richard Coles)d5428f32014-03-18 10:21:16 +0000393 if (count)
394 ++m_findMatchMarkersVersion;
395
396 m_totalMatchCount += count;
397
398 // Update the UI with the latest findings.
Ben Murdoch07a852d2014-03-31 11:51:52 +0100399 if (m_ownerFrame.client())
400 m_ownerFrame.client()->reportFindInPageMatchCount(identifier, m_totalMatchCount, !m_framesScopingCount);
Torne (Richard Coles)d5428f32014-03-18 10:21:16 +0000401}
402
Ben Murdoch07a852d2014-03-31 11:51:52 +0100403void TextFinder::reportFindInPageSelection(const WebRect& selectionRect, int activeMatchOrdinal, int identifier)
Torne (Richard Coles)d5428f32014-03-18 10:21:16 +0000404{
405 // Update the UI with the latest selection rect.
Ben Murdoch07a852d2014-03-31 11:51:52 +0100406 if (m_ownerFrame.client())
407 m_ownerFrame.client()->reportFindInPageSelection(identifier, ordinalOfFirstMatch() + activeMatchOrdinal, selectionRect);
Torne (Richard Coles)d5428f32014-03-18 10:21:16 +0000408}
409
Ben Murdoch07a852d2014-03-31 11:51:52 +0100410void TextFinder::resetMatchCount()
Torne (Richard Coles)d5428f32014-03-18 10:21:16 +0000411{
412 if (m_totalMatchCount > 0)
413 ++m_findMatchMarkersVersion;
414
415 m_totalMatchCount = 0;
416 m_framesScopingCount = 0;
417}
418
Ben Murdoch07a852d2014-03-31 11:51:52 +0100419void TextFinder::clearFindMatchesCache()
Torne (Richard Coles)d5428f32014-03-18 10:21:16 +0000420{
421 if (!m_findMatchesCache.isEmpty())
Ben Murdoch07a852d2014-03-31 11:51:52 +0100422 m_ownerFrame.viewImpl()->mainFrameImpl()->ensureTextFinder().m_findMatchMarkersVersion++;
Torne (Richard Coles)d5428f32014-03-18 10:21:16 +0000423
424 m_findMatchesCache.clear();
425 m_findMatchRectsAreValid = false;
426}
427
Ben Murdoch07a852d2014-03-31 11:51:52 +0100428bool TextFinder::isActiveMatchFrameValid() const
Torne (Richard Coles)d5428f32014-03-18 10:21:16 +0000429{
Ben Murdoch07a852d2014-03-31 11:51:52 +0100430 WebFrameImpl* mainFrameImpl = m_ownerFrame.viewImpl()->mainFrameImpl();
Torne (Richard Coles)d5428f32014-03-18 10:21:16 +0000431 WebFrameImpl* activeMatchFrame = mainFrameImpl->activeMatchFrame();
Ben Murdoch07a852d2014-03-31 11:51:52 +0100432 return activeMatchFrame && activeMatchFrame->activeMatch() && activeMatchFrame->frame()->tree().isDescendantOf(mainFrameImpl->frame());
Torne (Richard Coles)d5428f32014-03-18 10:21:16 +0000433}
434
Ben Murdoch07a852d2014-03-31 11:51:52 +0100435void TextFinder::updateFindMatchRects()
Torne (Richard Coles)d5428f32014-03-18 10:21:16 +0000436{
Ben Murdoch07a852d2014-03-31 11:51:52 +0100437 IntSize currentContentsSize = m_ownerFrame.contentsSize();
Torne (Richard Coles)d5428f32014-03-18 10:21:16 +0000438 if (m_contentsSizeForCurrentFindMatchRects != currentContentsSize) {
439 m_contentsSizeForCurrentFindMatchRects = currentContentsSize;
440 m_findMatchRectsAreValid = false;
441 }
442
443 size_t deadMatches = 0;
444 for (Vector<FindMatch>::iterator it = m_findMatchesCache.begin(); it != m_findMatchesCache.end(); ++it) {
445 if (!it->m_range->boundaryPointsValid() || !it->m_range->startContainer()->inDocument())
446 it->m_rect = FloatRect();
447 else if (!m_findMatchRectsAreValid)
448 it->m_rect = findInPageRectFromRange(it->m_range.get());
449
450 if (it->m_rect.isEmpty())
451 ++deadMatches;
452 }
453
454 // Remove any invalid matches from the cache.
455 if (deadMatches) {
456 Vector<FindMatch> filteredMatches;
457 filteredMatches.reserveCapacity(m_findMatchesCache.size() - deadMatches);
458
Ben Murdoch07a852d2014-03-31 11:51:52 +0100459 for (Vector<FindMatch>::const_iterator it = m_findMatchesCache.begin(); it != m_findMatchesCache.end(); ++it) {
Torne (Richard Coles)d5428f32014-03-18 10:21:16 +0000460 if (!it->m_rect.isEmpty())
461 filteredMatches.append(*it);
Ben Murdoch07a852d2014-03-31 11:51:52 +0100462 }
Torne (Richard Coles)d5428f32014-03-18 10:21:16 +0000463
464 m_findMatchesCache.swap(filteredMatches);
465 }
466
467 // Invalidate the rects in child frames. Will be updated later during traversal.
468 if (!m_findMatchRectsAreValid)
Ben Murdoch07a852d2014-03-31 11:51:52 +0100469 for (WebFrame* child = m_ownerFrame.firstChild(); child; child = child->nextSibling())
470 toWebFrameImpl(child)->ensureTextFinder().m_findMatchRectsAreValid = false;
Torne (Richard Coles)d5428f32014-03-18 10:21:16 +0000471
472 m_findMatchRectsAreValid = true;
473}
474
Ben Murdoch07a852d2014-03-31 11:51:52 +0100475WebFloatRect TextFinder::activeFindMatchRect()
Torne (Richard Coles)d5428f32014-03-18 10:21:16 +0000476{
Torne (Richard Coles)d5428f32014-03-18 10:21:16 +0000477 if (!isActiveMatchFrameValid())
478 return WebFloatRect();
479
Ben Murdoch07a852d2014-03-31 11:51:52 +0100480 return WebFloatRect(findInPageRectFromRange(m_currentActiveMatchFrame->activeMatch()));
Torne (Richard Coles)d5428f32014-03-18 10:21:16 +0000481}
482
Ben Murdoch07a852d2014-03-31 11:51:52 +0100483void TextFinder::findMatchRects(WebVector<WebFloatRect>& outputRects)
Torne (Richard Coles)d5428f32014-03-18 10:21:16 +0000484{
Torne (Richard Coles)d5428f32014-03-18 10:21:16 +0000485 Vector<WebFloatRect> matchRects;
Ben Murdoch07a852d2014-03-31 11:51:52 +0100486 for (WebFrameImpl* frame = &m_ownerFrame; frame; frame = toWebFrameImpl(frame->traverseNext(false)))
487 frame->ensureTextFinder().appendFindMatchRects(matchRects);
Torne (Richard Coles)d5428f32014-03-18 10:21:16 +0000488
489 outputRects = matchRects;
490}
491
Ben Murdoch07a852d2014-03-31 11:51:52 +0100492void TextFinder::appendFindMatchRects(Vector<WebFloatRect>& frameRects)
Torne (Richard Coles)d5428f32014-03-18 10:21:16 +0000493{
494 updateFindMatchRects();
495 frameRects.reserveCapacity(frameRects.size() + m_findMatchesCache.size());
496 for (Vector<FindMatch>::const_iterator it = m_findMatchesCache.begin(); it != m_findMatchesCache.end(); ++it) {
497 ASSERT(!it->m_rect.isEmpty());
498 frameRects.append(it->m_rect);
499 }
500}
501
Ben Murdoch07a852d2014-03-31 11:51:52 +0100502int TextFinder::selectNearestFindMatch(const WebFloatPoint& point, WebRect* selectionRect)
Torne (Richard Coles)d5428f32014-03-18 10:21:16 +0000503{
Ben Murdoch07a852d2014-03-31 11:51:52 +0100504 TextFinder* bestFinder = 0;
Torne (Richard Coles)d5428f32014-03-18 10:21:16 +0000505 int indexInBestFrame = -1;
506 float distanceInBestFrame = FLT_MAX;
507
Ben Murdoch07a852d2014-03-31 11:51:52 +0100508 for (WebFrameImpl* frame = &m_ownerFrame; frame; frame = toWebFrameImpl(frame->traverseNext(false))) {
Torne (Richard Coles)d5428f32014-03-18 10:21:16 +0000509 float distanceInFrame;
Ben Murdoch07a852d2014-03-31 11:51:52 +0100510 TextFinder& finder = frame->ensureTextFinder();
511 int indexInFrame = finder.nearestFindMatch(point, distanceInFrame);
Torne (Richard Coles)d5428f32014-03-18 10:21:16 +0000512 if (distanceInFrame < distanceInBestFrame) {
Ben Murdoch07a852d2014-03-31 11:51:52 +0100513 bestFinder = &finder;
Torne (Richard Coles)d5428f32014-03-18 10:21:16 +0000514 indexInBestFrame = indexInFrame;
515 distanceInBestFrame = distanceInFrame;
516 }
517 }
518
519 if (indexInBestFrame != -1)
Ben Murdoch07a852d2014-03-31 11:51:52 +0100520 return bestFinder->selectFindMatch(static_cast<unsigned>(indexInBestFrame), selectionRect);
Torne (Richard Coles)d5428f32014-03-18 10:21:16 +0000521
522 return -1;
523}
524
Ben Murdoch07a852d2014-03-31 11:51:52 +0100525int TextFinder::nearestFindMatch(const FloatPoint& point, float& distanceSquared)
Torne (Richard Coles)d5428f32014-03-18 10:21:16 +0000526{
527 updateFindMatchRects();
528
529 int nearest = -1;
530 distanceSquared = FLT_MAX;
531 for (size_t i = 0; i < m_findMatchesCache.size(); ++i) {
532 ASSERT(!m_findMatchesCache[i].m_rect.isEmpty());
533 FloatSize offset = point - m_findMatchesCache[i].m_rect.center();
534 float width = offset.width();
535 float height = offset.height();
536 float currentDistanceSquared = width * width + height * height;
537 if (currentDistanceSquared < distanceSquared) {
538 nearest = i;
539 distanceSquared = currentDistanceSquared;
540 }
541 }
542 return nearest;
543}
544
Ben Murdoch07a852d2014-03-31 11:51:52 +0100545int TextFinder::selectFindMatch(unsigned index, WebRect* selectionRect)
Torne (Richard Coles)d5428f32014-03-18 10:21:16 +0000546{
547 ASSERT_WITH_SECURITY_IMPLICATION(index < m_findMatchesCache.size());
548
549 RefPtr<Range> range = m_findMatchesCache[index].m_range;
550 if (!range->boundaryPointsValid() || !range->startContainer()->inDocument())
551 return -1;
552
553 // Check if the match is already selected.
Ben Murdoch07a852d2014-03-31 11:51:52 +0100554 TextFinder& mainFrameTextFinder = m_ownerFrame.viewImpl()->mainFrameImpl()->ensureTextFinder();
555 WebFrameImpl* activeMatchFrame = mainFrameTextFinder.m_currentActiveMatchFrame;
556 if (&m_ownerFrame != activeMatchFrame || !m_activeMatch || !areRangesEqual(m_activeMatch.get(), range.get())) {
Torne (Richard Coles)d5428f32014-03-18 10:21:16 +0000557 if (isActiveMatchFrameValid())
Ben Murdoch07a852d2014-03-31 11:51:52 +0100558 activeMatchFrame->ensureTextFinder().setMatchMarkerActive(false);
Torne (Richard Coles)d5428f32014-03-18 10:21:16 +0000559
560 m_activeMatchIndexInCurrentFrame = m_findMatchesCache[index].m_ordinal - 1;
561
562 // Set this frame as the active frame (the one with the active highlight).
Ben Murdoch07a852d2014-03-31 11:51:52 +0100563 mainFrameTextFinder.m_currentActiveMatchFrame = &m_ownerFrame;
564 m_ownerFrame.viewImpl()->setFocusedFrame(&m_ownerFrame);
Torne (Richard Coles)d5428f32014-03-18 10:21:16 +0000565
566 m_activeMatch = range.release();
567 setMarkerActive(m_activeMatch.get(), true);
568
569 // Clear any user selection, to make sure Find Next continues on from the match we just activated.
Ben Murdoch07a852d2014-03-31 11:51:52 +0100570 m_ownerFrame.frame()->selection().clear();
Torne (Richard Coles)d5428f32014-03-18 10:21:16 +0000571
572 // Make sure no node is focused. See http://crbug.com/38700.
Ben Murdoch07a852d2014-03-31 11:51:52 +0100573 m_ownerFrame.frame()->document()->setFocusedElement(nullptr);
Torne (Richard Coles)d5428f32014-03-18 10:21:16 +0000574 }
575
576 IntRect activeMatchRect;
577 IntRect activeMatchBoundingBox = enclosingIntRect(RenderObject::absoluteBoundingBoxRectForRange(m_activeMatch.get()));
578
579 if (!activeMatchBoundingBox.isEmpty()) {
Ben Murdoch07a852d2014-03-31 11:51:52 +0100580 if (m_activeMatch->firstNode() && m_activeMatch->firstNode()->renderer()) {
581 m_activeMatch->firstNode()->renderer()->scrollRectToVisible(
582 activeMatchBoundingBox, ScrollAlignment::alignCenterIfNeeded, ScrollAlignment::alignCenterIfNeeded);
583 }
Torne (Richard Coles)d5428f32014-03-18 10:21:16 +0000584
585 // Zoom to the active match.
Ben Murdoch07a852d2014-03-31 11:51:52 +0100586 activeMatchRect = m_ownerFrame.frameView()->contentsToWindow(activeMatchBoundingBox);
587 m_ownerFrame.viewImpl()->zoomToFindInPageRect(activeMatchRect);
Torne (Richard Coles)d5428f32014-03-18 10:21:16 +0000588 }
589
590 if (selectionRect)
591 *selectionRect = activeMatchRect;
592
Ben Murdoch07a852d2014-03-31 11:51:52 +0100593 return ordinalOfFirstMatch() + m_activeMatchIndexInCurrentFrame + 1;
Torne (Richard Coles)d5428f32014-03-18 10:21:16 +0000594}
595
Ben Murdoch07a852d2014-03-31 11:51:52 +0100596PassOwnPtr<TextFinder> TextFinder::create(WebFrameImpl& ownerFrame)
Torne (Richard Coles)d5428f32014-03-18 10:21:16 +0000597{
Ben Murdoch07a852d2014-03-31 11:51:52 +0100598 return adoptPtr(new TextFinder(ownerFrame));
Torne (Richard Coles)d5428f32014-03-18 10:21:16 +0000599}
600
Ben Murdoch07a852d2014-03-31 11:51:52 +0100601TextFinder::TextFinder(WebFrameImpl& ownerFrame)
602 : m_ownerFrame(ownerFrame)
Torne (Richard Coles)d5428f32014-03-18 10:21:16 +0000603 , m_currentActiveMatchFrame(0)
604 , m_activeMatchIndexInCurrentFrame(-1)
Torne (Richard Coles)d5428f32014-03-18 10:21:16 +0000605 , m_resumeScopingFromRange(nullptr)
606 , m_lastMatchCount(-1)
607 , m_totalMatchCount(-1)
608 , m_framesScopingCount(-1)
609 , m_findRequestIdentifier(-1)
Torne (Richard Coles)d5428f32014-03-18 10:21:16 +0000610 , m_nextInvalidateAfter(0)
611 , m_findMatchMarkersVersion(0)
Ben Murdoch07a852d2014-03-31 11:51:52 +0100612 , m_locatingActiveRect(false)
613 , m_scopingInProgress(false)
614 , m_lastFindRequestCompletedWithNoMatches(false)
Torne (Richard Coles)d5428f32014-03-18 10:21:16 +0000615 , m_findMatchRectsAreValid(false)
Torne (Richard Coles)d5428f32014-03-18 10:21:16 +0000616{
Torne (Richard Coles)d5428f32014-03-18 10:21:16 +0000617}
618
Ben Murdoch07a852d2014-03-31 11:51:52 +0100619TextFinder::~TextFinder()
Torne (Richard Coles)d5428f32014-03-18 10:21:16 +0000620{
Torne (Richard Coles)d5428f32014-03-18 10:21:16 +0000621 cancelPendingScopingEffort();
622}
623
Ben Murdoch07a852d2014-03-31 11:51:52 +0100624void TextFinder::addMarker(Range* range, bool activeMatch)
Torne (Richard Coles)d5428f32014-03-18 10:21:16 +0000625{
Ben Murdoch07a852d2014-03-31 11:51:52 +0100626 m_ownerFrame.frame()->document()->markers().addTextMatchMarker(range, activeMatch);
Torne (Richard Coles)d5428f32014-03-18 10:21:16 +0000627}
628
Ben Murdoch07a852d2014-03-31 11:51:52 +0100629void TextFinder::setMarkerActive(Range* range, bool active)
Torne (Richard Coles)d5428f32014-03-18 10:21:16 +0000630{
631 if (!range || range->collapsed(IGNORE_EXCEPTION))
632 return;
Ben Murdoch07a852d2014-03-31 11:51:52 +0100633 m_ownerFrame.frame()->document()->markers().setMarkersActive(range, active);
Torne (Richard Coles)d5428f32014-03-18 10:21:16 +0000634}
635
Ben Murdoch07a852d2014-03-31 11:51:52 +0100636int TextFinder::ordinalOfFirstMatchForFrame(WebFrameImpl* frame) const
Torne (Richard Coles)d5428f32014-03-18 10:21:16 +0000637{
638 int ordinal = 0;
Ben Murdoch07a852d2014-03-31 11:51:52 +0100639 WebFrameImpl* mainFrameImpl = m_ownerFrame.viewImpl()->mainFrameImpl();
Torne (Richard Coles)d5428f32014-03-18 10:21:16 +0000640 // Iterate from the main frame up to (but not including) |frame| and
641 // add up the number of matches found so far.
642 for (WebFrameImpl* it = mainFrameImpl; it != frame; it = toWebFrameImpl(it->traverseNext(true))) {
Ben Murdoch07a852d2014-03-31 11:51:52 +0100643 TextFinder& finder = it->ensureTextFinder();
644 if (finder.m_lastMatchCount > 0)
645 ordinal += finder.m_lastMatchCount;
Torne (Richard Coles)d5428f32014-03-18 10:21:16 +0000646 }
647 return ordinal;
648}
649
Ben Murdoch07a852d2014-03-31 11:51:52 +0100650bool TextFinder::shouldScopeMatches(const String& searchText)
Torne (Richard Coles)d5428f32014-03-18 10:21:16 +0000651{
652 // Don't scope if we can't find a frame or a view.
653 // The user may have closed the tab/application, so abort.
654 // Also ignore detached frames, as many find operations report to the main frame.
Ben Murdoch07a852d2014-03-31 11:51:52 +0100655 LocalFrame* frame = m_ownerFrame.frame();
656 if (!frame || !frame->view() || !frame->page() || !m_ownerFrame.hasVisibleContent())
Torne (Richard Coles)d5428f32014-03-18 10:21:16 +0000657 return false;
658
Ben Murdoch07a852d2014-03-31 11:51:52 +0100659 ASSERT(frame->document() && frame->view());
Torne (Richard Coles)d5428f32014-03-18 10:21:16 +0000660
661 // If the frame completed the scoping operation and found 0 matches the last
662 // time it was searched, then we don't have to search it again if the user is
663 // just adding to the search string or sending the same search string again.
664 if (m_lastFindRequestCompletedWithNoMatches && !m_lastSearchString.isEmpty()) {
665 // Check to see if the search string prefixes match.
666 String previousSearchPrefix =
667 searchText.substring(0, m_lastSearchString.length());
668
669 if (previousSearchPrefix == m_lastSearchString)
670 return false; // Don't search this frame, it will be fruitless.
671 }
672
673 return true;
674}
675
Ben Murdoch07a852d2014-03-31 11:51:52 +0100676void TextFinder::scopeStringMatchesSoon(int identifier, const WebString& searchText, const WebFindOptions& options, bool reset)
Torne (Richard Coles)d5428f32014-03-18 10:21:16 +0000677{
678 m_deferredScopingWork.append(new DeferredScopeStringMatches(this, identifier, searchText, options, reset));
679}
680
Ben Murdoch07a852d2014-03-31 11:51:52 +0100681void TextFinder::callScopeStringMatches(DeferredScopeStringMatches* caller, int identifier, const WebString& searchText, const WebFindOptions& options, bool reset)
Torne (Richard Coles)d5428f32014-03-18 10:21:16 +0000682{
683 m_deferredScopingWork.remove(m_deferredScopingWork.find(caller));
684 scopeStringMatches(identifier, searchText, options, reset);
685
686 // This needs to happen last since searchText is passed by reference.
687 delete caller;
688}
689
Ben Murdoch07a852d2014-03-31 11:51:52 +0100690void TextFinder::invalidateIfNecessary()
Torne (Richard Coles)d5428f32014-03-18 10:21:16 +0000691{
692 if (m_lastMatchCount <= m_nextInvalidateAfter)
693 return;
694
695 // FIXME: (http://b/1088165) Optimize the drawing of the tickmarks and
696 // remove this. This calculation sets a milestone for when next to
697 // invalidate the scrollbar and the content area. We do this so that we
698 // don't spend too much time drawing the scrollbar over and over again.
699 // Basically, up until the first 500 matches there is no throttle.
700 // After the first 500 matches, we set set the milestone further and
701 // further out (750, 1125, 1688, 2K, 3K).
702 static const int startSlowingDownAfter = 500;
703 static const int slowdown = 750;
704
705 int i = m_lastMatchCount / startSlowingDownAfter;
706 m_nextInvalidateAfter += i * slowdown;
Ben Murdoch07a852d2014-03-31 11:51:52 +0100707 m_ownerFrame.invalidateScrollbar();
Torne (Richard Coles)d5428f32014-03-18 10:21:16 +0000708}
709
Ben Murdoch07a852d2014-03-31 11:51:52 +0100710void TextFinder::flushCurrentScoping()
Torne (Richard Coles)d5428f32014-03-18 10:21:16 +0000711{
Ben Murdoch07a852d2014-03-31 11:51:52 +0100712 flushCurrentScopingEffort(m_findRequestIdentifier);
Torne (Richard Coles)d5428f32014-03-18 10:21:16 +0000713}
714
Ben Murdoch07a852d2014-03-31 11:51:52 +0100715void TextFinder::setMatchMarkerActive(bool active)
Torne (Richard Coles)d5428f32014-03-18 10:21:16 +0000716{
Ben Murdoch07a852d2014-03-31 11:51:52 +0100717 setMarkerActive(m_activeMatch.get(), active);
718}
Torne (Richard Coles)d5428f32014-03-18 10:21:16 +0000719
Ben Murdoch07a852d2014-03-31 11:51:52 +0100720void TextFinder::decrementFramesScopingCount(int identifier)
721{
722 // This frame has no further scoping left, so it is done. Other frames might,
723 // of course, continue to scope matches.
724 --m_framesScopingCount;
Torne (Richard Coles)d5428f32014-03-18 10:21:16 +0000725
Ben Murdoch07a852d2014-03-31 11:51:52 +0100726 // If this is the last frame to finish scoping we need to trigger the final
727 // update to be sent.
728 if (!m_framesScopingCount)
729 m_ownerFrame.increaseMatchCount(0, identifier);
730}
731
732int TextFinder::ordinalOfFirstMatch() const
733{
734 return ordinalOfFirstMatchForFrame(&m_ownerFrame);
Torne (Richard Coles)d5428f32014-03-18 10:21:16 +0000735}
736
737} // namespace blink