Title: [148124] trunk/Source/WebCore
Revision
148124
Author
[email protected]
Date
2013-04-10 12:16:58 -0700 (Wed, 10 Apr 2013)

Log Message

Refactor Editor::markAndReplaceFor before fixing autocorrection bugs
https://bugs.webkit.org/show_bug.cgi?id=114344

Reviewed by Enrica Casucci.

This patch refactors Editor::markAndReplaceFor so that we can start fixing bugs in a sane state.
Extracted isAutomaticTextReplacementType and correctSpellcheckingPreservingTextCheckingParagraph,
and made convenience local variables const.

In particular, shouldMarkSpelling used to be assigned of false when shouldShowCorrectionPanel was true
in a middle of a function but this was removed in favor of explicitly checking this condition later
since shouldMarkSpelling was only referenced once after the assignment.

* editing/Editor.cpp:
(WebCore::isAutomaticTextReplacementType): Extracted.

(WebCore::correctSpellcheckingPreservingTextCheckingParagraph): Extracted.  Used highestAncestor
and rangeFromLocationAndLength to match the rest of the up-to-date editing code.

(WebCore::Editor::markAndReplaceFor): See above.

Modified Paths

Diff

Modified: trunk/Source/WebCore/ChangeLog (148123 => 148124)


--- trunk/Source/WebCore/ChangeLog	2013-04-10 19:11:20 UTC (rev 148123)
+++ trunk/Source/WebCore/ChangeLog	2013-04-10 19:16:58 UTC (rev 148124)
@@ -1,3 +1,26 @@
+2013-04-10  Ryosuke Niwa  <[email protected]>
+
+        Refactor Editor::markAndReplaceFor before fixing autocorrection bugs
+        https://bugs.webkit.org/show_bug.cgi?id=114344
+
+        Reviewed by Enrica Casucci.
+
+        This patch refactors Editor::markAndReplaceFor so that we can start fixing bugs in a sane state.
+        Extracted isAutomaticTextReplacementType and correctSpellcheckingPreservingTextCheckingParagraph,
+        and made convenience local variables const.
+
+        In particular, shouldMarkSpelling used to be assigned of false when shouldShowCorrectionPanel was true
+        in a middle of a function but this was removed in favor of explicitly checking this condition later
+        since shouldMarkSpelling was only referenced once after the assignment.
+
+        * editing/Editor.cpp:
+        (WebCore::isAutomaticTextReplacementType): Extracted.
+
+        (WebCore::correctSpellcheckingPreservingTextCheckingParagraph): Extracted.  Used highestAncestor
+        and rangeFromLocationAndLength to match the rest of the up-to-date editing code.
+
+        (WebCore::Editor::markAndReplaceFor): See above.
+
 2013-04-08  Anders Carlsson  <[email protected]>
 
         Remove unneeded headers from FrameLoader.h

Modified: trunk/Source/WebCore/editing/Editor.cpp (148123 => 148124)


--- trunk/Source/WebCore/editing/Editor.cpp	2013-04-10 19:11:20 UTC (rev 148123)
+++ trunk/Source/WebCore/editing/Editor.cpp	2013-04-10 19:16:58 UTC (rev 148124)
@@ -2133,6 +2133,43 @@
     markAndReplaceFor(request, results);
 }
 
