[BlackBerry] Queue spellcheck requests with char-count limitations
authorcommit-queue@webkit.org <commit-queue@webkit.org@268f45cc-cd09-0410-ab3c-d52691b4dbfc>
Tue, 14 Aug 2012 20:45:10 +0000 (20:45 +0000)
committercommit-queue@webkit.org <commit-queue@webkit.org@268f45cc-cd09-0410-ab3c-d52691b4dbfc>
Tue, 14 Aug 2012 20:45:10 +0000 (20:45 +0000)
https://bugs.webkit.org/show_bug.cgi?id=93866

Patch by Nima Ghanavatian <nghanavatian@rim.com> on 2012-08-14
Reviewed by Rob Buis.

PR184196
Making spellcheck a little more granular so as to work around
the limitations of the inputservice.
Also, spellcheck the entire field on focus only if
spellcheck="on" is explicitly set.

Internally reviewed by Mike Fenton.

* WebKitSupport/InputHandler.cpp:
(BlackBerry::WebKit::InputHandler::requestCheckingOfString):
(BlackBerry::WebKit::InputHandler::spellCheckingRequestProcessed):

git-svn-id: https://svn.webkit.org/repository/webkit/trunk@125598 268f45cc-cd09-0410-ab3c-d52691b4dbfc

Source/WebKit/blackberry/ChangeLog
Source/WebKit/blackberry/WebKitSupport/DOMSupport.cpp
Source/WebKit/blackberry/WebKitSupport/DOMSupport.h
Source/WebKit/blackberry/WebKitSupport/InputHandler.cpp
Source/WebKit/blackberry/WebKitSupport/InputHandler.h

index 2d85668..a7b12a6 100644 (file)
@@ -1,3 +1,22 @@
+2012-08-14  Nima Ghanavatian  <nghanavatian@rim.com>
+
+        [BlackBerry] Queue spellcheck requests with char-count limitations
+        https://bugs.webkit.org/show_bug.cgi?id=93866
+
+        Reviewed by Rob Buis.
+
+        PR184196
+        Making spellcheck a little more granular so as to work around
+        the limitations of the inputservice.
+        Also, spellcheck the entire field on focus only if
+        spellcheck="on" is explicitly set.
+
+        Internally reviewed by Mike Fenton.
+
+        * WebKitSupport/InputHandler.cpp:
+        (BlackBerry::WebKit::InputHandler::requestCheckingOfString):
+        (BlackBerry::WebKit::InputHandler::spellCheckingRequestProcessed):
+
 2012-08-14  Mike Fenton  <mifenton@rim.com>
 
         [BlackBerry] Use Form helper functions to determine state in DOMSupport::isTextBasedContentEditableElement
index 1c07a33..4b0ffbc 100644 (file)
@@ -198,6 +198,11 @@ AttributeState elementSupportsAutocomplete(const Element* element)
     return elementAttributeState(element, HTMLNames::autocompleteAttr);
 }
 
