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