1/* 2 * Copyright (C) Research In Motion Limited 2013. All rights reserved. 3 */ 4 5#include "config.h" 6#include "SpellingHandler.h" 7 8#include "DOMSupport.h" 9#include "Frame.h" 10#include "InputHandler.h" 11#include "Range.h" 12#include "SpellChecker.h" 13#include "VisibleUnits.h" 14 15#include <BlackBerryPlatformIMF.h> 16#include <BlackBerryPlatformLog.h> 17#include <BlackBerryPlatformStopWatch.h> 18 19#define ENABLE_SPELLING_LOG 0 20 21using namespace BlackBerry::Platform; 22using namespace WebCore; 23 24#if ENABLE_SPELLING_LOG 25#define SpellingLog(severity, format, ...) Platform::logAlways(severity, format, ## __VA_ARGS__) 26#else 27#define SpellingLog(severity, format, ...) 28#endif // ENABLE_SPELLING_LOG 29 30static const double s_timeout = 0.05; 31 32namespace BlackBerry { 33namespace WebKit { 34 35SpellingHandler::SpellingHandler(InputHandler* inputHandler) 36 : m_inputHandler(inputHandler) 37 , m_iterationDelayTimer(this, &SpellingHandler::parseBlockForSpellChecking) 38 , m_isSpellCheckActive(false) 39{ 40} 41 42SpellingHandler::~SpellingHandler() 43{ 44} 45 46void SpellingHandler::spellCheckTextBlock(const WebCore::Element* element, WebCore::TextCheckingProcessType textCheckingProcessType) 47{ 48 SpellingLog(Platform::LogLevelInfo, "SpellingHandler::spellCheckTextBlock received request of type %s", 49 textCheckingProcessType == TextCheckingProcessBatch ? "Batch" : "Incremental"); 50 51 if (!(element->document() && element->document()->frame() && element->document()->frame()->selection())) 52 return; 53 54 VisiblePosition caretPosition = element->document()->frame()->selection()->start(); 55 // 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. 56 // 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. 57 VisibleSelection visibleSelection = VisibleSelection( 58 startOfWord(startOfLine(previousLinePosition(caretPosition, caretPosition.lineDirectionPointForBlockDirectionNavigation()))), 59 endOfWord(endOfLine(caretPosition))); 60 61 // Check if this request can be sent off in one message, or if it needs to be broken down. 62 RefPtr<Range> rangeForSpellChecking = visibleSelection.toNormalizedRange(); 63 if (!rangeForSpellChecking || !rangeForSpellChecking->text() || !rangeForSpellChecking->text().length()) 64 return; 65 66 m_textCheckingProcessType = textCheckingProcessType; 67 68 // Spellcheck Batch requests are used when focusing an element. During this time, we might have a lingering request 69 // from a previously focused element. 70 if (m_textCheckingProcessType == TextCheckingProcessBatch) { 71 // If a previous request is being processed, stop it before continueing. 72 if (m_iterationDelayTimer.isActive()) 73 m_iterationDelayTimer.stop(); 74 } 75 76 m_isSpellCheckActive = true; 77 78 // If we have a batch request, try to send off the entire block. 79 if (m_textCheckingProcessType == TextCheckingProcessBatch) { 80 // If total block text is under the limited amount, send the entire chunk. 81 if (rangeForSpellChecking->text().length() < MaxSpellCheckingStringLength) { 82 SpellingLog(Platform::LogLevelInfo, "SpellingHandler::spellCheckTextBlock creating single batch request"); 83 createSpellCheckRequest(rangeForSpellChecking); 84 return; 85 } 86 } 87 88 // Since we couldn't check the entire block at once, set up starting and ending markers to fire incrementally. 89 // Find the start and end of the region we're intending on checking 90 m_startPosition = visibleSelection.visibleStart(); 91 m_endPosition = endOfWord(m_startPosition); 92 m_endOfRange = visibleSelection.visibleEnd(); 93 m_cachedEndPosition = m_endOfRange; 94 95 SpellingLog(Platform::LogLevelInfo, "SpellingHandler::spellCheckTextBlock starting first iteration"); 96 m_iterationDelayTimer.startOneShot(0); 97} 98 99void SpellingHandler::createSpellCheckRequest(const PassRefPtr<WebCore::Range> rangeForSpellCheckingPtr) 100{ 101 RefPtr<WebCore::Range> rangeForSpellChecking = rangeForSpellCheckingPtr; 102 rangeForSpellChecking = DOMSupport::trimWhitespaceFromRange(rangeForSpellChecking); 103 if (!rangeForSpellChecking) 104 return; 105 106 if (rangeForSpellChecking->text().length() >= MinSpellCheckingStringLength) { 107 SpellingLog(Platform::LogLevelInfo, "SpellingHandler::createSpellCheckRequest Substring text is '%s', of size %d" 108 , rangeForSpellChecking->text().latin1().data() 109 , rangeForSpellChecking->text().length()); 110 m_inputHandler->callRequestCheckingFor(SpellCheckRequest::create(TextCheckingTypeSpelling, m_textCheckingProcessType, rangeForSpellChecking, rangeForSpellChecking)); 111 } 112} 113 114void SpellingHandler::parseBlockForSpellChecking(WebCore::Timer<SpellingHandler>*) 115{ 116#if ENABLE_SPELLING_LOG 117 BlackBerry::Platform::StopWatch timer; 118 timer.start(); 119#endif 120 SpellingLog(Platform::LogLevelInfo, "SpellingHandler::parseBlockForSpellChecking m_startPosition = %d, m_endPosition = %d, m_cachedEndPosition = %d, m_endOfRange = %d" 121 , DOMSupport::offsetFromStartOfBlock(m_startPosition) 122 , DOMSupport::offsetFromStartOfBlock(m_endPosition) 123 , DOMSupport::offsetFromStartOfBlock(m_cachedEndPosition) 124 , DOMSupport::offsetFromStartOfBlock(m_endOfRange)); 125 126 if (m_startPosition == m_endOfRange) 127 return; 128 129 RefPtr<Range> rangeForSpellChecking = makeRange(m_startPosition, m_endPosition); 130 if (!rangeForSpellChecking) { 131 SpellingLog(Platform::LogLevelInfo, "SpellingHandler::parseBlockForSpellChecking Failed to set text range for spellchecking."); 132 return; 133 } 134 135 if (rangeForSpellChecking->text().length() < MaxSpellCheckingStringLength) { 136 if (m_endPosition == m_endOfRange || m_cachedEndPosition == m_endPosition) { 137 createSpellCheckRequest(rangeForSpellChecking); 138 m_isSpellCheckActive = false; 139 return; 140 } 141 142 incrementSentinels(false /* shouldIncrementStartPosition */); 143#if ENABLE_SPELLING_LOG 144 SpellingLog(Platform::LogLevelInfo, "SpellingHandler::parseBlockForSpellChecking spellcheck iteration took %lf seconds", timer.elapsed()); 145#endif 146 m_iterationDelayTimer.startOneShot(s_timeout); 147 return; 148 } 149 150 // Create a spellcheck request with the substring if we have a range that is of size less than MaxSpellCheckingStringLength 151 if (rangeForSpellChecking = handleOversizedRange()) 152 createSpellCheckRequest(rangeForSpellChecking); 153 154 if (isSpellCheckActive()) { 155#if ENABLE_SPELLING_LOG 156 SpellingLog(Platform::LogLevelInfo, "SpellingHandler::parseBlockForSpellChecking spellcheck iteration took %lf seconds", timer.elapsed()); 157#endif 158 m_iterationDelayTimer.startOneShot(s_timeout); 159 } 160} 161 162PassRefPtr<Range> SpellingHandler::handleOversizedRange() 163{ 164 SpellingLog(Platform::LogLevelInfo, "SpellingHandler::handleOversizedRange"); 165 166 if (m_startPosition == m_cachedEndPosition || m_startPosition == startOfWord(m_endPosition, LeftWordIfOnBoundary)) { 167 // Our first word has gone over the character limit. Increment the starting position past an uncheckable word. 168 incrementSentinels(true /* shouldIncrementStartPosition */); 169 return 0; 170 } 171 172 // If this is not the first word, return a Range with end boundary set to the previous word. 173 RefPtr<Range> rangeToStartOfOversizedWord = makeRange(m_startPosition, m_cachedEndPosition); 174 // We've created the range using the cached end position. Now increment the sentinals forward. 175 // FIXME Incrementing the start/end positions outside of incrementSentinels 176 m_startPosition = m_cachedEndPosition; 177 m_endPosition = endOfWord(m_startPosition); 178 return rangeToStartOfOversizedWord; 179} 180 181void SpellingHandler::incrementSentinels(bool shouldIncrementStartPosition) 182{ 183 SpellingLog(Platform::LogLevelInfo, "SpellingHandler::incrementSentinels shouldIncrementStartPosition %s", shouldIncrementStartPosition ? "true" : "false"); 184 185 if (shouldIncrementStartPosition) 186 m_startPosition = m_endPosition; 187 188 VisiblePosition nextWord = nextWordPosition(m_endPosition); 189 VisiblePosition startOfNextWord = startOfWord(nextWord, LeftWordIfOnBoundary); 190 if (DOMSupport::isRangeTextAllWhitespace(m_endPosition, startOfNextWord)) { 191 m_cachedEndPosition = startOfNextWord; 192 m_endPosition = endOfWord(startOfNextWord); 193 return; 194 } 195 196 m_cachedEndPosition = m_endPosition; 197 m_endPosition = endOfWord(nextWord); 198} 199 200} // WebKit 201} // BlackBerry 202