+AttributeState elementSupportsSpellCheck(const Element* element)
+{
+    return elementAttributeState(element, HTMLNames::spellcheckAttr);
+}
+
 AttributeState elementAttributeState(const Element* element, const QualifiedName& attributeName)
 {
     // First we check the input item itself. If the attribute is not defined,
index 38699dc..5de7f73 100644 (file)
@@ -60,6 +60,7 @@ bool isColorInputField(const WebCore::Element*);
 AttributeState elementAttributeState(const WebCore::Element*, const WebCore::QualifiedName&);
 AttributeState elementSupportsAutocorrect(const WebCore::Element*);
 AttributeState elementSupportsAutocomplete(const WebCore::Element*);
+AttributeState elementSupportsSpellCheck(const WebCore::Element*);
 
 WTF::String inputElementText(WebCore::Element*);
 
index 1d0a5d2..31f646b 100644 (file)
 #include "SpellChecker.h"
 #include "TextCheckerClient.h"
 #include "TextIterator.h"
+#include "VisiblePosition.h"
 #include "WebPageClient.h"
 #include "WebPage_p.h"
 #include "WebSettings.h"
+#include "visible_units.h"
 
 #include <BlackBerryPlatformKeyboardEvent.h>
 #include <BlackBerryPlatformLog.h>
@@ -72,6 +74,7 @@
 #define ENABLE_SPELLING_LOG 0
 
 static const unsigned MaxLearnTextDataSize = 500;
+static const unsigned MaxSpellCheckingStringLength = 250;
 
 using namespace BlackBerry::Platform;
 using namespace WebCore;
@@ -531,28 +534,49 @@ void InputHandler::requestCheckingOfString(PassRefPtr<WebCore::TextCheckingReque
     RefPtr<WebCore::TextCheckingRequest> request = textCheckingRequest;
 
     int32_t sequenceId = request->sequence();
-    int requestLength = request->text().length();
-    if (!requestLength /* || requestLength > maxSpellCheckStringLength */) {
+
+    // Check if field explicitly asked for spellchecking.
+    if (DOMSupport::elementSupportsSpellCheck(m_currentFocusElement.get()) != DOMSupport::On) {
+        spellCheckingRequestCancelled(sequenceId, true /* isSequenceId */);
+        return;
+    }
+
+    unsigned requestLength = request->text().length();
+
+    if (requestLength < 2) {
         spellCheckingRequestCancelled(sequenceId, true /* isSequenceId */);
         return;
     }
 
+    if (requestLength > MaxSpellCheckingStringLength) {
+        // Cancel this request and send it off in newly created chunks.
+        spellCheckingRequestCancelled(sequenceId, true /* isSequenceId */);
+        if (m_currentFocusElement && m_currentFocusElement->document() && m_currentFocusElement->document()->frame() && m_currentFocusElement->document()->frame()->selection()) {
+            // Convert from position back to selection so we can 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.
+            VisiblePosition caretPosition = m_currentFocusElement->document()->frame()->selection()->start();
+            VisibleSelection visibleSelection = VisibleSelection(previousLinePosition(caretPosition, caretPosition.lineDirectionPointForBlockDirectionNavigation()), caretPosition);
+            spellCheckBlock(visibleSelection, TextCheckingProcessIncremental);
+        }
+        return;
+    }
+
     wchar_t* checkingString = (wchar_t*)malloc(sizeof(wchar_t) * (requestLength + 1));
     if (!checkingString) {
-        BlackBerry::Platform::logAlways(BlackBerry::Platform::LogLevelCritical, "InputHandler::requestCheckingOfString Cannot allocate memory for string.");
+        logAlways(LogLevelCritical, "InputHandler::requestCheckingOfString Cannot allocate memory for string.");
         spellCheckingRequestCancelled(sequenceId, true /* isSequenceId */);
         return;
     }
 
-    int stringLength = 0;
-    if (!convertStringToWchar(request->text(), checkingString, requestLength + 1, &stringLength)) {
-        BlackBerry::Platform::logAlways(BlackBerry::Platform::LogLevelCritical, "InputHandler::requestCheckingOfString Failed to convert String to wchar type.");
+    int paragraphLength = 0;
+    if (!convertStringToWchar(request->text(), checkingString, requestLength + 1, &paragraphLength)) {
+        logAlways(LogLevelCritical, "InputHandler::requestCheckingOfString Failed to convert String to wchar type.");
         free(checkingString);
         spellCheckingRequestCancelled(sequenceId, true /* isSequenceId */);
         return;
     }
 
-    int32_t transactionId = m_webPage->m_client->checkSpellingOfStringAsync(checkingString, stringLength);
+    int32_t transactionId = m_webPage->m_client->checkSpellingOfStringAsync(checkingString, paragraphLength);
     free(checkingString);
 
     // If the call to the input service did not go through, then cancel the request so we don't block endlessly.
@@ -620,13 +644,20 @@ void InputHandler::spellCheckingRequestProcessed(int32_t transactionId, spannabl
     // transactionId here is for use with the input service. We need to translate this to sequenceId used with SpellChecker.
     int32_t sequenceId = convertTransactionIdToSequenceId(transactionId);
 
-    SpellChecker* sp = getSpellChecker();
-    if (!sp || !sequenceId) {
-        InputLog(LogLevelWarn, "InputHandler::spellCheckingRequestProcessed failed to process the request with sequenceId %d", sequenceId);
+    SpellChecker* spellChecker = getSpellChecker();
+    if (!spellChecker || !sequenceId) {
+        SpellingLog(LogLevelWarn, "InputHandler::spellCheckingRequestProcessed Failed to process the request with sequenceId %d", sequenceId);
         spellCheckingRequestCancelled(sequenceId, true /* isSequenceId */);
         return;
     }
-    sp->didCheckSucceeded(sequenceId, results);
+    spellChecker->didCheckSucceeded(sequenceId, results);
+}
+
+void InputHandler::cancelAllSpellCheckingRequests()
+{
+    for (std::map<int32_t, int32_t>::iterator it = m_sequenceMap.begin(); it != m_sequenceMap.end(); ++it)
+        spellCheckingRequestCancelled(it->second, true /* isSequenceId */);
+    m_sequenceMap.clear();
 }
 
 void InputHandler::spellCheckingRequestCancelled(int32_t id, bool isSequenceId)
@@ -635,12 +666,12 @@ void InputHandler::spellCheckingRequestCancelled(int32_t id, bool isSequenceId)
         return;
 
     int32_t sequenceId = isSequenceId ? id : convertTransactionIdToSequenceId(id);
-    SpellChecker* sp = getSpellChecker();
-    if (!sp) {
-        InputLog(LogLevelWarn, "InputHandler::spellCheckingRequestCancelled failed to cancel the request with sequenceId %d", sequenceId);
+    SpellChecker* spellChecker = getSpellChecker();
+    if (!spellChecker) {
+        SpellingLog(LogLevelWarn, "InputHandler::spellCheckingRequestCancelled failed to cancel the request with sequenceId %d", sequenceId);
         return;
     }
-    sp->didCheckCanceled(sequenceId);
+    spellChecker->didCheckCanceled(sequenceId);
 }
 
 SpellChecker* InputHandler::getSpellChecker()
@@ -719,6 +750,9 @@ void InputHandler::setElementUnfocused(bool refocusOccuring)
         // If the frame selection isn't focused, focus it.
         if (!m_currentFocusElement->document()->frame()->selection()->isFocused())
             m_currentFocusElement->document()->frame()->selection()->setFocused(true);
+
+        // Cancel any spellcheck requests that might be ongoing.
+        cancelAllSpellCheckingRequests();
     }
 
     // Clear the node details.
@@ -748,10 +782,22 @@ void InputHandler::setInputModeEnabled(bool active)
 void InputHandler::setElementFocused(Element* element)
 {
     ASSERT(DOMSupport::isTextBasedContentEditableElement(element));
-    ASSERT(element->document() && element->document()->frame());
+    ASSERT(element && element->document() && element->document()->frame());
+
+#ifdef ENABLE_SPELLING_LOG
+    BlackBerry::Platform::StopWatch timer;
+    timer.start();
+#endif
+
+    if (!element || !(element->document()))
+        return;
+
+    Frame* frame = element->document()->frame();
+    if (!frame)
+        return;
 
-    if (element->document()->frame()->selection()->isFocused() != isInputModeEnabled())
-        element->document()->frame()->selection()->setFocused(isInputModeEnabled());
+    if (frame->selection()->isFocused() != isInputModeEnabled())
+        frame->selection()->setFocused(isInputModeEnabled());
 
     // Clear the existing focus node details.
     setElementUnfocused(true /*refocusOccuring*/);
@@ -774,6 +820,91 @@ void InputHandler::setElementFocused(Element* element)
 
     if (!m_delayKeyboardVisibilityChange)
         notifyClientOfKeyboardVisibilityChange(true);
+
+#ifdef ENABLE_SPELLING_LOG
+    SpellingLog(LogLevelInfo, "InputHandler::setElementFocused Focusing the field took %f seconds.", timer.elapsed());
+#endif
+
+    // Check if the field explicitly asks for spellchecking.
+    if (DOMSupport::elementSupportsSpellCheck(element) != DOMSupport::On)
+        return;
+
+    // Spellcheck the field in its entirety.
+    VisibleSelection focusedBlock = DOMSupport::visibleSelectionForInputElement(element);
+    spellCheckBlock(focusedBlock, TextCheckingProcessBatch);
+
+#ifdef ENABLE_SPELLING_LOG
+    SpellingLog(LogLevelInfo, "InputHandler::setElementFocused Spellchecking the field increased the total time to focus to %f seconds.", timer.elapsed());
+#endif
+}
+
+void InputHandler::spellCheckBlock(VisibleSelection& visibleSelection, TextCheckingProcessType textCheckingProcessType)
+{
+    if (!isActiveTextEdit())
+        return;
+
+    SpellChecker* spellChecker = getSpellChecker();
+    if (!spellChecker) {
+        SpellingLog(LogLevelInfo, "InputHandler::spellCheckBlock Failed to spellcheck the current focused element.");
+        return;
+    }
+
+    RefPtr<Range> rangeForSpellChecking = visibleSelection.toNormalizedRange();
+
+    // If we have a batch request, try to send off the entire block.
+    if (textCheckingProcessType == TextCheckingProcessBatch) {
+        // If total block text is under the limited amount, send the entire chunk.
+        if (rangeForSpellChecking->text().length() < MaxSpellCheckingStringLength) {
+            spellChecker->requestCheckingFor(SpellCheckRequest::create(TextCheckingTypeSpelling, TextCheckingProcessBatch, rangeForSpellChecking, rangeForSpellChecking));
+            return;
+        }
+    }
+
+    // Since we couldn't check the entire block at once, set up starting and ending markers to fire incrementally.
+    VisiblePosition startPos = visibleSelection.visibleStart();
+    VisiblePosition startOfCurrentLine = startOfLine(startPos);
+    VisiblePosition endOfCurrentLine = endOfLine(startPos);
+
+    while (startOfCurrentLine != endOfCurrentLine) {
+        // Create a selection with the start and end points of the line, and convert to Range to create a SpellCheckRequest.
+        rangeForSpellChecking = VisibleSelection(startOfCurrentLine, endOfCurrentLine).toNormalizedRange();
+
+        if (rangeForSpellChecking->text().length() >= MaxSpellCheckingStringLength) {
+            // Iterate through words from the start of the line to the end.
+            rangeForSpellChecking = getRangeForSpellCheckWithFineGranularity(startOfCurrentLine, endOfCurrentLine);
+            if (!rangeForSpellChecking) {
+                SpellingLog(LogLevelWarn, "InputHandler::spellCheckBlock Failed to set text range for spellchecking");
+                return;
+            }
+            startOfCurrentLine = VisiblePosition(rangeForSpellChecking->endPosition());
+        } else {
+            startOfCurrentLine = nextLinePosition(startOfCurrentLine, startOfCurrentLine.lineDirectionPointForBlockDirectionNavigation());
+            endOfCurrentLine = endOfLine(startOfCurrentLine);
+            // If we are at the last line, nextLinePosition will return the position at the end of the line. If we're not at the end, wrap with a call to startOfLine to be safe.
+            if (startOfCurrentLine != endOfCurrentLine)
+                startOfCurrentLine = startOfLine(startOfCurrentLine);
+        }
+
+        SpellingLog(LogLevelInfo, "InputHandler::spellCheckBlock Substring text is '%s', of size %d", rangeForSpellChecking->text().latin1().data(), rangeForSpellChecking->text().length());
+
+        // Call spellcheck with substring.
+        spellChecker->requestCheckingFor(SpellCheckRequest::create(TextCheckingTypeSpelling, TextCheckingProcessBatch, rangeForSpellChecking, rangeForSpellChecking));
+    }
+}
+
+PassRefPtr<Range> InputHandler::getRangeForSpellCheckWithFineGranularity(VisiblePosition startPosition, VisiblePosition endPosition)
+{
+    VisiblePosition endOfCurrentWord = endOfWord(startPosition);
+    RefPtr<Range> rangeForSpellChecking;
+    while (endOfCurrentWord != endPosition) {
+        rangeForSpellChecking = VisibleSelection(startPosition, endOfCurrentWord).toNormalizedRange();
+        // If we exceed the MaxSpellCheckingStringLength limit, then go back one word and return this range.
+        if (rangeForSpellChecking->text().length() >= MaxSpellCheckingStringLength)
+            return VisibleSelection(startPosition, endOfWord(previousWordPosition(endOfCurrentWord))).toNormalizedRange();
+
+        endOfCurrentWord = endOfWord(nextWordPosition(endOfCurrentWord));
+    }
+    return 0;
 }
 
 bool InputHandler::openDatePopup(HTMLInputElement* element, BlackBerryInputType type)
index 3672640..869cfc2 100644 (file)
@@ -19,6 +19,8 @@
 #ifndef InputHandler_h
 #define InputHandler_h
 
+#include "TextChecking.h"
+
 #include <BlackBerryPlatformInputEvents.h>
 
 #include <imf/events.h>
@@ -38,8 +40,11 @@ class HTMLInputElement;
 class HTMLSelectElement;
 class IntRect;
 class Node;
+class Range;
 class SpellChecker;
 class TextCheckingRequest;
+class VisiblePosition;
+class VisibleSelection;
 }
 
 namespace BlackBerry {
@@ -189,6 +194,9 @@ private:
     void learnText();
     void sendLearnTextDetails(const WTF::String&);
     int32_t convertTransactionIdToSequenceId(int32_t transactionId);
+    void spellCheckBlock(WebCore::VisibleSelection&, WebCore::TextCheckingProcessType);
+    PassRefPtr<WebCore::Range> getRangeForSpellCheckWithFineGranularity(WebCore::VisiblePosition startPosition, WebCore::VisiblePosition endPosition);
+    void cancelAllSpellCheckingRequests();
     WebCore::SpellChecker* getSpellChecker();
 
     WebPagePrivate* m_webPage;