+static bool isAutomaticTextReplacementType(TextCheckingType type)
+{
+    switch (type) {
+    case TextCheckingTypeNone:
+    case TextCheckingTypeSpelling:
+    case TextCheckingTypeGrammar:
+        return false;
+    case TextCheckingTypeLink:
+    case TextCheckingTypeQuote:
+    case TextCheckingTypeDash:
+    case TextCheckingTypeReplacement:
+    case TextCheckingTypeCorrection:
+    case TextCheckingTypeShowCorrectionPanel:
+        return true;
+    }
+    ASSERT_NOT_REACHED();
+    return false;
+}
+
+static void correctSpellcheckingPreservingTextCheckingParagraph(TextCheckingParagraph& paragraph, PassRefPtr<Range> rangeToReplace, const String& replacement, int resultLocation, int resultLength)
+{
+    ContainerNode* scope = toContainerNode(highestAncestor(paragraph.paragraphRange()->startContainer()));
+
+    size_t paragraphLocation;
+    size_t paragraphLength;
+    TextIterator::getLocationAndLengthFromRange(scope, paragraph.paragraphRange().get(), paragraphLocation, paragraphLength);
+
+    applyCommand(SpellingCorrectionCommand::create(rangeToReplace, replacement));
+
+    // TextCheckingParagraph may be orphaned after SpellingCorrectionCommand mutated DOM.
+    // See <rdar://10305315>, http://webkit.org/b/89526.
+
+    RefPtr<Range> newParagraphRange = TextIterator::rangeFromLocationAndLength(scope, paragraphLocation, paragraphLength + replacement.length() - resultLength);
+
+    paragraph = TextCheckingParagraph(TextIterator::subrange(newParagraphRange.get(), resultLocation, replacement.length()), newParagraphRange);
+}
+
 void Editor::markAndReplaceFor(PassRefPtr<SpellCheckRequest> request, const Vector<TextCheckingResult>& results)
 {
     ASSERT(request);
@@ -2140,12 +2177,12 @@
     TextCheckingTypeMask textCheckingOptions = request->data().mask();
     TextCheckingParagraph paragraph(request->checkingRange(), request->paragraphRange());
 
-    bool shouldMarkSpelling = textCheckingOptions & TextCheckingTypeSpelling;
-    bool shouldMarkGrammar = textCheckingOptions & TextCheckingTypeGrammar;
-    bool shouldMarkLink = textCheckingOptions & TextCheckingTypeLink;
-    bool shouldPerformReplacement = textCheckingOptions & TextCheckingTypeReplacement;
-    bool shouldShowCorrectionPanel = textCheckingOptions & TextCheckingTypeShowCorrectionPanel;
-    bool shouldCheckForCorrection = shouldShowCorrectionPanel || (textCheckingOptions & TextCheckingTypeCorrection);
+    const bool shouldMarkSpelling = textCheckingOptions & TextCheckingTypeSpelling;
+    const bool shouldMarkGrammar = textCheckingOptions & TextCheckingTypeGrammar;
+    const bool shouldMarkLink = textCheckingOptions & TextCheckingTypeLink;
+    const bool shouldPerformReplacement = textCheckingOptions & TextCheckingTypeReplacement;
+    const bool shouldShowCorrectionPanel = textCheckingOptions & TextCheckingTypeShowCorrectionPanel;
+    const bool shouldCheckForCorrection = shouldShowCorrectionPanel || (textCheckingOptions & TextCheckingTypeCorrection);
 
     // Expand the range to encompass entire paragraphs, since text checking needs that much context.
     int selectionOffset = 0;
@@ -2167,65 +2204,57 @@
         }
     }
 
