4dd9949b8fbc469bf03a8a2cd84350222b090390
[WebKit-https.git] / Source / WebKit / blackberry / WebKitSupport / SpellingHandler.cpp
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 "InputHandler.h"
10 #include "Range.h"
11 #include "SpellChecker.h"
12 #include "TextIterator.h"
13 #include "visible_units.h"
14
15 #include <BlackBerryPlatformIMF.h>
16 #include <BlackBerryPlatformLog.h>
17
18 #define ENABLE_SPELLING_LOG 0
19
20 using namespace BlackBerry::Platform;
21 using namespace WebCore;
22
23 #if ENABLE_SPELLING_LOG
24 #define SpellingLog(severity, format, ...) Platform::logAlways(severity, format, ## __VA_ARGS__)
25 #else
26 #define SpellingLog(severity, format, ...)
27 #endif // ENABLE_SPELLING_LOG
28
29 static const double s_timeout = 0.05;
30
31 namespace BlackBerry {
32 namespace WebKit {
33
34 SpellingHandler::SpellingHandler(InputHandler* inputHandler)
35     : m_inputHandler(inputHandler)
36     , m_timer(this, &SpellingHandler::parseBlockForSpellChecking)
37     , m_isSpellCheckActive(false)
38 {
39 }
40
41 SpellingHandler::~SpellingHandler()
42 {
43 }
44
45 void SpellingHandler::spellCheckTextBlock(WebCore::VisibleSelection& visibleSelection, WebCore::TextCheckingProcessType textCheckingProcessType)
46 {
47     // Check if this request can be sent off in one message, or if it needs to be broken down.
48     RefPtr<Range> rangeForSpellChecking = visibleSelection.toNormalizedRange();
49     if (!rangeForSpellChecking || !rangeForSpellChecking->text() || !rangeForSpellChecking->text().length())
50         return;
51
52     // Spellcheck Batch requests are used when focusing an element. During this time, we might have a lingering request
53     // from a previously focused element.
54     if (textCheckingProcessType == TextCheckingProcessBatch) {
55         // If a previous request is being processed, stop it before continueing.
56         if (m_timer.isActive())
57             m_timer.stop();
58     }
59
60     m_isSpellCheckActive = true;
61
62     // If we have a batch request, try to send off the entire block.
63     if (textCheckingProcessType == TextCheckingProcessBatch) {
64         // If total block text is under the limited amount, send the entire chunk.
65         if (rangeForSpellChecking->text().length() < MaxSpellCheckingStringLength) {
66             createSpellCheckRequest(rangeForSpellChecking, TextCheckingProcessBatch);
67             return;
68         }
69     }
70
71     // Since we couldn't check the entire block at once, set up starting and ending markers to fire incrementally.
72     m_startOfCurrentLine = startOfLine(visibleSelection.visibleStart());
73     m_endOfCurrentLine = endOfLine(m_startOfCurrentLine);
74     m_textCheckingProcessType = textCheckingProcessType;
75
76     m_timer.startOneShot(0);
77 }
78
79 void SpellingHandler::createSpellCheckRequest(PassRefPtr<WebCore::Range> rangeForSpellCheckingPtr, WebCore::TextCheckingProcessType textCheckingProcessType)
80 {
81     RefPtr<WebCore::Range> rangeForSpellChecking = rangeForSpellCheckingPtr;
82     if (isSpellCheckActive() && rangeForSpellChecking->text().length() >= MinSpellCheckingStringLength)
83         m_inputHandler->callRequestCheckingFor(SpellCheckRequest::create(TextCheckingTypeSpelling, textCheckingProcessType, rangeForSpellChecking, rangeForSpellChecking));
84 }
85
86 void SpellingHandler::parseBlockForSpellChecking(WebCore::Timer<SpellingHandler>*)
87 {
88     if (isEndOfDocument(m_startOfCurrentLine)) {
89         m_isSpellCheckActive = false;
90         return;
91     }
92
93     // Create a selection with the start and end points of the line, and convert to Range to create a SpellCheckRequest.
94     RefPtr<Range> rangeForSpellChecking = VisibleSelection(m_startOfCurrentLine, m_endOfCurrentLine).toNormalizedRange();
95     if (!rangeForSpellChecking) {
96         SpellingLog(Platform::LogLevelInfo, "SpellingHandler::spellCheckBlock Failed to set text range for spellchecking.");
97         return;
98     }
99
100     if (rangeForSpellChecking->text().length() < MaxSpellCheckingStringLength) {
101         m_startOfCurrentLine = nextLinePosition(m_startOfCurrentLine, m_startOfCurrentLine.lineDirectionPointForBlockDirectionNavigation());
102         m_endOfCurrentLine = endOfLine(m_startOfCurrentLine);
103     } else {
104         // Iterate through words from the start of the line to the end.
105         rangeForSpellChecking = getRangeForSpellCheckWithFineGranularity(m_startOfCurrentLine, m_endOfCurrentLine);
106         if (!rangeForSpellChecking) {
107             SpellingLog(Platform::LogLevelWarn, "SpellingHandler::spellCheckBlock Failed to set text range with fine granularity.");
108             return;
109         }
110         m_startOfCurrentLine = VisiblePosition(rangeForSpellChecking->endPosition());
111         m_endOfCurrentLine = endOfLine(m_startOfCurrentLine);
112         rangeForSpellChecking = DOMSupport::trimWhitespaceFromRange(VisiblePosition(rangeForSpellChecking->startPosition()), VisiblePosition(rangeForSpellChecking->endPosition()));
113     }
114
115     SpellingLog(Platform::LogLevelInfo,
116         "SpellingHandler::spellCheckBlock Substring text is '%s', of size %d",
117         rangeForSpellChecking->text().latin1().data(),
118         rangeForSpellChecking->text().length());
119
120     // Call spellcheck with substring.
121     createSpellCheckRequest(rangeForSpellChecking, m_textCheckingProcessType);
122     if (isSpellCheckActive())
123         m_timer.startOneShot(s_timeout);
124 }
125
126 PassRefPtr<Range> SpellingHandler::getRangeForSpellCheckWithFineGranularity(WebCore::VisiblePosition startPosition, WebCore::VisiblePosition endPosition)
127 {
128     VisiblePosition endOfCurrentWord = endOfWord(startPosition);
129
130     // Keep iterating until one of our cases is hit, or we've incremented the starting position right to the end.
131     while (startPosition != endPosition) {
132         // Check the text length within this range.
133         if (VisibleSelection(startPosition, endOfCurrentWord).toNormalizedRange()->text().length() >= MaxSpellCheckingStringLength) {
134             // If this is not the first word, return a Range with end boundary set to the previous word.
135             if (startOfWord(endOfCurrentWord, LeftWordIfOnBoundary) != startPosition && !DOMSupport::isEmptyRangeOrAllSpaces(startPosition, endOfCurrentWord))
136                 return VisibleSelection(startPosition, endOfWord(previousWordPosition(endOfCurrentWord), LeftWordIfOnBoundary)).toNormalizedRange();
137
138             // Our first word has gone over the character limit. Increment the starting position past an uncheckable word.
139             startPosition = endOfCurrentWord;
140             endOfCurrentWord = endOfWord(nextWordPosition(endOfCurrentWord));
141         } else if (endOfCurrentWord == endPosition)
142             return VisibleSelection(startPosition, endPosition).toNormalizedRange(); // Return the last segment if the end of our word lies at the end of the range.
143         else
144             endOfCurrentWord = endOfWord(nextWordPosition(endOfCurrentWord)); // Increment the current word.
145     }
146     return 0;
147 }
148
149 } // WebKit
150 } // BlackBerry