/* * Copyright (C) Research In Motion Limited 2013. All rights reserved. */ #include "config.h" #include "SpellingHandler.h" #include "DOMSupport.h" #include "Frame.h" #include "InputHandler.h" #include "Range.h" #include "SpellChecker.h" #include "VisibleUnits.h" #include #include #include #define ENABLE_SPELLING_LOG 0 using namespace BlackBerry::Platform; using namespace WebCore; #if ENABLE_SPELLING_LOG #define SpellingLog(severity, format, ...) Platform::logAlways(severity, format, ## __VA_ARGS__) #else #define SpellingLog(severity, format, ...) #endif // ENABLE_SPELLING_LOG static const double s_timeout = 0.05; namespace BlackBerry { namespace WebKit { SpellingHandler::SpellingHandler(InputHandler* inputHandler) : m_inputHandler(inputHandler) , m_iterationDelayTimer(this, &SpellingHandler::parseBlockForSpellChecking) , m_isSpellCheckActive(false) { } SpellingHandler::~SpellingHandler() { } void SpellingHandler::spellCheckTextBlock(const WebCore::Element* element, WebCore::TextCheckingProcessType textCheckingProcessType) { SpellingLog(Platform::LogLevelInfo, "SpellingHandler::spellCheckTextBlock received request of type %s", textCheckingProcessType == TextCheckingProcessBatch ? "Batch" : "Incremental"); if (!(element->document() && element->document()->frame() && element->document()->frame()->selection())) return; VisiblePosition caretPosition = element->document()->frame()->selection()->start(); // Expand the range to include the previous line. This should handle cases when the user hits enter to finish composing a word and create a new line. // Account for word wrapping by jumping to the start of the previous line, then moving to the start of any word which might be there. VisibleSelection visibleSelection = VisibleSelection( startOfWord(startOfLine(previousLinePosition(caretPosition, caretPosition.lineDirectionPointForBlockDirectionNavigation()))), endOfWord(endOfLine(caretPosition))); // Check if this request can be sent off in one message, or if it needs to be broken down. RefPtr rangeForSpellChecking = visibleSelection.toNormalizedRange(); if (!rangeForSpellChecking || !rangeForSpellChecking->text() || !rangeForSpellChecking->text().length()) return; m_textCheckingProcessType = textCheckingProcessType; // Spellcheck Batch requests are used when focusing an element. During this time, we might have a lingering request // from a previously focused element. if (m_textCheckingProcessType == TextCheckingProcessBatch) { // If a previous request is being processed, stop it before continueing. if (m_iterationDelayTimer.isActive()) m_iterationDelayTimer.stop(); } m_isSpellCheckActive = true; // If we have a batch request, try to send off the entire block. if (m_textCheckingProcessType == TextCheckingProcessBatch) { // If total block text is under the limited amount, send the entire chunk. if (rangeForSpellChecking->text().length() < MaxSpellCheckingStringLength) { SpellingLog(Platform::LogLevelInfo, "SpellingHandler::spellCheckTextBlock creating single batch request"); createSpellCheckRequest(rangeForSpellChecking); return; } } // Since we couldn't check the entire block at once, set up starting and ending markers to fire incrementally. // Find the start and end of the region we're intending on checking m_startPosition = visibleSelection.visibleStart(); m_endPosition = endOfWord(m_startPosition); m_endOfRange = visibleSelection.visibleEnd(); m_cachedEndPosition = m_endOfRange; SpellingLog(Platform::LogLevelInfo, "SpellingHandler::spellCheckTextBlock starting first iteration"); m_iterationDelayTimer.startOneShot(0); } void SpellingHandler::createSpellCheckRequest(const PassRefPtr rangeForSpellCheckingPtr) { RefPtr rangeForSpellChecking = rangeForSpellCheckingPtr; rangeForSpellChecking = DOMSupport::trimWhitespaceFromRange(rangeForSpellChecking); if (!rangeForSpellChecking) return; if (rangeForSpellChecking->text().length() >= MinSpellCheckingStringLength) { SpellingLog(Platform::LogLevelInfo, "SpellingHandler::createSpellCheckRequest Substring text is '%s', of size %d" , rangeForSpellChecking->text().latin1().data() , rangeForSpellChecking->text().length()); m_inputHandler->callRequestCheckingFor(SpellCheckRequest::create(TextCheckingTypeSpelling, m_textCheckingProcessType, rangeForSpellChecking, rangeForSpellChecking)); } } void SpellingHandler::parseBlockForSpellChecking(WebCore::Timer*) { #if ENABLE_SPELLING_LOG BlackBerry::Platform::StopWatch timer; timer.start(); #endif SpellingLog(Platform::LogLevelInfo, "SpellingHandler::parseBlockForSpellChecking m_startPosition = %d, m_endPosition = %d, m_cachedEndPosition = %d, m_endOfRange = %d" , DOMSupport::offsetFromStartOfBlock(m_startPosition) , DOMSupport::offsetFromStartOfBlock(m_endPosition) , DOMSupport::offsetFromStartOfBlock(m_cachedEndPosition) , DOMSupport::offsetFromStartOfBlock(m_endOfRange)); if (m_startPosition == m_endOfRange) return; RefPtr rangeForSpellChecking = makeRange(m_startPosition, m_endPosition); if (!rangeForSpellChecking) { SpellingLog(Platform::LogLevelInfo, "SpellingHandler::parseBlockForSpellChecking Failed to set text range for spellchecking."); return; } if (rangeForSpellChecking->text().length() < MaxSpellCheckingStringLength) { if (m_endPosition == m_endOfRange || m_cachedEndPosition == m_endPosition) { createSpellCheckRequest(rangeForSpellChecking); m_isSpellCheckActive = false; return; } incrementSentinels(false /* shouldIncrementStartPosition */); #if ENABLE_SPELLING_LOG SpellingLog(Platform::LogLevelInfo, "SpellingHandler::parseBlockForSpellChecking spellcheck iteration took %lf seconds", timer.elapsed()); #endif m_iterationDelayTimer.startOneShot(s_timeout); return; } // Create a spellcheck request with the substring if we have a range that is of size less than MaxSpellCheckingStringLength if (rangeForSpellChecking = handleOversizedRange()) createSpellCheckRequest(rangeForSpellChecking); if (isSpellCheckActive()) { #if ENABLE_SPELLING_LOG SpellingLog(Platform::LogLevelInfo, "SpellingHandler::parseBlockForSpellChecking spellcheck iteration took %lf seconds", timer.elapsed()); #endif m_iterationDelayTimer.startOneShot(s_timeout); } } PassRefPtr SpellingHandler::handleOversizedRange() { SpellingLog(Platform::LogLevelInfo, "SpellingHandler::handleOversizedRange"); if (m_startPosition == m_cachedEndPosition || m_startPosition == startOfWord(m_endPosition, LeftWordIfOnBoundary)) { // Our first word has gone over the character limit. Increment the starting position past an uncheckable word. incrementSentinels(true /* shouldIncrementStartPosition */); return 0; } // If this is not the first word, return a Range with end boundary set to the previous word. RefPtr rangeToStartOfOversizedWord = makeRange(m_startPosition, m_cachedEndPosition); // We've created the range using the cached end position. Now increment the sentinals forward. // FIXME Incrementing the start/end positions outside of incrementSentinels m_startPosition = m_cachedEndPosition; m_endPosition = endOfWord(m_startPosition); return rangeToStartOfOversizedWord; } void SpellingHandler::incrementSentinels(bool shouldIncrementStartPosition) { SpellingLog(Platform::LogLevelInfo, "SpellingHandler::incrementSentinels shouldIncrementStartPosition %s", shouldIncrementStartPosition ? "true" : "false"); if (shouldIncrementStartPosition) m_startPosition = m_endPosition; VisiblePosition nextWord = nextWordPosition(m_endPosition); VisiblePosition startOfNextWord = startOfWord(nextWord, LeftWordIfOnBoundary); if (DOMSupport::isRangeTextAllWhitespace(m_endPosition, startOfNextWord)) { m_cachedEndPosition = startOfNextWord; m_endPosition = endOfWord(startOfNextWord); return; } m_cachedEndPosition = m_endPosition; m_endPosition = endOfWord(nextWord); } } // WebKit } // BlackBerry