-    // If this checking is only for showing correction panel, we shouldn't bother to mark misspellings.
-    if (shouldShowCorrectionPanel)
-        shouldMarkSpelling = false;
-
     int offsetDueToReplacement = 0;
 
     for (unsigned i = 0; i < results.size(); i++) {
-        int spellingRangeEndOffset = paragraph.checkingEnd() + offsetDueToReplacement;
-        const TextCheckingResult* result = &results[i];
-        int resultLocation = result->location + offsetDueToReplacement;
-        int resultLength = result->length;
-        bool resultEndsAtAmbiguousBoundary = ambiguousBoundaryOffset >= 0 && resultLocation + resultLength == ambiguousBoundaryOffset;
+        const int spellingRangeEndOffset = paragraph.checkingEnd() + offsetDueToReplacement;
+        const TextCheckingType resultType = results[i].type;
+        const int resultLocation = results[i].location + offsetDueToReplacement;
+        const int resultLength = results[i].length;
+        const String& replacement = results[i].replacement;
+        const bool resultEndsAtAmbiguousBoundary = ambiguousBoundaryOffset >= 0 && resultLocation + resultLength == ambiguousBoundaryOffset;
 
         // Only mark misspelling if:
         // 1. Current text checking isn't done for autocorrection, in which case shouldMarkSpelling is false.
         // 2. Result falls within spellingRange.
         // 3. The word in question doesn't end at an ambiguous boundary. For instance, we would not mark
         //    "wouldn'" as misspelled right after apostrophe is typed.
-        if (shouldMarkSpelling && result->type == TextCheckingTypeSpelling && resultLocation >= paragraph.checkingStart() && resultLocation + resultLength <= spellingRangeEndOffset && !resultEndsAtAmbiguousBoundary) {
+        if (shouldMarkSpelling && !shouldShowCorrectionPanel && resultType == TextCheckingTypeSpelling
+            && resultLocation >= paragraph.checkingStart() && resultLocation + resultLength <= spellingRangeEndOffset && !resultEndsAtAmbiguousBoundary) {
             ASSERT(resultLength > 0 && resultLocation >= 0);
             RefPtr<Range> misspellingRange = paragraph.subrange(resultLocation, resultLength);
             if (!m_alternativeTextController->isSpellingMarkerAllowed(misspellingRange))
                 continue;
-            misspellingRange->startContainer()->document()->markers()->addMarker(misspellingRange.get(), DocumentMarker::Spelling, result->replacement);
-        } else if (shouldMarkGrammar && result->type == TextCheckingTypeGrammar && paragraph.checkingRangeCovers(resultLocation, resultLength)) {
+            misspellingRange->startContainer()->document()->markers()->addMarker(misspellingRange.get(), DocumentMarker::Spelling, replacement);
+        } else if (shouldMarkGrammar && resultType == TextCheckingTypeGrammar && paragraph.checkingRangeCovers(resultLocation, resultLength)) {
             ASSERT(resultLength > 0 && resultLocation >= 0);
-            for (unsigned j = 0; j < result->details.size(); j++) {
-                const GrammarDetail* detail = &result->details[j];
-                ASSERT(detail->length > 0 && detail->location >= 0);
-                if (paragraph.checkingRangeCovers(resultLocation + detail->location, detail->length)) {
-                    RefPtr<Range> badGrammarRange = paragraph.subrange(resultLocation + detail->location, detail->length);
-                    badGrammarRange->startContainer()->document()->markers()->addMarker(badGrammarRange.get(), DocumentMarker::Grammar, detail->userDescription);
+            const Vector<GrammarDetail>& details = results[i].details;
+            for (unsigned j = 0; j < details.size(); j++) {
+                const GrammarDetail& detail = details[j];
+                ASSERT(detail.length > 0 && detail.location >= 0);
+                if (paragraph.checkingRangeCovers(resultLocation + detail.location, detail.length)) {
+                    RefPtr<Range> badGrammarRange = paragraph.subrange(resultLocation + detail.location, detail.length);
+                    badGrammarRange->startContainer()->document()->markers()->addMarker(badGrammarRange.get(), DocumentMarker::Grammar, detail.userDescription);
                 }
             }
         } else if (resultLocation + resultLength <= spellingRangeEndOffset && resultLocation + resultLength >= paragraph.checkingStart()
-                    && (result->type == TextCheckingTypeLink
-                    || result->type == TextCheckingTypeQuote
-                    || result->type == TextCheckingTypeDash
-                    || result->type == TextCheckingTypeReplacement
-                    || result->type == TextCheckingTypeCorrection)) {
+            && isAutomaticTextReplacementType(resultType)) {
             // In this case the result range just has to touch the spelling range, so we can handle replacing non-word text such as punctuation.
             ASSERT(resultLength > 0 && resultLocation >= 0);
 
-            if (shouldShowCorrectionPanel && (resultLocation + resultLength < spellingRangeEndOffset || result->type != TextCheckingTypeCorrection))
+            if (shouldShowCorrectionPanel && (resultLocation + resultLength < spellingRangeEndOffset || resultType != TextCheckingTypeCorrection))
                 continue;
 
-            int replacementLength = result->replacement.length();
-
             // Apply replacement if:
             // 1. The replacement length is non-zero.
             // 2. The result doesn't end at an ambiguous boundary.
             //    (FIXME: this is required until 6853027 is fixed and text checking can do this for us
-            bool doReplacement = replacementLength > 0 && !resultEndsAtAmbiguousBoundary;
+            bool doReplacement = replacement.length() > 0 && !resultEndsAtAmbiguousBoundary;
             RefPtr<Range> rangeToReplace = paragraph.subrange(resultLocation, resultLength);
-            VisibleSelection selectionToReplace(rangeToReplace.get(), DOWNSTREAM);
 
             // adding links should be done only immediately after they are typed
             int resultEnd = resultLocation + resultLength;
-            if (result->type == TextCheckingTypeLink
+            if (resultType == TextCheckingTypeLink
                 && (selectionOffset > resultEnd + 1 || selectionOffset <= resultLocation))
                 continue;
 
@@ -2233,7 +2262,7 @@
                 continue;
 
             String replacedString = plainText(rangeToReplace.get());
-            bool existingMarkersPermitReplacement = m_alternativeTextController->processMarkersOnTextToBeReplacedByResult(result, rangeToReplace.get(), replacedString);
+            const bool existingMarkersPermitReplacement = m_alternativeTextController->processMarkersOnTextToBeReplacedByResult(&results[i], rangeToReplace.get(), replacedString);
             if (!existingMarkersPermitReplacement)
                 continue;
 
@@ -2244,52 +2273,44 @@
                 // shouldShowCorrectionPanel can be true only when the panel is available.
                 if (resultLocation + resultLength == spellingRangeEndOffset) {
                     // We only show the correction panel on the last word.
-                    m_alternativeTextController->show(rangeToReplace, result->replacement);
+                    m_alternativeTextController->show(rangeToReplace, replacement);
                     break;
                 }
                 // If this function is called for showing correction panel, we ignore other correction or replacement.
                 continue;
             }
 
+            VisibleSelection selectionToReplace(rangeToReplace.get(), DOWNSTREAM);
             if (selectionToReplace != m_frame->selection()->selection()) {
                 if (!m_frame->selection()->shouldChangeSelection(selectionToReplace))
                     continue;
             }
 
-            if (result->type == TextCheckingTypeLink) {
+            if (resultType == TextCheckingTypeLink) {
                 m_frame->selection()->setSelection(selectionToReplace);
                 selectionChanged = true;
                 restoreSelectionAfterChange = false;
                 if (canEditRichly())
-                    applyCommand(CreateLinkCommand::create(m_frame->document(), result->replacement));
-            } else if (canEdit() && shouldInsertText(result->replacement, rangeToReplace.get(), EditorInsertActionTyped)) {
-                Node* root = paragraph.paragraphRange()->startContainer();
-                while (ContainerNode* parent = root->parentNode())
-                    root = parent;
+                    applyCommand(CreateLinkCommand::create(m_frame->document(), replacement));
+            } else if (canEdit() && shouldInsertText(replacement, rangeToReplace.get(), EditorInsertActionTyped)) {
+                correctSpellcheckingPreservingTextCheckingParagraph(paragraph, rangeToReplace, replacement, resultLocation, resultLength);
 
-                int paragraphStartIndex = TextIterator::rangeLength(Range::create(m_frame->document(), root, 0, paragraph.paragraphRange()->startContainer(), paragraph.paragraphRange()->startOffset()).get());
-                int paragraphLength = TextIterator::rangeLength(paragraph.paragraphRange().get());
-                applyCommand(SpellingCorrectionCommand::create(rangeToReplace, result->replacement));
-                // Recalculate newParagraphRange, since SpellingCorrectionCommand modifies the DOM, such that the original paragraph range is no longer valid. Radar: 10305315 Bugzilla: 89526
-                RefPtr<Range> newParagraphRange = TextIterator::rangeFromLocationAndLength(toContainerNode(root), paragraphStartIndex, paragraphLength + replacementLength - resultLength);
-                paragraph = TextCheckingParagraph(TextIterator::subrange(newParagraphRange.get(), resultLocation, replacementLength), newParagraphRange);
-                
                 if (AXObjectCache* cache = m_frame->document()->existingAXObjectCache()) {
                     if (Element* root = m_frame->selection()->selection().rootEditableElement())
                         cache->postNotification(root, AXObjectCache::AXAutocorrectionOccured, true);
                 }
 
                 selectionChanged = true;
-                offsetDueToReplacement += replacementLength - resultLength;
+                offsetDueToReplacement += replacement.length() - resultLength;
                 if (resultLocation < selectionOffset) {
-                    selectionOffset += replacementLength - resultLength;
+                    selectionOffset += replacement.length() - resultLength;
                     if (ambiguousBoundaryOffset >= 0)
                         ambiguousBoundaryOffset = selectionOffset - 1;
                 }
 
                 // Add a marker so that corrections can easily be undone and won't be re-corrected.
-                if (result->type == TextCheckingTypeCorrection)
-                    m_alternativeTextController->markCorrection(paragraph.subrange(resultLocation, replacementLength), replacedString);
+                if (resultType == TextCheckingTypeCorrection)
+                    m_alternativeTextController->markCorrection(paragraph.subrange(resultLocation, replacement.length()), replacedString);
             }
         }
     }
_______________________________________________
webkit-changes mailing list
[email protected]
https://lists.webkit.org/mailman/listinfo/webkit-changes

Reply via email to