diff options
Diffstat (limited to 'Source/WebKit/blackberry/WebKitSupport/InPageSearchManager.cpp')
-rw-r--r-- | Source/WebKit/blackberry/WebKitSupport/InPageSearchManager.cpp | 227 |
1 files changed, 195 insertions, 32 deletions
diff --git a/Source/WebKit/blackberry/WebKitSupport/InPageSearchManager.cpp b/Source/WebKit/blackberry/WebKitSupport/InPageSearchManager.cpp index 09cfd55bc..c4322de57 100644 --- a/Source/WebKit/blackberry/WebKitSupport/InPageSearchManager.cpp +++ b/Source/WebKit/blackberry/WebKitSupport/InPageSearchManager.cpp @@ -24,30 +24,69 @@ #include "DocumentMarkerController.h" #include "Editor.h" #include "Frame.h" +#include "Node.h" #include "Page.h" +#include "Range.h" +#include "TextIterator.h" +#include "Timer.h" #include "WebPage_p.h" -static const int TextMatchMarkerLimit = 100; +static const double MaxScopingDuration = 0.1; using namespace WebCore; namespace BlackBerry { namespace WebKit { +class InPageSearchManager::DeferredScopeStringMatches { +public: + DeferredScopeStringMatches(InPageSearchManager* ipsm, Frame* scopingFrame, const String& text, bool reset) + : m_searchManager(ipsm) + , m_scopingFrame(scopingFrame) + , m_timer(this, &DeferredScopeStringMatches::doTimeout) + , m_searchText(text) + , m_reset(reset) + { + m_timer.startOneShot(0.0); + } + +private: + void doTimeout(Timer<DeferredScopeStringMatches>*) + { + m_searchManager->callScopeStringMatches(this, m_scopingFrame, m_searchText, m_reset); + } + InPageSearchManager* m_searchManager; + Frame* m_scopingFrame; + Timer<DeferredScopeStringMatches> m_timer; + String m_searchText; + bool m_reset; +}; + InPageSearchManager::InPageSearchManager(WebPagePrivate* page) : m_webPage(page) , m_activeMatch(0) + , m_resumeScopingFromRange(0) + , m_activeMatchCount(0) + , m_scopingComplete(false) + , m_scopingCaseInsensitive(false) + , m_locatingActiveMatch(false) + , m_highlightAllMatches(false) + , m_activeMatchIndex(0) { } InPageSearchManager::~InPageSearchManager() { + cancelPendingScopingEffort(); } -bool InPageSearchManager::findNextString(const String& text, bool forward) +bool InPageSearchManager::findNextString(const String& text, FindOptions findOptions, bool wrap, bool highlightAllMatches) { + m_highlightAllMatches = highlightAllMatches; + if (!text.length()) { clearTextMatches(); + cancelPendingScopingEffort(); m_activeSearchString = String(); return false; } @@ -61,17 +100,16 @@ bool InPageSearchManager::findNextString(const String& text, bool forward) if (m_activeMatch && !m_activeMatch->boundaryPointsValid()) m_activeMatch = 0; - RefPtr<Range> searchStartingPoint(m_activeMatch); - if (m_activeSearchString != text) { // Start a new search. + ExceptionCode ec = 0; + RefPtr<Range> searchStartingPoint = m_activeMatch ? m_activeMatch->cloneRange(ec) : 0; + bool newSearch = m_activeSearchString != text; + bool forward = !(findOptions & WebCore::Backwards); + if (newSearch) { // Start a new search. m_activeSearchString = text; + cancelPendingScopingEffort(); + m_scopingCaseInsensitive = findOptions & CaseInsensitive; m_webPage->m_page->unmarkAllTextMatches(); - m_activeMatchCount = m_webPage->m_page->markAllMatchesForText(text, CaseInsensitive, true /* shouldHighlight */, TextMatchMarkerLimit); - if (!m_activeMatchCount) { - clearTextMatches(); - return false; - } - } else { // Search same string for next occurrence. - setMarkerActive(m_activeMatch.get(), false /* active */); + } else { // Searching for same string should start from the end of last match. if (m_activeMatch) { if (forward) @@ -90,23 +128,27 @@ bool InPageSearchManager::findNextString(const String& text, bool forward) Frame* currentActiveMatchFrame = selection.isNone() && m_activeMatch ? m_activeMatch->ownerDocument()->frame() : m_webPage->focusedOrMainFrame(); - const FindOptions findOptions = (forward ? 0 : Backwards) - | CaseInsensitive - | StartInSelection; - - if (findAndMarkText(text, searchStartingPoint.get(), currentActiveMatchFrame, findOptions)) + if (findAndMarkText(text, searchStartingPoint.get(), currentActiveMatchFrame, findOptions, newSearch)) return true; Frame* startFrame = currentActiveMatchFrame; do { - currentActiveMatchFrame = DOMSupport::incrementFrame(currentActiveMatchFrame, forward, true /* wrapFlag */); - if (findAndMarkText(text, 0, currentActiveMatchFrame, findOptions)) + currentActiveMatchFrame = DOMSupport::incrementFrame(currentActiveMatchFrame, forward, wrap); + + if (!currentActiveMatchFrame) { + // We should only ever have a null frame if we haven't found any + // matches and we're not wrapping. We have searched every frame. + ASSERT(!wrap); + return false; + } + + if (findAndMarkText(text, 0, currentActiveMatchFrame, findOptions, newSearch)) return true; - } while (currentActiveMatchFrame && startFrame != currentActiveMatchFrame); + } while (startFrame != currentActiveMatchFrame); clearTextMatches(); - ASSERT_NOT_REACHED(); + // FIXME: We need to notify client here. return false; } @@ -117,7 +159,8 @@ bool InPageSearchManager::shouldSearchForText(const String& text) // If the previous search string is prefix of new search string, // don't search if the previous one has zero result. - if (!m_activeMatchCount + if (m_scopingComplete + && !m_activeMatchCount && m_activeSearchString.length() && text.length() > m_activeSearchString.length() && m_activeSearchString == text.substring(0, m_activeSearchString.length())) @@ -125,11 +168,27 @@ bool InPageSearchManager::shouldSearchForText(const String& text) return true; } -bool InPageSearchManager::findAndMarkText(const String& text, Range* range, Frame* frame, const FindOptions& options) +bool InPageSearchManager::findAndMarkText(const String& text, Range* range, Frame* frame, const FindOptions& options, bool isNewSearch) { - m_activeMatch = frame->editor()->findStringAndScrollToVisible(text, range, options); - if (m_activeMatch) { - setMarkerActive(m_activeMatch.get(), true /* active */); + if (RefPtr<Range> match = frame->editor()->findStringAndScrollToVisible(text, range, options)) { + // Move the highlight to the new match. + setActiveMatchAndMarker(match); + + if (m_highlightAllMatches) { + // FIXME: If it is a not new search, we need to calculate activeMatchIndex and notify client. + if (isNewSearch) + scopeStringMatches(text, true /* reset */); + } else { + // When only showing single matches, cancel any scoping effort and ensure + // only the single active match is marked. + cancelPendingScopingEffort(); + m_webPage->m_page->unmarkAllTextMatches(); + m_activeMatch->ownerDocument()->markers()->addTextMatchMarker(m_activeMatch.get(), true); + frame->editor()->setMarkedTextMatchesAreHighlighted(true /* highlight */); + m_activeMatchCount = 1; + m_activeMatchIndex = 1; + } + return true; } return false; @@ -139,16 +198,23 @@ void InPageSearchManager::clearTextMatches() { m_webPage->m_page->unmarkAllTextMatches(); m_activeMatch = 0; + m_activeMatchCount = 0; + m_activeMatchIndex = 0; } -void InPageSearchManager::setMarkerActive(Range* range, bool active) +void InPageSearchManager::setActiveMatchAndMarker(PassRefPtr<Range> range) { - if (!range) - return; - Document* doc = m_activeMatch->ownerDocument(); - if (!doc) - return; - doc->markers()->setMarkersActive(range, active); + // Clear the old marker, update our range, and highlight the new range. + if (m_activeMatch.get()) { + if (Document* doc = m_activeMatch->ownerDocument()) + doc->markers()->setMarkersActive(m_activeMatch.get(), false); + } + + m_activeMatch = range; + if (m_activeMatch.get()) { + if (Document* doc = m_activeMatch->ownerDocument()) + doc->markers()->setMarkersActive(m_activeMatch.get(), true); + } } void InPageSearchManager::frameUnloaded(const Frame* frame) @@ -161,6 +227,8 @@ void InPageSearchManager::frameUnloaded(const Frame* frame) Frame* currentActiveMatchFrame = m_activeMatch->ownerDocument()->frame(); if (currentActiveMatchFrame == frame) { + // FIXME: We need to re-scope this frame instead of cancelling all effort? + cancelPendingScopingEffort(); m_activeMatch = 0; m_activeSearchString = String(); m_activeMatchCount = 0; @@ -171,5 +239,100 @@ void InPageSearchManager::frameUnloaded(const Frame* frame) } } +void InPageSearchManager::scopeStringMatches(const String& text, bool reset, Frame* scopingFrame) +{ + if (reset) { + m_activeMatchCount = 0; + m_resumeScopingFromRange = 0; + m_scopingComplete = false; + m_locatingActiveMatch = true; + // New search should always start from mainFrame. + scopeStringMatchesSoon(m_webPage->mainFrame(), text, false /* reset */); + return; + } + + if (m_resumeScopingFromRange && scopingFrame != m_resumeScopingFromRange->ownerDocument()->frame()) + m_resumeScopingFromRange = 0; + + RefPtr<Range> searchRange(rangeOfContents(scopingFrame->document())); + Node* originalEndContainer = searchRange->endContainer(); + int originalEndOffset = searchRange->endOffset(); + ExceptionCode ec = 0, ec2 = 0; + if (m_resumeScopingFromRange) { + searchRange->setStart(m_resumeScopingFromRange->startContainer(), m_resumeScopingFromRange->startOffset(ec2) + 1, ec); + if (ec || ec2) { + m_scopingComplete = true; // We should stop scoping because of some stale data. + return; + } + } + + int matchCount = 0; + bool timeout = false; + double startTime = currentTime(); + do { + RefPtr<Range> resultRange(findPlainText(searchRange.get(), text, m_scopingCaseInsensitive ? CaseInsensitive : 0)); + if (resultRange->collapsed(ec)) { + if (!resultRange->startContainer()->isInShadowTree()) + break; + searchRange->setStartAfter(resultRange->startContainer()->shadowAncestorNode(), ec); + searchRange->setEnd(originalEndContainer, originalEndOffset, ec); + continue; + } + + if (scopingFrame->editor()->insideVisibleArea(resultRange.get())) { + ++matchCount; + bool foundActiveMatch = false; + if (m_locatingActiveMatch && areRangesEqual(resultRange.get(), m_activeMatch.get())) { + foundActiveMatch = true; + m_locatingActiveMatch = false; + m_activeMatchIndex = m_activeMatchCount + matchCount; + // FIXME: We need to notify client with m_activeMatchIndex. + } + resultRange->ownerDocument()->markers()->addTextMatchMarker(resultRange.get(), foundActiveMatch); + } + searchRange->setStart(resultRange->endContainer(ec), resultRange->endOffset(ec), ec); + Node* shadowTreeRoot = searchRange->shadowTreeRootNode(); + if (searchRange->collapsed(ec) && shadowTreeRoot) + searchRange->setEnd(shadowTreeRoot, shadowTreeRoot->childNodeCount(), ec); + m_resumeScopingFromRange = resultRange; + timeout = (currentTime() - startTime) >= MaxScopingDuration; + } while (!timeout); + + if (matchCount > 0) { + scopingFrame->editor()->setMarkedTextMatchesAreHighlighted(true /* highlight */); + m_activeMatchCount += matchCount; + } + + if (timeout) + scopeStringMatchesSoon(scopingFrame, text, false /* reset */); + else { + // Scoping is done for this frame. + Frame* nextFrame = DOMSupport::incrementFrame(scopingFrame, true /* forward */, false /* wrapFlag */); + if (!nextFrame) { + m_scopingComplete = true; + return; // Scoping is done for all frames; + } + scopeStringMatchesSoon(nextFrame, text, false /* reset */); + } +} + +void InPageSearchManager::scopeStringMatchesSoon(Frame* scopingFrame, const String& text, bool reset) +{ + m_deferredScopingWork.append(new DeferredScopeStringMatches(this, scopingFrame, text, reset)); +} + +void InPageSearchManager::callScopeStringMatches(DeferredScopeStringMatches* caller, Frame* scopingFrame, const String& text, bool reset) +{ + m_deferredScopingWork.remove(m_deferredScopingWork.find(caller)); + scopeStringMatches(text, reset, scopingFrame); + delete caller; +} + +void InPageSearchManager::cancelPendingScopingEffort() +{ + deleteAllValues(m_deferredScopingWork); + m_deferredScopingWork.clear(); +} + } } |