Changeset 143391 in webkit


Ignore:
Timestamp:
Feb 19, 2013 3:15:42 PM (11 years ago)
Author:
nghanavatian@rim.com
Message:

[BlackBerry] Appropriately handle word wrapping in SpellingHandler
https://bugs.webkit.org/show_bug.cgi?id=110253

Reviewed by Rob Buis.

PR286001
Since we traverse through text by visual lines instead of blocks, word wrapping causes some
bad behavior. Changing the way we traverse text to jump by words instead of lines. This will
mean it takes longer to finish spellchecking, but the removal of any loops allows webkit
processing to continue. This gives priority to user actions while still completing a large
paragraph in a reasonable amount of time.

Internally reviewed by Mike Fenton

  • WebKitSupport/InputHandler.cpp:

(BlackBerry::WebKit::InputHandler::requestCheckingOfString):

  • WebKitSupport/SpellingHandler.cpp:

(BlackBerry::WebKit::SpellingHandler::createSpellCheckRequest):
(BlackBerry::WebKit::SpellingHandler::parseBlockForSpellChecking):
(BlackBerry::WebKit::SpellingHandler::getRangeForSpellCheckWithFineGranularity):
(BlackBerry::WebKit::SpellingHandler::startOfNextWord):
(WebKit):
(BlackBerry::WebKit::SpellingHandler::incrementByWord):
(BlackBerry::WebKit::SpellingHandler::doesWordWrap):

  • WebKitSupport/SpellingHandler.h:

(SpellingHandler):

Location:
trunk/Source/WebKit/blackberry
Files:
6 edited

Legend:

Unmodified
Added
Removed
  • trunk/Source/WebKit/blackberry/ChangeLog

    r143295 r143391  
     12013-02-19  Nima Ghanavatian  <nghanavatian@rim.com>
     2
     3        [BlackBerry] Appropriately handle word wrapping in SpellingHandler
     4        https://bugs.webkit.org/show_bug.cgi?id=110253
     5
     6        Reviewed by Rob Buis.
     7
     8        PR286001
     9        Since we traverse through text by visual lines instead of blocks, word wrapping causes some
     10        bad behavior. Changing the way we traverse text to jump by words instead of lines. This will
     11        mean it takes longer to finish spellchecking, but the removal of any loops allows webkit
     12        processing to continue. This gives priority to user actions while still completing a large
     13        paragraph in a reasonable amount of time.
     14
     15        Internally reviewed by Mike Fenton
     16
     17        * WebKitSupport/InputHandler.cpp:
     18        (BlackBerry::WebKit::InputHandler::requestCheckingOfString):
     19        * WebKitSupport/SpellingHandler.cpp:
     20        (BlackBerry::WebKit::SpellingHandler::createSpellCheckRequest):
     21        (BlackBerry::WebKit::SpellingHandler::parseBlockForSpellChecking):
     22        (BlackBerry::WebKit::SpellingHandler::getRangeForSpellCheckWithFineGranularity):
     23        (BlackBerry::WebKit::SpellingHandler::startOfNextWord):
     24        (WebKit):
     25        (BlackBerry::WebKit::SpellingHandler::incrementByWord):
     26        (BlackBerry::WebKit::SpellingHandler::doesWordWrap):
     27        * WebKitSupport/SpellingHandler.h:
     28        (SpellingHandler):
     29
    1302013-02-18  Simon Fraser  <simon.fraser@apple.com>
    231
  • trunk/Source/WebKit/blackberry/WebKitSupport/DOMSupport.cpp

    r140708 r143391  
    509509}
    510510
     511int offsetFromStartOfBlock(const VisiblePosition offset)
     512{
     513    RefPtr<Range> range = makeRange(startOfBlock(offset), offset);
     514    if (!range)
     515        return -1;
     516
     517    return range->text().latin1().length();
     518}
     519
     520VisibleSelection visibleSelectionForFocusedBlock(Element* element)
     521{
     522    int textLength = inputElementText(element).length();
     523
     524    if (DOMSupport::toTextControlElement(element)) {
     525        RenderTextControl* textRender = toRenderTextControl(element->renderer());
     526        if (!textRender)
     527            return VisibleSelection();
     528
     529        VisiblePosition startPosition = textRender->visiblePositionForIndex(0);
     530        VisiblePosition endPosition;
     531
     532        if (textLength)
     533            endPosition = textRender->visiblePositionForIndex(textLength);
     534        else
     535            endPosition = startPosition;
     536        return VisibleSelection(startPosition, endPosition);
     537    }
     538
     539    // Must be content editable, generate the range.
     540    RefPtr<Range> selectionRange = TextIterator::rangeFromLocationAndLength(element, 0, textLength);
     541
     542    if (!selectionRange)
     543        return VisibleSelection();
     544
     545    if (!textLength)
     546        return VisibleSelection(selectionRange->startPosition(), DOWNSTREAM);
     547
     548    return VisibleSelection(selectionRange->startPosition(), selectionRange->endPosition());
     549}
     550
    511551// This function is copied from WebCore/page/Page.cpp.
    512552Frame* incrementFrame(Frame* curr, bool forward, bool wrapFlag)
     
    524564PassRefPtr<Range> trimWhitespaceFromRange(VisiblePosition startPosition, VisiblePosition endPosition)
    525565{
    526     if (isEmptyRangeOrAllSpaces(startPosition, endPosition))
     566    if (startPosition == endPosition || isRangeTextAllWhitespace(startPosition, endPosition))
    527567        return 0;
    528568
     
    536576}
    537577
    538 bool isEmptyRangeOrAllSpaces(VisiblePosition startPosition, VisiblePosition endPosition)
    539 {
    540     if (startPosition == endPosition)
    541         return true;
    542 
     578bool isRangeTextAllWhitespace(VisiblePosition startPosition, VisiblePosition endPosition)
     579{
    543580    while (isWhitespace(startPosition.characterAfter())) {
    544581        startPosition = startPosition.next();
  • trunk/Source/WebKit/blackberry/WebKitSupport/DOMSupport.h

    r140562 r143391  
    9191
    9292WebCore::VisibleSelection visibleSelectionForClosestActualWordStart(const WebCore::VisibleSelection&);
     93WebCore::VisibleSelection visibleSelectionForFocusedBlock(WebCore::Element*);
     94int offsetFromStartOfBlock(const WebCore::VisiblePosition offset);
    9395
    9496WebCore::Frame* incrementFrame(WebCore::Frame* curr, bool forward, bool wrapFlag);
     
    9698PassRefPtr<WebCore::Range> trimWhitespaceFromRange(PassRefPtr<WebCore::Range>);
    9799PassRefPtr<WebCore::Range> trimWhitespaceFromRange(WebCore::VisiblePosition startPosition, WebCore::VisiblePosition endPosition);
    98 bool isEmptyRangeOrAllSpaces(WebCore::VisiblePosition, WebCore::VisiblePosition);
     100bool isRangeTextAllWhitespace(WebCore::VisiblePosition startPosition, WebCore::VisiblePosition endPosition);
    99101
    100102bool isFixedPositionOrHasFixedPositionAncestor(WebCore::RenderObject*);
  • trunk/Source/WebKit/blackberry/WebKitSupport/InputHandler.cpp

    r142884 r143391  
    613613    }
    614614
    615     if (requestLength > MaxSpellCheckingStringLength) {
     615    if (requestLength >= MaxSpellCheckingStringLength) {
    616616        // Batch requests which are generally created by us on focus, should not exceed this limit. Check that this is in fact of Incremental type.
    617617        ASSERT(textCheckingRequest->processType() == TextCheckingProcessIncremental);
     
    620620        m_request->didCancel();
    621621        if (m_currentFocusElement->document() && m_currentFocusElement->document()->frame() && m_currentFocusElement->document()->frame()->selection()) {
     622            VisiblePosition caretPosition = m_currentFocusElement->document()->frame()->selection()->start();
    622623            // 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
    623             // enter to finish composing a word and create a new line.
    624             VisiblePosition caretPosition = m_currentFocusElement->document()->frame()->selection()->start();
    625             VisibleSelection visibleSelection = VisibleSelection(previousLinePosition(caretPosition, caretPosition.lineDirectionPointForBlockDirectionNavigation()), caretPosition);
     624            // 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
     625            // to the start of any word which might be there.
     626            VisibleSelection visibleSelection = VisibleSelection(
     627                startOfWord(startOfLine(previousLinePosition(caretPosition, caretPosition.lineDirectionPointForBlockDirectionNavigation()))),
     628                endOfWord(endOfLine(caretPosition)));
    626629            m_spellingHandler->spellCheckTextBlock(visibleSelection, TextCheckingProcessIncremental);
    627630        }
     
    10931096
    10941097    // Spellcheck the field in its entirety.
    1095     VisibleSelection focusedBlock = DOMSupport::visibleSelectionForInputElement(element);
    1096     m_spellingHandler->spellCheckTextBlock(focusedBlock, TextCheckingProcessBatch);
     1098    const VisibleSelection visibleSelection = DOMSupport::visibleSelectionForFocusedBlock(element);
     1099    m_spellingHandler->spellCheckTextBlock(visibleSelection, TextCheckingProcessBatch);
    10971100
    10981101#ifdef ENABLE_SPELLING_LOG
  • trunk/Source/WebKit/blackberry/WebKitSupport/SpellingHandler.cpp

    r142019 r143391  
    4242}
    4343
    44 void SpellingHandler::spellCheckTextBlock(WebCore::VisibleSelection& visibleSelection, WebCore::TextCheckingProcessType textCheckingProcessType)
     44void SpellingHandler::spellCheckTextBlock(const WebCore::VisibleSelection& visibleSelection, WebCore::TextCheckingProcessType textCheckingProcessType)
    4545{
    46     SpellingLog(Platform::LogLevelInfo, "SpellingHandler::spellCheckTextBlock");
    47 
    4846    // Check if this request can be sent off in one message, or if it needs to be broken down.
    4947    RefPtr<Range> rangeForSpellChecking = visibleSelection.toNormalizedRange();
     
    5149        return;
    5250
     51    m_textCheckingProcessType = textCheckingProcessType;
     52
    5353    // Spellcheck Batch requests are used when focusing an element. During this time, we might have a lingering request
    5454    // from a previously focused element.
    55     if (textCheckingProcessType == TextCheckingProcessBatch) {
     55    if (m_textCheckingProcessType == TextCheckingProcessBatch) {
    5656        // If a previous request is being processed, stop it before continueing.
    5757        if (m_timer.isActive())
     
    6262
    6363    // If we have a batch request, try to send off the entire block.
    64     if (textCheckingProcessType == TextCheckingProcessBatch) {
     64    if (m_textCheckingProcessType == TextCheckingProcessBatch) {
    6565        // If total block text is under the limited amount, send the entire chunk.
    6666        if (rangeForSpellChecking->text().length() < MaxSpellCheckingStringLength) {
    67             createSpellCheckRequest(rangeForSpellChecking, TextCheckingProcessBatch);
     67            createSpellCheckRequest(rangeForSpellChecking);
    6868            return;
    6969        }
     
    7171
    7272    // 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;
     73    // Find the start and end of the region we're intending on checking
     74    m_startPosition = visibleSelection.visibleStart();
     75    m_endPosition = endOfWord(m_startPosition);
     76    m_endOfRange = visibleSelection.visibleEnd();
     77    m_cachedEndPosition = m_endOfRange;
    7678
    77     // Find the end of the region we're intending on checking.
    78     m_endOfRange = endOfLine(visibleSelection.visibleEnd());
    7979    m_timer.startOneShot(0);
    8080}
    8181
    82 void SpellingHandler::createSpellCheckRequest(PassRefPtr<WebCore::Range> rangeForSpellCheckingPtr, WebCore::TextCheckingProcessType textCheckingProcessType)
     82void SpellingHandler::createSpellCheckRequest(const PassRefPtr<WebCore::Range> rangeForSpellCheckingPtr)
    8383{
    8484    RefPtr<WebCore::Range> rangeForSpellChecking = rangeForSpellCheckingPtr;
     
    8787        return;
    8888
    89     SpellingLog(Platform::LogLevelInfo, "SpellingHandler::createSpellCheckRequest Substring text is '%s', of size %d"
    90         , rangeForSpellChecking->text().latin1().data()
    91         , rangeForSpellChecking->text().length());
    92 
    93     if (rangeForSpellChecking->text().length() >= MinSpellCheckingStringLength)
    94         m_inputHandler->callRequestCheckingFor(SpellCheckRequest::create(TextCheckingTypeSpelling, textCheckingProcessType, rangeForSpellChecking, rangeForSpellChecking));
     89    if (rangeForSpellChecking->text().length() >= MinSpellCheckingStringLength) {
     90        SpellingLog(Platform::LogLevelInfo, "SpellingHandler::createSpellCheckRequest Substring text is '%s', of size %d"
     91            , rangeForSpellChecking->text().latin1().data()
     92            , rangeForSpellChecking->text().length());
     93        m_inputHandler->callRequestCheckingFor(SpellCheckRequest::create(TextCheckingTypeSpelling, m_textCheckingProcessType, rangeForSpellChecking, rangeForSpellChecking));
     94    }
    9595}
    9696
    9797void SpellingHandler::parseBlockForSpellChecking(WebCore::Timer<SpellingHandler>*)
    9898{
    99     // Create a selection with the start and end points of the line, and convert to Range to create a SpellCheckRequest.
    100     RefPtr<Range> rangeForSpellChecking = makeRange(m_startOfCurrentLine, m_endOfCurrentLine);
     99    SpellingLog(Platform::LogLevelInfo, "SpellingHandler::parseBlockForSpellChecking m_startPosition = %d, m_endPosition = %d, m_cachedEndPosition = %d, m_endOfRange = %d"
     100        , DOMSupport::offsetFromStartOfBlock(m_startPosition)
     101        , DOMSupport::offsetFromStartOfBlock(m_endPosition)
     102        , DOMSupport::offsetFromStartOfBlock(m_cachedEndPosition)
     103        , DOMSupport::offsetFromStartOfBlock(m_endOfRange));
     104
     105    if (m_startPosition == m_endOfRange)
     106        return;
     107
     108    RefPtr<Range> rangeForSpellChecking = makeRange(m_startPosition, m_endPosition);
    101109    if (!rangeForSpellChecking) {
    102110        SpellingLog(Platform::LogLevelInfo, "SpellingHandler::parseBlockForSpellChecking Failed to set text range for spellchecking.");
     
    105113
    106114    if (rangeForSpellChecking->text().length() < MaxSpellCheckingStringLength) {
    107         if (m_endOfCurrentLine == m_endOfRange) {
    108             createSpellCheckRequest(rangeForSpellChecking, m_textCheckingProcessType);
     115        if (m_endPosition == m_endOfRange || m_cachedEndPosition == m_endPosition) {
     116            createSpellCheckRequest(rangeForSpellChecking);
    109117            m_isSpellCheckActive = false;
    110118            return;
    111119        }
    112         m_endOfCurrentLine = endOfLine(nextLinePosition(m_endOfCurrentLine, m_endOfCurrentLine.lineDirectionPointForBlockDirectionNavigation()));
     120
     121        incrementSentinels(false /* shouldIncrementStartPosition */);
    113122        m_timer.startOneShot(s_timeout);
    114123        return;
    115124    }
    116125
    117     // Iterate through words from the start of the line to the end.
    118     rangeForSpellChecking = getRangeForSpellCheckWithFineGranularity(m_startOfCurrentLine, m_endOfCurrentLine);
    119     if (!rangeForSpellChecking)
    120         return;
     126    // Create a spellcheck request with the substring if we have a range that is of size less than MaxSpellCheckingStringLength
     127    if (rangeForSpellChecking = handleOversizedRange())
     128        createSpellCheckRequest(rangeForSpellChecking);
    121129
    122     m_startOfCurrentLine = VisiblePosition(rangeForSpellChecking->endPosition());
    123     m_endOfCurrentLine = endOfLine(m_startOfCurrentLine);
    124 
    125     // Call spellcheck with substring.
    126     createSpellCheckRequest(rangeForSpellChecking, m_textCheckingProcessType);
    127130    if (isSpellCheckActive())
    128131        m_timer.startOneShot(s_timeout);
    129132}
    130133
    131 PassRefPtr<Range> SpellingHandler::getRangeForSpellCheckWithFineGranularity(WebCore::VisiblePosition startPosition, WebCore::VisiblePosition endPosition)
     134PassRefPtr<Range> SpellingHandler::handleOversizedRange()
    132135{
    133     SpellingLog(Platform::LogLevelWarn, "SpellingHandler::getRangeForSpellCheckWithFineGranularity");
    134     VisiblePosition endOfCurrentWord = endOfWord(startPosition);
    135     RefPtr<Range> currentRange;
     136    SpellingLog(Platform::LogLevelInfo, "SpellingHandler::handleOversizedRange");
    136137
    137     // Keep iterating until one of our cases is hit, or we've incremented the starting position right to the end.
    138     while (startPosition != endPosition) {
    139         currentRange = makeRange(startPosition, endOfCurrentWord);
    140         if (!currentRange)
    141             return 0;
     138    if (m_startPosition == m_cachedEndPosition || m_startPosition == startOfWord(m_endPosition, LeftWordIfOnBoundary)) {
     139        // Our first word has gone over the character limit. Increment the starting position past an uncheckable word.
     140        incrementSentinels(true /* shouldIncrementStartPosition */);
     141        return 0;
     142    }
    142143
    143         // Check the text length within this range.
    144         if (currentRange->text().length() >= MaxSpellCheckingStringLength) {
    145             // If this is not the first word, return a Range with end boundary set to the previous word.
    146             if (startOfWord(endOfCurrentWord, LeftWordIfOnBoundary) != startPosition && !DOMSupport::isEmptyRangeOrAllSpaces(startPosition, endOfCurrentWord)) {
    147                 // When a series of whitespace follows a word, previousWordPosition will jump passed all of them, and using LeftWordIfOnBoundary brings us to
    148                 // our starting position. Check for this case and use RightWordIfOnBoundary to jump back over the word.
    149                 VisiblePosition endOfPreviousWord = endOfWord(previousWordPosition(endOfCurrentWord), LeftWordIfOnBoundary);
    150                 if (startPosition == endOfPreviousWord)
    151                     return makeRange(startPosition, endOfWord(previousWordPosition(endOfCurrentWord), RightWordIfOnBoundary));
    152                 return makeRange(startPosition, endOfPreviousWord);
    153             }
    154             // Our first word has gone over the character limit. Increment the starting position past an uncheckable word.
    155             startPosition = endOfCurrentWord;
    156             endOfCurrentWord = endOfWord(nextWordPosition(endOfCurrentWord));
    157         } else if (endOfCurrentWord == endPosition)
    158             return makeRange(startPosition, endPosition); // Return the last segment if the end of our word lies at the end of the range.
    159         else
    160             endOfCurrentWord = endOfWord(nextWordPosition(endOfCurrentWord)); // Increment the current word.
     144    // If this is not the first word, return a Range with end boundary set to the previous word.
     145    RefPtr<Range> rangeToStartOfOversizedWord = makeRange(m_startPosition, m_cachedEndPosition);
     146    // We've created the range using the cached end position. Now increment the sentinals forward.
     147    // FIXME Incrementing the start/end positions outside of incrementSentinels
     148    m_startPosition = m_cachedEndPosition;
     149    m_endPosition = endOfWord(m_startPosition);
     150    return rangeToStartOfOversizedWord;
     151}
     152
     153void SpellingHandler::incrementSentinels(bool shouldIncrementStartPosition)
     154{
     155    SpellingLog(Platform::LogLevelInfo, "SpellingHandler::incrementSentinels shouldIncrementStartPosition %s", shouldIncrementStartPosition ? "true" : "false");
     156
     157    if (shouldIncrementStartPosition)
     158        m_startPosition = m_endPosition;
     159
     160    VisiblePosition nextWord = nextWordPosition(m_endPosition);
     161    VisiblePosition startOfNextWord = startOfWord(nextWord, LeftWordIfOnBoundary);
     162    if (DOMSupport::isRangeTextAllWhitespace(m_endPosition, startOfNextWord)) {
     163        m_cachedEndPosition = startOfNextWord;
     164        m_endPosition = endOfWord(startOfNextWord);
     165        return;
    161166    }
    162     // This will return an range with no string, but allows processing to continue
    163     return makeRange(startPosition, endPosition);
     167
     168    m_cachedEndPosition = m_endPosition;
     169    m_endPosition = endOfWord(nextWord);
    164170}
    165171
  • trunk/Source/WebKit/blackberry/WebKitSupport/SpellingHandler.h

    r141041 r143391  
    3131    ~SpellingHandler();
    3232
    33     void spellCheckTextBlock(WebCore::VisibleSelection&, WebCore::TextCheckingProcessType);
     33    void spellCheckTextBlock(const WebCore::VisibleSelection&, const WebCore::TextCheckingProcessType);
    3434    bool isSpellCheckActive() { return m_isSpellCheckActive; }
    3535    void setSpellCheckActive(bool active) { m_isSpellCheckActive = active; }
    3636
    3737private:
    38     void createSpellCheckRequest(PassRefPtr<WebCore::Range> rangeForSpellCheckingPtr, WebCore::TextCheckingProcessType);
     38    void createSpellCheckRequest(PassRefPtr<WebCore::Range> rangeForSpellCheckingPtr);
    3939    void parseBlockForSpellChecking(WebCore::Timer<SpellingHandler>*);
    40     PassRefPtr<WebCore::Range> getRangeForSpellCheckWithFineGranularity(WebCore::VisiblePosition startPosition, WebCore::VisiblePosition endPosition);
     40    void incrementSentinels(bool shouldIncrementStartPosition);
     41    PassRefPtr<WebCore::Range> handleOversizedRange();
    4142
    4243    InputHandler* m_inputHandler;
    4344
    4445    WebCore::Timer<SpellingHandler> m_timer;
    45     WebCore::VisiblePosition m_startOfCurrentLine;
    46     WebCore::VisiblePosition m_endOfCurrentLine;
    47     WebCore::VisibleSelection m_endOfRange;
     46    WebCore::VisiblePosition m_startPosition;
     47    WebCore::VisiblePosition m_endPosition;
     48    WebCore::VisiblePosition m_cachedEndPosition;
     49    WebCore::VisiblePosition m_endOfRange;
    4850    WebCore::TextCheckingProcessType m_textCheckingProcessType;
    4951    bool m_isSpellCheckActive;
Note: See TracChangeset for help on using the changeset viewer.