diff options
| author | Simon Hausmann <simon.hausmann@nokia.com> | 2012-05-07 11:21:11 +0200 |
|---|---|---|
| committer | Simon Hausmann <simon.hausmann@nokia.com> | 2012-05-07 11:21:11 +0200 |
| commit | 2cf6c8816a73e0132bd8fa3b509d62d7c51b6e47 (patch) | |
| tree | 988e8c5b116dd0466244ae2fe5af8ee9be926d76 /Source/WebCore/editing | |
| parent | dd91e772430dc294e3bf478c119ef8d43c0a3358 (diff) | |
| download | qtwebkit-2cf6c8816a73e0132bd8fa3b509d62d7c51b6e47.tar.gz | |
Imported WebKit commit 7e538425aa020340619e927792f3d895061fb54b (http://svn.webkit.org/repository/webkit/trunk@116286)
Diffstat (limited to 'Source/WebCore/editing')
55 files changed, 2031 insertions, 1229 deletions
diff --git a/Source/WebCore/editing/SpellingCorrectionController.cpp b/Source/WebCore/editing/AlternativeTextController.cpp index 4ffdc7f95..8d09ff1b9 100644 --- a/Source/WebCore/editing/SpellingCorrectionController.cpp +++ b/Source/WebCore/editing/AlternativeTextController.cpp @@ -25,11 +25,14 @@ */ #include "config.h" -#include "SpellingCorrectionController.h" +#include "AlternativeTextController.h" +#include "DictationAlternative.h" +#include "Document.h" #include "DocumentMarkerController.h" #include "EditCommand.h" #include "EditorClient.h" +#include "Event.h" #include "FloatQuad.h" #include "Frame.h" #include "FrameView.h" @@ -37,6 +40,7 @@ #include "SpellingCorrectionCommand.h" #include "TextCheckerClient.h" #include "TextCheckingHelper.h" +#include "TextEvent.h" #include "TextIterator.h" #include "VisibleSelection.h" #include "htmlediting.h" @@ -48,6 +52,22 @@ namespace WebCore { using namespace std; using namespace WTF; +class AutocorrectionAlternativeDetails : public AlternativeTextDetails { +public: + static PassRefPtr<AutocorrectionAlternativeDetails> create(const String& replacementString) + { + return adoptRef(new AutocorrectionAlternativeDetails(replacementString)); + } + + const String& replacementString() const { return m_replacementString; } +private: + AutocorrectionAlternativeDetails(const String& replacementString) + : m_replacementString(replacementString) + { } + + String m_replacementString; +}; + #if USE(AUTOCORRECTION_PANEL) static const Vector<DocumentMarker::MarkerType>& markerTypesForAutocorrection() @@ -85,48 +105,48 @@ static bool markersHaveIdenticalDescription(const Vector<DocumentMarker*>& marke return true; } -SpellingCorrectionController::SpellingCorrectionController(Frame* frame) - : m_frame(frame) - , m_correctionPanelTimer(this, &SpellingCorrectionController::correctionPanelTimerFired) +AlternativeTextController::AlternativeTextController(Frame* frame) + : m_timer(this, &AlternativeTextController::timerFired) + , m_frame(frame) { } -SpellingCorrectionController::~SpellingCorrectionController() +AlternativeTextController::~AlternativeTextController() { - dismiss(ReasonForDismissingCorrectionPanelIgnored); + dismiss(ReasonForDismissingAlternativeTextIgnored); } -void SpellingCorrectionController::startCorrectionPanelTimer(CorrectionPanelInfo::PanelType type) +void AlternativeTextController::startAlternativeTextUITimer(AlternativeTextType type) { const double correctionPanelTimerInterval = 0.3; if (!isAutomaticSpellingCorrectionEnabled()) return; // If type is PanelTypeReversion, then the new range has been set. So we shouldn't clear it. - if (type == CorrectionPanelInfo::PanelTypeCorrection) - m_correctionPanelInfo.rangeToBeReplaced.clear(); - m_correctionPanelInfo.panelType = type; - m_correctionPanelTimer.startOneShot(correctionPanelTimerInterval); + if (type == AlternativeTextTypeCorrection) + m_alternativeTextInfo.rangeWithAlternative.clear(); + m_alternativeTextInfo.type = type; + m_timer.startOneShot(correctionPanelTimerInterval); } -void SpellingCorrectionController::stopCorrectionPanelTimer() +void AlternativeTextController::stopAlternativeTextUITimer() { - m_correctionPanelTimer.stop(); - m_correctionPanelInfo.rangeToBeReplaced.clear(); + m_timer.stop(); + m_alternativeTextInfo.rangeWithAlternative.clear(); } -void SpellingCorrectionController::stopPendingCorrection(const VisibleSelection& oldSelection) +void AlternativeTextController::stopPendingCorrection(const VisibleSelection& oldSelection) { // Make sure there's no pending autocorrection before we call markMisspellingsAndBadGrammar() below. VisibleSelection currentSelection(m_frame->selection()->selection()); if (currentSelection == oldSelection) return; - stopCorrectionPanelTimer(); - dismiss(ReasonForDismissingCorrectionPanelIgnored); + stopAlternativeTextUITimer(); + dismiss(ReasonForDismissingAlternativeTextIgnored); } -void SpellingCorrectionController::applyPendingCorrection(const VisibleSelection& selectionAfterTyping) +void AlternativeTextController::applyPendingCorrection(const VisibleSelection& selectionAfterTyping) { // Apply pending autocorrection before next round of spell checking. bool doApplyCorrection = true; @@ -138,87 +158,88 @@ void SpellingCorrectionController::applyPendingCorrection(const VisibleSelection doApplyCorrection = false; } if (doApplyCorrection) - handleCorrectionPanelResult(dismissSoon(ReasonForDismissingCorrectionPanelAccepted)); + handleAlternativeTextUIResult(dismissSoon(ReasonForDismissingAlternativeTextAccepted)); else - m_correctionPanelInfo.rangeToBeReplaced.clear(); + m_alternativeTextInfo.rangeWithAlternative.clear(); } -bool SpellingCorrectionController::hasPendingCorrection() const +bool AlternativeTextController::hasPendingCorrection() const { - return m_correctionPanelInfo.rangeToBeReplaced; + return m_alternativeTextInfo.rangeWithAlternative; } -bool SpellingCorrectionController::isSpellingMarkerAllowed(PassRefPtr<Range> misspellingRange) const +bool AlternativeTextController::isSpellingMarkerAllowed(PassRefPtr<Range> misspellingRange) const { return !m_frame->document()->markers()->hasMarkers(misspellingRange.get(), DocumentMarker::SpellCheckingExemption); } -void SpellingCorrectionController::show(PassRefPtr<Range> rangeToReplace, const String& replacement) +void AlternativeTextController::show(PassRefPtr<Range> rangeToReplace, const String& replacement) { FloatRect boundingBox = rootViewRectForRange(rangeToReplace.get()); if (boundingBox.isEmpty()) return; - m_correctionPanelInfo.replacedString = plainText(rangeToReplace.get()); - m_correctionPanelInfo.rangeToBeReplaced = rangeToReplace; - m_correctionPanelInfo.replacementString = replacement; - m_correctionPanelInfo.isActive = true; - client()->showCorrectionPanel(m_correctionPanelInfo.panelType, boundingBox, m_correctionPanelInfo.replacedString, replacement, Vector<String>()); + m_alternativeTextInfo.originalText = plainText(rangeToReplace.get()); + m_alternativeTextInfo.rangeWithAlternative = rangeToReplace; + m_alternativeTextInfo.details = AutocorrectionAlternativeDetails::create(replacement); + m_alternativeTextInfo.isActive = true; + if (AlternativeTextClient* client = alternativeTextClient()) + client->showCorrectionAlternative(m_alternativeTextInfo.type, boundingBox, m_alternativeTextInfo.originalText, replacement, Vector<String>()); } -void SpellingCorrectionController::handleCancelOperation() +void AlternativeTextController::handleCancelOperation() { - if (!m_correctionPanelInfo.isActive) + if (!m_alternativeTextInfo.isActive) return; - m_correctionPanelInfo.isActive = false; - dismiss(ReasonForDismissingCorrectionPanelCancelled); + m_alternativeTextInfo.isActive = false; + dismiss(ReasonForDismissingAlternativeTextCancelled); } -void SpellingCorrectionController::dismiss(ReasonForDismissingCorrectionPanel reasonForDismissing) +void AlternativeTextController::dismiss(ReasonForDismissingAlternativeText reasonForDismissing) { - if (!m_correctionPanelInfo.isActive) + if (!m_alternativeTextInfo.isActive) return; - m_correctionPanelInfo.isActive = false; - m_correctionPanelIsDismissedByEditor = true; - if (client()) - client()->dismissCorrectionPanel(reasonForDismissing); + m_alternativeTextInfo.isActive = false; + m_isDismissedByEditing = true; + if (AlternativeTextClient* client = alternativeTextClient()) + client->dismissAlternative(reasonForDismissing); } -String SpellingCorrectionController::dismissSoon(ReasonForDismissingCorrectionPanel reasonForDismissing) +String AlternativeTextController::dismissSoon(ReasonForDismissingAlternativeText reasonForDismissing) { - if (!m_correctionPanelInfo.isActive) + if (!m_alternativeTextInfo.isActive) return String(); - m_correctionPanelInfo.isActive = false; - m_correctionPanelIsDismissedByEditor = true; - if (!client()) - return String(); - return client()->dismissCorrectionPanelSoon(reasonForDismissing); + m_alternativeTextInfo.isActive = false; + m_isDismissedByEditing = true; + if (AlternativeTextClient* client = alternativeTextClient()) + return client->dismissAlternativeSoon(reasonForDismissing); + return String(); } -void SpellingCorrectionController::applyCorrectionPanelInfo(const Vector<DocumentMarker::MarkerType>& markerTypesToAdd) +void AlternativeTextController::applyAlternativeText(const String& alternative, const Vector<DocumentMarker::MarkerType>& markerTypesToAdd) { - if (!m_correctionPanelInfo.rangeToBeReplaced) + if (!m_alternativeTextInfo.rangeWithAlternative) return; ExceptionCode ec = 0; - RefPtr<Range> paragraphRangeContainingCorrection = m_correctionPanelInfo.rangeToBeReplaced->cloneRange(ec); + RefPtr<Range> paragraphRangeContainingCorrection = m_alternativeTextInfo.rangeWithAlternative->cloneRange(ec); if (ec) return; - setStart(paragraphRangeContainingCorrection.get(), startOfParagraph(m_correctionPanelInfo.rangeToBeReplaced->startPosition())); - setEnd(paragraphRangeContainingCorrection.get(), endOfParagraph(m_correctionPanelInfo.rangeToBeReplaced->endPosition())); + setStart(paragraphRangeContainingCorrection.get(), startOfParagraph(m_alternativeTextInfo.rangeWithAlternative->startPosition())); + setEnd(paragraphRangeContainingCorrection.get(), endOfParagraph(m_alternativeTextInfo.rangeWithAlternative->endPosition())); - // After we replace the word at range rangeToBeReplaced, we need to add markers to that range. - // However, once the replacement took place, the value of rangeToBeReplaced is not valid anymore. - // So before we carry out the replacement, we need to store the start position of rangeToBeReplaced + // After we replace the word at range rangeWithAlternative, we need to add markers to that range. + // However, once the replacement took place, the value of rangeWithAlternative is not valid anymore. + // So before we carry out the replacement, we need to store the start position of rangeWithAlternative // relative to the start position of the containing paragraph. We use correctionStartOffsetInParagraph // to store this value. In order to obtain this offset, we need to first create a range - // which spans from the start of paragraph to the start position of rangeToBeReplaced. + // which spans from the start of paragraph to the start position of rangeWithAlternative. RefPtr<Range> correctionStartOffsetInParagraphAsRange = Range::create(paragraphRangeContainingCorrection->startContainer(ec)->document(), paragraphRangeContainingCorrection->startPosition(), paragraphRangeContainingCorrection->startPosition()); if (ec) return; - Position startPositionOfRangeToBeReplaced = m_correctionPanelInfo.rangeToBeReplaced->startPosition(); - correctionStartOffsetInParagraphAsRange->setEnd(startPositionOfRangeToBeReplaced.containerNode(), startPositionOfRangeToBeReplaced.computeOffsetInContainerNode(), ec); + Position startPositionOfrangeWithAlternative = m_alternativeTextInfo.rangeWithAlternative->startPosition(); + correctionStartOffsetInParagraphAsRange->setEnd(startPositionOfrangeWithAlternative.containerNode(), startPositionOfrangeWithAlternative.computeOffsetInContainerNode(), ec); if (ec) return; @@ -226,14 +247,14 @@ void SpellingCorrectionController::applyCorrectionPanelInfo(const Vector<Documen int correctionStartOffsetInParagraph = TextIterator::rangeLength(correctionStartOffsetInParagraphAsRange.get()); // Clone the range, since the caller of this method may want to keep the original range around. - RefPtr<Range> rangeToBeReplaced = m_correctionPanelInfo.rangeToBeReplaced->cloneRange(ec); - applyCommand(SpellingCorrectionCommand::create(rangeToBeReplaced, m_correctionPanelInfo.replacementString)); + RefPtr<Range> rangeWithAlternative = m_alternativeTextInfo.rangeWithAlternative->cloneRange(ec); + applyCommand(SpellingCorrectionCommand::create(rangeWithAlternative, alternative)); setEnd(paragraphRangeContainingCorrection.get(), m_frame->selection()->selection().start()); - RefPtr<Range> replacementRange = TextIterator::subrange(paragraphRangeContainingCorrection.get(), correctionStartOffsetInParagraph, m_correctionPanelInfo.replacementString.length()); + RefPtr<Range> replacementRange = TextIterator::subrange(paragraphRangeContainingCorrection.get(), correctionStartOffsetInParagraph, alternative.length()); String newText = plainText(replacementRange.get()); // Check to see if replacement succeeded. - if (newText != m_correctionPanelInfo.replacementString) + if (newText != alternative) return; DocumentMarkerController* markers = replacementRange->startContainer()->document()->markers(); @@ -241,36 +262,37 @@ void SpellingCorrectionController::applyCorrectionPanelInfo(const Vector<Documen for (size_t i = 0; i < size; ++i) { DocumentMarker::MarkerType markerType = markerTypesToAdd[i]; String description; - if (m_correctionPanelInfo.panelType != CorrectionPanelInfo::PanelTypeReversion && (markerType == DocumentMarker::Replacement || markerType == DocumentMarker::Autocorrected)) - description = m_correctionPanelInfo.replacedString; + if (m_alternativeTextInfo.type != AlternativeTextTypeReversion && (markerType == DocumentMarker::Replacement || markerType == DocumentMarker::Autocorrected)) + description = m_alternativeTextInfo.originalText; markers->addMarker(replacementRange.get(), markerType, description); } } -bool SpellingCorrectionController::applyAutocorrectionBeforeTypingIfAppropriate() +bool AlternativeTextController::applyAutocorrectionBeforeTypingIfAppropriate() { - if (!m_correctionPanelInfo.rangeToBeReplaced || !m_correctionPanelInfo.isActive) + if (!m_alternativeTextInfo.rangeWithAlternative || !m_alternativeTextInfo.isActive) return false; - if (m_correctionPanelInfo.panelType != CorrectionPanelInfo::PanelTypeCorrection) + if (m_alternativeTextInfo.type != AlternativeTextTypeCorrection) return false; Position caretPosition = m_frame->selection()->selection().start(); - if (m_correctionPanelInfo.rangeToBeReplaced->endPosition() == caretPosition) { - handleCorrectionPanelResult(dismissSoon(ReasonForDismissingCorrectionPanelAccepted)); + if (m_alternativeTextInfo.rangeWithAlternative->endPosition() == caretPosition) { + handleAlternativeTextUIResult(dismissSoon(ReasonForDismissingAlternativeTextAccepted)); return true; } // Pending correction should always be where caret is. But in case this is not always true, we still want to dismiss the panel without accepting the correction. - ASSERT(m_correctionPanelInfo.rangeToBeReplaced->endPosition() == caretPosition); - dismiss(ReasonForDismissingCorrectionPanelIgnored); + ASSERT(m_alternativeTextInfo.rangeWithAlternative->endPosition() == caretPosition); + dismiss(ReasonForDismissingAlternativeTextIgnored); return false; } -void SpellingCorrectionController::respondToUnappliedSpellCorrection(const VisibleSelection& selectionOfCorrected, const String& corrected, const String& correction) +void AlternativeTextController::respondToUnappliedSpellCorrection(const VisibleSelection& selectionOfCorrected, const String& corrected, const String& correction) { - client()->recordAutocorrectionResponse(EditorClient::AutocorrectionReverted, corrected, correction); + if (AlternativeTextClient* client = alternativeTextClient()) + client->recordAutocorrectionResponse(AutocorrectionReverted, corrected, correction); m_frame->document()->updateLayout(); m_frame->selection()->setSelection(selectionOfCorrected, FrameSelection::CloseTyping | FrameSelection::ClearTypingStyle | FrameSelection::SpellCorrectionTriggered); RefPtr<Range> range = Range::create(m_frame->document(), m_frame->selection()->selection().start(), m_frame->selection()->selection().end()); @@ -281,11 +303,11 @@ void SpellingCorrectionController::respondToUnappliedSpellCorrection(const Visib markers->addMarker(range.get(), DocumentMarker::SpellCheckingExemption); } -void SpellingCorrectionController::correctionPanelTimerFired(Timer<SpellingCorrectionController>*) +void AlternativeTextController::timerFired(Timer<AlternativeTextController>*) { - m_correctionPanelIsDismissedByEditor = false; - switch (m_correctionPanelInfo.panelType) { - case CorrectionPanelInfo::PanelTypeCorrection: { + m_isDismissedByEditing = false; + switch (m_alternativeTextInfo.type) { + case AlternativeTextTypeCorrection: { VisibleSelection selection(m_frame->selection()->selection()); VisiblePosition start(selection.start(), selection.affinity()); VisiblePosition p = startOfWord(start, LeftWordIfOnBoundary); @@ -293,76 +315,78 @@ void SpellingCorrectionController::correctionPanelTimerFired(Timer<SpellingCorre m_frame->editor()->markAllMisspellingsAndBadGrammarInRanges(TextCheckingTypeSpelling | TextCheckingTypeShowCorrectionPanel, adjacentWords.toNormalizedRange().get(), 0); } break; - case CorrectionPanelInfo::PanelTypeReversion: { - if (!m_correctionPanelInfo.rangeToBeReplaced) + case AlternativeTextTypeReversion: { + if (!m_alternativeTextInfo.rangeWithAlternative) break; - m_correctionPanelInfo.isActive = true; - m_correctionPanelInfo.replacedString = plainText(m_correctionPanelInfo.rangeToBeReplaced.get()); - FloatRect boundingBox = rootViewRectForRange(m_correctionPanelInfo.rangeToBeReplaced.get()); - if (!boundingBox.isEmpty()) - client()->showCorrectionPanel(m_correctionPanelInfo.panelType, boundingBox, m_correctionPanelInfo.replacedString, m_correctionPanelInfo.replacementString, Vector<String>()); + m_alternativeTextInfo.isActive = true; + m_alternativeTextInfo.originalText = plainText(m_alternativeTextInfo.rangeWithAlternative.get()); + FloatRect boundingBox = rootViewRectForRange(m_alternativeTextInfo.rangeWithAlternative.get()); + if (!boundingBox.isEmpty()) { + const AutocorrectionAlternativeDetails* details = static_cast<const AutocorrectionAlternativeDetails*>(m_alternativeTextInfo.details.get()); + if (AlternativeTextClient* client = alternativeTextClient()) + client->showCorrectionAlternative(m_alternativeTextInfo.type, boundingBox, m_alternativeTextInfo.originalText, details->replacementString(), Vector<String>()); + } } break; - case CorrectionPanelInfo::PanelTypeSpellingSuggestions: { - if (!m_correctionPanelInfo.rangeToBeReplaced || plainText(m_correctionPanelInfo.rangeToBeReplaced.get()) != m_correctionPanelInfo.replacedString) + case AlternativeTextTypeSpellingSuggestions: { + if (!m_alternativeTextInfo.rangeWithAlternative || plainText(m_alternativeTextInfo.rangeWithAlternative.get()) != m_alternativeTextInfo.originalText) break; - String paragraphText = plainText(TextCheckingParagraph(m_correctionPanelInfo.rangeToBeReplaced).paragraphRange().get()); + String paragraphText = plainText(TextCheckingParagraph(m_alternativeTextInfo.rangeWithAlternative).paragraphRange().get()); Vector<String> suggestions; - textChecker()->getGuessesForWord(m_correctionPanelInfo.replacedString, paragraphText, suggestions); + textChecker()->getGuessesForWord(m_alternativeTextInfo.originalText, paragraphText, suggestions); if (suggestions.isEmpty()) { - m_correctionPanelInfo.rangeToBeReplaced.clear(); + m_alternativeTextInfo.rangeWithAlternative.clear(); break; } String topSuggestion = suggestions.first(); suggestions.remove(0); - m_correctionPanelInfo.isActive = true; - FloatRect boundingBox = rootViewRectForRange(m_correctionPanelInfo.rangeToBeReplaced.get()); - if (!boundingBox.isEmpty()) - client()->showCorrectionPanel(m_correctionPanelInfo.panelType, boundingBox, m_correctionPanelInfo.replacedString, topSuggestion, suggestions); + m_alternativeTextInfo.isActive = true; + FloatRect boundingBox = rootViewRectForRange(m_alternativeTextInfo.rangeWithAlternative.get()); + if (!boundingBox.isEmpty()) { + if (AlternativeTextClient* client = alternativeTextClient()) + client->showCorrectionAlternative(m_alternativeTextInfo.type, boundingBox, m_alternativeTextInfo.originalText, topSuggestion, suggestions); + } } break; } } -void SpellingCorrectionController::handleCorrectionPanelResult(const String& correction) +void AlternativeTextController::handleAlternativeTextUIResult(const String& result) { - Range* replacedRange = m_correctionPanelInfo.rangeToBeReplaced.get(); + Range* replacedRange = m_alternativeTextInfo.rangeWithAlternative.get(); if (!replacedRange || m_frame->document() != replacedRange->ownerDocument()) return; - String currentWord = plainText(m_correctionPanelInfo.rangeToBeReplaced.get()); + String currentWord = plainText(m_alternativeTextInfo.rangeWithAlternative.get()); // Check to see if the word we are about to correct has been changed between timer firing and callback being triggered. - if (currentWord != m_correctionPanelInfo.replacedString) + if (currentWord != m_alternativeTextInfo.originalText) return; - m_correctionPanelInfo.isActive = false; + m_alternativeTextInfo.isActive = false; - switch (m_correctionPanelInfo.panelType) { - case CorrectionPanelInfo::PanelTypeCorrection: - if (correction.length()) { - m_correctionPanelInfo.replacementString = correction; - applyCorrectionPanelInfo(markerTypesForAutocorrection()); - } else if (!m_correctionPanelIsDismissedByEditor) - replacedRange->startContainer()->document()->markers()->addMarker(replacedRange, DocumentMarker::RejectedCorrection, m_correctionPanelInfo.replacedString); + switch (m_alternativeTextInfo.type) { + case AlternativeTextTypeCorrection: + if (result.length()) + applyAlternativeText(result, markerTypesForAutocorrection()); + else if (!m_isDismissedByEditing) + replacedRange->startContainer()->document()->markers()->addMarker(replacedRange, DocumentMarker::RejectedCorrection, m_alternativeTextInfo.originalText); break; - case CorrectionPanelInfo::PanelTypeReversion: - case CorrectionPanelInfo::PanelTypeSpellingSuggestions: - if (correction.length()) { - m_correctionPanelInfo.replacementString = correction; - applyCorrectionPanelInfo(markerTypesForReplacement()); - } + case AlternativeTextTypeReversion: + case AlternativeTextTypeSpellingSuggestions: + if (result.length()) + applyAlternativeText(result, markerTypesForReplacement()); break; } - m_correctionPanelInfo.rangeToBeReplaced.clear(); + m_alternativeTextInfo.rangeWithAlternative.clear(); } -bool SpellingCorrectionController::isAutomaticSpellingCorrectionEnabled() +bool AlternativeTextController::isAutomaticSpellingCorrectionEnabled() { - return client() && client()->isAutomaticSpellingCorrectionEnabled(); + return editorClient() && editorClient()->isAutomaticSpellingCorrectionEnabled(); } -FloatRect SpellingCorrectionController::rootViewRectForRange(const Range* range) const +FloatRect AlternativeTextController::rootViewRectForRange(const Range* range) const { FrameView* view = m_frame->view(); if (!view) @@ -376,7 +400,7 @@ FloatRect SpellingCorrectionController::rootViewRectForRange(const Range* range) return view->contentsToRootView(IntRect(boundingRect)); } -void SpellingCorrectionController::respondToChangedSelection(const VisibleSelection& oldSelection) +void AlternativeTextController::respondToChangedSelection(const VisibleSelection& oldSelection) { VisibleSelection currentSelection(m_frame->selection()->selection()); // When user moves caret to the end of autocorrected word and pauses, we show the panel @@ -414,20 +438,20 @@ void SpellingCorrectionController::respondToChangedSelection(const VisibleSelect if (!currentWord.length()) continue; - m_correctionPanelInfo.rangeToBeReplaced = wordRange; - m_correctionPanelInfo.replacedString = currentWord; + m_alternativeTextInfo.rangeWithAlternative = wordRange; + m_alternativeTextInfo.originalText = currentWord; if (marker->type() == DocumentMarker::Spelling) - startCorrectionPanelTimer(CorrectionPanelInfo::PanelTypeSpellingSuggestions); + startAlternativeTextUITimer(AlternativeTextTypeSpellingSuggestions); else { - m_correctionPanelInfo.replacementString = marker->description(); - startCorrectionPanelTimer(CorrectionPanelInfo::PanelTypeReversion); + m_alternativeTextInfo.details = AutocorrectionAlternativeDetails::create(marker->description()); + startAlternativeTextUITimer(AlternativeTextTypeReversion); } break; } } -void SpellingCorrectionController::respondToAppliedEditing(CompositeEditCommand* command) +void AlternativeTextController::respondToAppliedEditing(CompositeEditCommand* command) { if (command->isTopLevelCommand() && !command->shouldRetainAutocorrectionIndicator()) m_frame->document()->markers()->removeMarkers(DocumentMarker::CorrectionIndicator); @@ -436,7 +460,7 @@ void SpellingCorrectionController::respondToAppliedEditing(CompositeEditCommand* m_originalStringForLastDeletedAutocorrection = String(); } -void SpellingCorrectionController::respondToUnappliedEditing(EditCommandComposition* command) +void AlternativeTextController::respondToUnappliedEditing(EditCommandComposition* command) { if (!command->wasCreateLinkCommand()) return; @@ -448,35 +472,47 @@ void SpellingCorrectionController::respondToUnappliedEditing(EditCommandComposit markers->addMarker(range.get(), DocumentMarker::SpellCheckingExemption); } -EditorClient* SpellingCorrectionController::client() +AlternativeTextClient* AlternativeTextController::alternativeTextClient() { + if (!m_frame) + return 0; + + return m_frame->page() ? m_frame->page()->alternativeTextClient() : 0; +} + +EditorClient* AlternativeTextController::editorClient() +{ + if (!m_frame) + return 0; + return m_frame->page() ? m_frame->page()->editorClient() : 0; } -TextCheckerClient* SpellingCorrectionController::textChecker() +TextCheckerClient* AlternativeTextController::textChecker() { - if (EditorClient* owner = client()) + if (EditorClient* owner = editorClient()) return owner->textChecker(); return 0; } -void SpellingCorrectionController::recordAutocorrectionResponseReversed(const String& replacedString, const String& replacementString) +void AlternativeTextController::recordAutocorrectionResponseReversed(const String& replacedString, const String& replacementString) { - client()->recordAutocorrectionResponse(EditorClient::AutocorrectionReverted, replacedString, replacementString); + if (AlternativeTextClient* client = alternativeTextClient()) + client->recordAutocorrectionResponse(AutocorrectionReverted, replacedString, replacementString); } -void SpellingCorrectionController::recordAutocorrectionResponseReversed(const String& replacedString, PassRefPtr<Range> replacementRange) +void AlternativeTextController::recordAutocorrectionResponseReversed(const String& replacedString, PassRefPtr<Range> replacementRange) { recordAutocorrectionResponseReversed(replacedString, plainText(replacementRange.get())); } -void SpellingCorrectionController::markReversed(PassRefPtr<Range> changedRange) +void AlternativeTextController::markReversed(PassRefPtr<Range> changedRange) { changedRange->startContainer()->document()->markers()->removeMarkers(changedRange.get(), DocumentMarker::Autocorrected, DocumentMarkerController::RemovePartiallyOverlappingMarker); changedRange->startContainer()->document()->markers()->addMarker(changedRange.get(), DocumentMarker::SpellCheckingExemption); } -void SpellingCorrectionController::markCorrection(PassRefPtr<Range> replacedRange, const String& replacedString) +void AlternativeTextController::markCorrection(PassRefPtr<Range> replacedRange, const String& replacedString) { Vector<DocumentMarker::MarkerType> markerTypesToAdd = markerTypesForAutocorrection(); DocumentMarkerController* markers = replacedRange->startContainer()->document()->markers(); @@ -489,7 +525,7 @@ void SpellingCorrectionController::markCorrection(PassRefPtr<Range> replacedRang } } -void SpellingCorrectionController::recordSpellcheckerResponseForModifiedCorrection(Range* rangeOfCorrection, const String& corrected, const String& correction) +void AlternativeTextController::recordSpellcheckerResponseForModifiedCorrection(Range* rangeOfCorrection, const String& corrected, const String& correction) { if (!rangeOfCorrection) return; @@ -497,23 +533,26 @@ void SpellingCorrectionController::recordSpellcheckerResponseForModifiedCorrecti Vector<DocumentMarker*> correctedOnceMarkers = markers->markersInRange(rangeOfCorrection, DocumentMarker::Autocorrected); if (correctedOnceMarkers.isEmpty()) return; - - // Spelling corrected text has been edited. We need to determine whether user has reverted it to original text or - // edited it to something else, and notify spellchecker accordingly. - if (markersHaveIdenticalDescription(correctedOnceMarkers) && correctedOnceMarkers[0]->description() == corrected) - client()->recordAutocorrectionResponse(EditorClient::AutocorrectionReverted, corrected, correction); - else - client()->recordAutocorrectionResponse(EditorClient::AutocorrectionEdited, corrected, correction); + + if (AlternativeTextClient* client = alternativeTextClient()) { + // Spelling corrected text has been edited. We need to determine whether user has reverted it to original text or + // edited it to something else, and notify spellchecker accordingly. + if (markersHaveIdenticalDescription(correctedOnceMarkers) && correctedOnceMarkers[0]->description() == corrected) + client->recordAutocorrectionResponse(AutocorrectionReverted, corrected, correction); + else + client->recordAutocorrectionResponse(AutocorrectionEdited, corrected, correction); + } + markers->removeMarkers(rangeOfCorrection, DocumentMarker::Autocorrected, DocumentMarkerController::RemovePartiallyOverlappingMarker); } -void SpellingCorrectionController::deletedAutocorrectionAtPosition(const Position& position, const String& originalString) +void AlternativeTextController::deletedAutocorrectionAtPosition(const Position& position, const String& originalString) { m_originalStringForLastDeletedAutocorrection = originalString; m_positionForLastDeletedAutocorrection = position; } -void SpellingCorrectionController::markPrecedingWhitespaceForDeletedAutocorrectionAfterCommand(EditCommand* command) +void AlternativeTextController::markPrecedingWhitespaceForDeletedAutocorrectionAfterCommand(EditCommand* command) { Position endOfSelection = command->endingSelection().end(); if (endOfSelection != m_positionForLastDeletedAutocorrection) @@ -534,19 +573,19 @@ void SpellingCorrectionController::markPrecedingWhitespaceForDeletedAutocorrecti m_frame->document()->markers()->addMarker(precedingCharacterRange.get(), DocumentMarker::DeletedAutocorrection, m_originalStringForLastDeletedAutocorrection); } -bool SpellingCorrectionController::processMarkersOnTextToBeReplacedByResult(const TextCheckingResult* result, Range* rangeToBeReplaced, const String& stringToBeReplaced) +bool AlternativeTextController::processMarkersOnTextToBeReplacedByResult(const TextCheckingResult* result, Range* rangeWithAlternative, const String& stringToBeReplaced) { DocumentMarkerController* markerController = m_frame->document()->markers(); - if (markerController->hasMarkers(rangeToBeReplaced, DocumentMarker::Replacement)) { + if (markerController->hasMarkers(rangeWithAlternative, DocumentMarker::Replacement)) { if (result->type == TextCheckingTypeCorrection) - recordSpellcheckerResponseForModifiedCorrection(rangeToBeReplaced, stringToBeReplaced, result->replacement); + recordSpellcheckerResponseForModifiedCorrection(rangeWithAlternative, stringToBeReplaced, result->replacement); return false; } - if (markerController->hasMarkers(rangeToBeReplaced, DocumentMarker::RejectedCorrection)) + if (markerController->hasMarkers(rangeWithAlternative, DocumentMarker::RejectedCorrection)) return false; - Position beginningOfRange = rangeToBeReplaced->startPosition(); + Position beginningOfRange = rangeWithAlternative->startPosition(); Position precedingCharacterPosition = beginningOfRange.previous(); RefPtr<Range> precedingCharacterRange = Range::create(m_frame->document(), precedingCharacterPosition, beginningOfRange); @@ -562,4 +601,27 @@ bool SpellingCorrectionController::processMarkersOnTextToBeReplacedByResult(cons #endif +bool AlternativeTextController::insertDictatedText(const String& text, const Vector<DictationAlternative>& dictationAlternatives, Event* triggeringEvent) +{ + if (!m_frame) + return false; + EventTarget* target; + if (triggeringEvent) + target = triggeringEvent->target(); + else + target = eventTargetNodeForDocument(m_frame->document()); + if (!target) + return false; + + if (FrameView* view = m_frame->view()) + view->resetDeferredRepaintDelay(); + + RefPtr<TextEvent> event = TextEvent::createForDictation(m_frame->domWindow(), text, dictationAlternatives); + event->setUnderlyingEvent(triggeringEvent); + + ExceptionCode ec; + target->dispatchEvent(event, ec); + return event->defaultHandled(); +} + } // namespace WebCore diff --git a/Source/WebCore/editing/SpellingCorrectionController.h b/Source/WebCore/editing/AlternativeTextController.h index dfd0608b2..28449efd9 100644 --- a/Source/WebCore/editing/SpellingCorrectionController.h +++ b/Source/WebCore/editing/AlternativeTextController.h @@ -23,9 +23,10 @@ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ -#ifndef SpellingCorrectionController_h -#define SpellingCorrectionController_h +#ifndef AlternativeTextController_h +#define AlternativeTextController_h +#include "AlternativeTextClient.h" #include "DocumentMarker.h" #include "Range.h" #include "TextChecking.h" @@ -40,51 +41,67 @@ class CompositeEditCommand; class EditorClient; class EditCommand; class EditCommandComposition; +class Event; class Frame; class TextCheckerClient; +struct DictationAlternative; -struct CorrectionPanelInfo { - enum PanelType { - PanelTypeCorrection = 0, - PanelTypeReversion, - PanelTypeSpellingSuggestions - }; - - RefPtr<Range> rangeToBeReplaced; - String replacedString; - String replacementString; - PanelType panelType; - bool isActive; +class AlternativeTextDetails : public RefCounted<AlternativeTextDetails> { +public: + AlternativeTextDetails() { } + virtual ~AlternativeTextDetails() { } }; -struct TextCheckingResult; +struct AlternativeTextInfo { + RefPtr<Range> rangeWithAlternative; + bool isActive; + AlternativeTextType type; + String originalText; + RefPtr<AlternativeTextDetails> details; +}; -enum ReasonForDismissingCorrectionPanel { - ReasonForDismissingCorrectionPanelCancelled = 0, - ReasonForDismissingCorrectionPanelIgnored, - ReasonForDismissingCorrectionPanelAccepted +class DictationMarkerDetails : public DocumentMarkerDetails { +public: + static PassRefPtr<DictationMarkerDetails> create(const String& originalText, uint64_t dictationContext) + { + return adoptRef(new DictationMarkerDetails(originalText, dictationContext)); + } + const String& originalText() const { return m_originalText; } + uint64_t dictationContext() const { return m_dictationContext; } +private: + DictationMarkerDetails(const String& originalText, uint64_t dictationContext) + : m_dictationContext(dictationContext) + , m_originalText(originalText) + { } + + uint64_t m_dictationContext; + String m_originalText; }; +struct TextCheckingResult; + #if USE(AUTOCORRECTION_PANEL) -#define UNLESS_ENABLED(functionBody) ; +// These backslashes are for making style checker happy. +#define UNLESS_ENABLED(functionBody) \ +;\ + #else #define UNLESS_ENABLED(functionBody) functionBody #endif -class SpellingCorrectionController { - WTF_MAKE_NONCOPYABLE(SpellingCorrectionController); WTF_MAKE_FAST_ALLOCATED; +class AlternativeTextController { + WTF_MAKE_NONCOPYABLE(AlternativeTextController); + WTF_MAKE_FAST_ALLOCATED; public: - SpellingCorrectionController(Frame*) UNLESS_ENABLED({}) - ~SpellingCorrectionController() UNLESS_ENABLED({}) + AlternativeTextController(Frame*) UNLESS_ENABLED({ }) + ~AlternativeTextController() UNLESS_ENABLED({ }) - void startCorrectionPanelTimer(CorrectionPanelInfo::PanelType) UNLESS_ENABLED({}) - void stopCorrectionPanelTimer() UNLESS_ENABLED({}) + void startAlternativeTextUITimer(AlternativeTextType) UNLESS_ENABLED({ }) + void stopAlternativeTextUITimer() UNLESS_ENABLED({ }) - void dismiss(ReasonForDismissingCorrectionPanel) UNLESS_ENABLED({}) - String dismissSoon(ReasonForDismissingCorrectionPanel) UNLESS_ENABLED({ return String(); }) + void dismiss(ReasonForDismissingAlternativeText) UNLESS_ENABLED({ }) void show(PassRefPtr<Range> rangeToReplace, const String& replacement) UNLESS_ENABLED({ UNUSED_PARAM(rangeToReplace); UNUSED_PARAM(replacement); }) - void applyCorrectionPanelInfo(const Vector<DocumentMarker::MarkerType>&) UNLESS_ENABLED({}) // Return true if correction was applied, false otherwise. bool applyAutocorrectionBeforeTypingIfAppropriate() UNLESS_ENABLED({ return false; }) @@ -96,27 +113,31 @@ public: void stopPendingCorrection(const VisibleSelection& oldSelection) UNLESS_ENABLED({ UNUSED_PARAM(oldSelection); }) void applyPendingCorrection(const VisibleSelection& selectionAfterTyping) UNLESS_ENABLED({ UNUSED_PARAM(selectionAfterTyping); }) - void handleCorrectionPanelResult(const String& correction) UNLESS_ENABLED({ UNUSED_PARAM(correction); }) - void handleCancelOperation() UNLESS_ENABLED({}) + void handleAlternativeTextUIResult(const String& result) UNLESS_ENABLED({ UNUSED_PARAM(result); }) + void handleCancelOperation() UNLESS_ENABLED({ }) bool hasPendingCorrection() const UNLESS_ENABLED({ return false; }) bool isSpellingMarkerAllowed(PassRefPtr<Range> misspellingRange) const UNLESS_ENABLED({ UNUSED_PARAM(misspellingRange); return true; }) bool isAutomaticSpellingCorrectionEnabled() UNLESS_ENABLED({ return false; }) bool shouldRemoveMarkersUponEditing(); - void correctionPanelTimerFired(Timer<SpellingCorrectionController>*) UNLESS_ENABLED({}) void recordAutocorrectionResponseReversed(const String& replacedString, PassRefPtr<Range> replacementRange) UNLESS_ENABLED({ UNUSED_PARAM(replacedString); UNUSED_PARAM(replacementRange); }) void markReversed(PassRefPtr<Range> changedRange) UNLESS_ENABLED({ UNUSED_PARAM(changedRange); }) void markCorrection(PassRefPtr<Range> replacedRange, const String& replacedString) UNLESS_ENABLED({ UNUSED_PARAM(replacedRange); UNUSED_PARAM(replacedString); }) - void recordSpellcheckerResponseForModifiedCorrection(Range* rangeOfCorrection, const String& corrected, const String& correction) UNLESS_ENABLED({ UNUSED_PARAM(rangeOfCorrection); UNUSED_PARAM(corrected); UNUSED_PARAM(correction); }) // This function returns false if the replacement should not be carried out. bool processMarkersOnTextToBeReplacedByResult(const TextCheckingResult*, Range* rangeToBeReplaced, const String& stringToBeReplaced) UNLESS_ENABLED({ UNUSED_PARAM(rangeToBeReplaced); UNUSED_PARAM(stringToBeReplaced); return true; }); void deletedAutocorrectionAtPosition(const Position&, const String& originalString) UNLESS_ENABLED({ UNUSED_PARAM(originalString); }) -#if USE(AUTOCORRECTION_PANEL) + bool insertDictatedText(const String&, const Vector<DictationAlternative>&, Event*); + private: +#if USE(AUTOCORRECTION_PANEL) + String dismissSoon(ReasonForDismissingAlternativeText); + void applyAlternativeText(const String& alternative, const Vector<DocumentMarker::MarkerType>&); + void timerFired(Timer<AlternativeTextController>*); void recordAutocorrectionResponseReversed(const String& replacedString, const String& replacementString); + void recordSpellcheckerResponseForModifiedCorrection(Range* rangeOfCorrection, const String& corrected, const String& correction); bool shouldStartTimerFor(const DocumentMarker* marker, int endOffset) const { @@ -124,26 +145,27 @@ private: || marker->type() == DocumentMarker::Spelling) && static_cast<int>(marker->endOffset()) == endOffset); } - EditorClient* client(); + AlternativeTextClient* alternativeTextClient(); + EditorClient* editorClient(); + TextCheckerClient* textChecker(); FloatRect rootViewRectForRange(const Range*) const; void markPrecedingWhitespaceForDeletedAutocorrectionAfterCommand(EditCommand*); - EditorClient* m_client; - Frame* m_frame; - - Timer<SpellingCorrectionController> m_correctionPanelTimer; - CorrectionPanelInfo m_correctionPanelInfo; - bool m_correctionPanelIsDismissedByEditor; + Timer<AlternativeTextController> m_timer; + AlternativeTextInfo m_alternativeTextInfo; + bool m_isDismissedByEditing; String m_originalStringForLastDeletedAutocorrection; Position m_positionForLastDeletedAutocorrection; #endif + + Frame* m_frame; }; #undef UNLESS_ENABLED -inline bool SpellingCorrectionController::shouldRemoveMarkersUponEditing() +inline bool AlternativeTextController::shouldRemoveMarkersUponEditing() { #if USE(MARKER_REMOVAL_UPON_EDITING) return true; @@ -154,4 +176,4 @@ inline bool SpellingCorrectionController::shouldRemoveMarkersUponEditing() } // namespace WebCore -#endif // SpellingCorrectionController_h +#endif // AlternativeTextController_h diff --git a/Source/WebCore/editing/ApplyBlockElementCommand.cpp b/Source/WebCore/editing/ApplyBlockElementCommand.cpp index b32641be7..952dc097b 100644 --- a/Source/WebCore/editing/ApplyBlockElementCommand.cpp +++ b/Source/WebCore/editing/ApplyBlockElementCommand.cpp @@ -56,14 +56,14 @@ ApplyBlockElementCommand::ApplyBlockElementCommand(Document* document, const Qua void ApplyBlockElementCommand::doApply() { - if (!endingSelection().isNonOrphanedCaretOrRange()) - return; - if (!endingSelection().rootEditableElement()) return; VisiblePosition visibleEnd = endingSelection().visibleEnd(); VisiblePosition visibleStart = endingSelection().visibleStart(); + if (visibleStart.isNull() || visibleStart.isOrphan() || visibleEnd.isNull() || visibleEnd.isOrphan()) + return; + // When a selection ends at the start of a paragraph, we rarely paint // the selection gap before that paragraph, because there often is no gap. // In a case like this, it's not obvious to the user that the selection diff --git a/Source/WebCore/editing/ApplyStyleCommand.cpp b/Source/WebCore/editing/ApplyStyleCommand.cpp index 7c6347c41..403ca570d 100644 --- a/Source/WebCore/editing/ApplyStyleCommand.cpp +++ b/Source/WebCore/editing/ApplyStyleCommand.cpp @@ -30,7 +30,6 @@ #include "CSSParser.h" #include "CSSProperty.h" #include "CSSPropertyNames.h" -#include "CSSStyleSelector.h" #include "CSSValueKeywords.h" #include "CSSValueList.h" #include "Document.h" @@ -45,6 +44,7 @@ #include "RenderObject.h" #include "RenderText.h" #include "StylePropertySet.h" +#include "StyleResolver.h" #include "Text.h" #include "TextIterator.h" #include "htmlediting.h" @@ -704,12 +704,13 @@ static bool containsNonEditableRegion(Node* node) return false; } -void ApplyStyleCommand::applyInlineStyleToNodeRange(EditingStyle* style, Node* node, Node* pastEndNode) +void ApplyStyleCommand::applyInlineStyleToNodeRange(EditingStyle* style, PassRefPtr<Node> startNode, PassRefPtr<Node> pastEndNode) { if (m_removeOnly) return; - for (RefPtr<Node> next; node && node != pastEndNode; node = next.get()) { + RefPtr<Node> node = startNode; + for (RefPtr<Node> next; node && node != pastEndNode; node = next) { next = node->traverseNextNode(); if (!node->renderer() || !node->rendererIsEditable()) @@ -719,10 +720,10 @@ void ApplyStyleCommand::applyInlineStyleToNodeRange(EditingStyle* style, Node* n // This is a plaintext-only region. Only proceed if it's fully selected. // pastEndNode is the node after the last fully selected node, so if it's inside node then // node isn't fully selected. - if (pastEndNode && pastEndNode->isDescendantOf(node)) + if (pastEndNode && pastEndNode->isDescendantOf(node.get())) break; // Add to this element's inline style and skip over its contents. - HTMLElement* element = toHTMLElement(node); + HTMLElement* element = toHTMLElement(node.get()); RefPtr<StylePropertySet> inlineStyle = element->ensureInlineStyle()->copy(); inlineStyle->merge(style->style()); setNodeAttribute(element, styleAttr, inlineStyle->asText()); @@ -730,13 +731,13 @@ void ApplyStyleCommand::applyInlineStyleToNodeRange(EditingStyle* style, Node* n continue; } - if (isBlock(node)) + if (isBlock(node.get())) continue; if (node->childNodeCount()) { - if (node->contains(pastEndNode) || containsNonEditableRegion(node) || !node->parentNode()->rendererIsEditable()) + if (node->contains(pastEndNode.get()) || containsNonEditableRegion(node.get()) || !node->parentNode()->rendererIsEditable()) continue; - if (editingIgnoresContent(node)) { + if (editingIgnoresContent(node.get())) { next = node->traverseNextSibling(); continue; } @@ -745,7 +746,7 @@ void ApplyStyleCommand::applyInlineStyleToNodeRange(EditingStyle* style, Node* n RefPtr<Node> runStart = node; RefPtr<Node> runEnd = node; Node* sibling = node->nextSibling(); - while (sibling && sibling != pastEndNode && !sibling->contains(pastEndNode) + while (sibling && sibling != pastEndNode && !sibling->contains(pastEndNode.get()) && (!isBlock(sibling) || sibling->hasTagName(brTag)) && !containsNonEditableRegion(sibling)) { runEnd = sibling; @@ -1062,8 +1063,7 @@ void ApplyStyleCommand::removeInlineStyle(EditingStyle* style, const Position &s // Since elem must have been fully selected, and it is at the end // of the selection, it is clear we can set the new e offset to // the max range offset of prev. - ASSERT(s.anchorType() == Position::PositionIsAfterAnchor - || s.offsetInContainerNode() >= lastOffsetInNode(s.containerNode())); + ASSERT(s.anchorType() == Position::PositionIsAfterAnchor || !offsetIsBeforeLastNodeOffset(s.offsetInContainerNode(), s.containerNode())); e = lastPositionInOrAfterNode(prev.get()); } } @@ -1226,7 +1226,7 @@ bool ApplyStyleCommand::mergeEndWithNextIfIdentical(const Position& start, const int endOffset = end.computeOffsetInContainerNode(); if (isAtomicNode(endNode)) { - if (endOffset < lastOffsetInNode(endNode)) + if (offsetIsBeforeLastNodeOffset(endOffset, endNode)) return false; unsigned parentLastOffset = end.deprecatedNode()->parentNode()->childNodes()->length() - 1; diff --git a/Source/WebCore/editing/ApplyStyleCommand.h b/Source/WebCore/editing/ApplyStyleCommand.h index 6b6e6fdef..58bc19b9e 100644 --- a/Source/WebCore/editing/ApplyStyleCommand.h +++ b/Source/WebCore/editing/ApplyStyleCommand.h @@ -94,7 +94,7 @@ private: void applyRelativeFontStyleChange(EditingStyle*); void applyInlineStyle(EditingStyle*); void fixRangeAndApplyInlineStyle(EditingStyle*, const Position& start, const Position& end); - void applyInlineStyleToNodeRange(EditingStyle*, Node* startNode, Node* pastEndNode); + void applyInlineStyleToNodeRange(EditingStyle*, PassRefPtr<Node> startNode, PassRefPtr<Node> pastEndNode); void addBlockStyle(const StyleChange&, HTMLElement*); void addInlineStyleIfNeeded(EditingStyle*, PassRefPtr<Node> start, PassRefPtr<Node> end, EAddStyledElement = AddStyledElement); void splitTextAtStart(const Position& start, const Position& end); diff --git a/Source/WebCore/editing/CompositeEditCommand.cpp b/Source/WebCore/editing/CompositeEditCommand.cpp index 830eacf6f..8f112c067 100644 --- a/Source/WebCore/editing/CompositeEditCommand.cpp +++ b/Source/WebCore/editing/CompositeEditCommand.cpp @@ -292,9 +292,9 @@ void CompositeEditCommand::removeStyledElement(PassRefPtr<Element> element) applyCommandToComposite(ApplyStyleCommand::create(element, true)); } -void CompositeEditCommand::insertParagraphSeparator(bool useDefaultParagraphElement) +void CompositeEditCommand::insertParagraphSeparator(bool useDefaultParagraphElement, bool pasteBlockqutoeIntoUnquotedArea) { - applyCommandToComposite(InsertParagraphSeparatorCommand::create(document(), useDefaultParagraphElement)); + applyCommandToComposite(InsertParagraphSeparatorCommand::create(document(), useDefaultParagraphElement, pasteBlockqutoeIntoUnquotedArea)); } void CompositeEditCommand::insertLineBreak() @@ -304,11 +304,14 @@ void CompositeEditCommand::insertLineBreak() bool CompositeEditCommand::isRemovableBlock(const Node* node) { + if (!node->hasTagName(divTag)) + return false; + Node* parentNode = node->parentNode(); - if ((parentNode && parentNode->firstChild() != parentNode->lastChild()) || !node->hasTagName(divTag)) + if (parentNode && parentNode->firstChild() != parentNode->lastChild()) return false; - if (!node->isElementNode() || !toElement(node)->hasAttributes()) + if (!toElement(node)->hasAttributes()) return true; return false; @@ -403,6 +406,14 @@ void CompositeEditCommand::removeNodeAndPruneAncestors(PassRefPtr<Node> node) prune(parent.release()); } +void CompositeEditCommand::updatePositionForNodeRemovalPreservingChildren(Position& position, Node* node) +{ + int offset = (position.anchorType() == Position::PositionIsOffsetInAnchor) ? position.offsetInContainerNode() : 0; + updatePositionForNodeRemoval(position, node); + if (offset) + position.moveToOffset(offset); +} + HTMLElement* CompositeEditCommand::replaceElementWithSpanPreservingChildrenAndAttributes(PassRefPtr<HTMLElement> node) { // It would also be possible to implement all of ReplaceNodeWithSpanCommand @@ -1267,21 +1278,23 @@ bool CompositeEditCommand::breakOutOfEmptyListItem() if (!newBlock) newBlock = createDefaultParagraphElement(document()); - if (emptyListItem->renderer()->nextSibling()) { - // If emptyListItem follows another list item, split the list node. - if (emptyListItem->renderer()->previousSibling()) + Node* previousListNode = emptyListItem->isElementNode() ? toElement(emptyListItem)->previousElementSibling(): emptyListItem->previousSibling(); + Node* nextListNode = emptyListItem->isElementNode() ? toElement(emptyListItem)->nextElementSibling(): emptyListItem->nextSibling(); + if (isListItem(nextListNode) || isListElement(nextListNode)) { + // If emptyListItem follows another list item or nested list, split the list node. + if (isListItem(previousListNode) || isListElement(previousListNode)) splitElement(static_cast<Element*>(listNode), emptyListItem); - // If emptyListItem is followed by other list item, then insert newBlock before the list node. + // If emptyListItem is followed by other list item or nested list, then insert newBlock before the list node. // Because we have splitted the element, emptyListItem is the first element in the list node. // i.e. insert newBlock before ul or ol whose first element is emptyListItem insertNodeBefore(newBlock, listNode); removeNode(emptyListItem); } else { - // When emptyListItem does not follow any list item, insert newBlock after the enclosing list node. + // When emptyListItem does not follow any list item or nested list, insert newBlock after the enclosing list node. // Remove the enclosing node if emptyListItem is the only child; otherwise just remove emptyListItem. insertNodeAfter(newBlock, listNode); - removeNode(emptyListItem->renderer()->previousSibling() ? emptyListItem : listNode); + removeNode(isListItem(previousListNode) || isListElement(previousListNode) ? emptyListItem : listNode); } appendBlockPlaceholder(newBlock); diff --git a/Source/WebCore/editing/CompositeEditCommand.h b/Source/WebCore/editing/CompositeEditCommand.h index f7d174943..7384cac36 100644 --- a/Source/WebCore/editing/CompositeEditCommand.h +++ b/Source/WebCore/editing/CompositeEditCommand.h @@ -82,6 +82,7 @@ public: virtual bool isCreateLinkCommand() const; virtual bool isTypingCommand() const; + virtual bool isDictationCommand() const { return false; } virtual bool preservesTypingStyle() const; virtual bool shouldRetainAutocorrectionIndicator() const; virtual void setShouldRetainAutocorrectionIndicator(bool); @@ -108,7 +109,7 @@ protected: void insertNodeAt(PassRefPtr<Node>, const Position&); void insertNodeAtTabSpanPosition(PassRefPtr<Node>, const Position&); void insertNodeBefore(PassRefPtr<Node>, PassRefPtr<Node> refChild); - void insertParagraphSeparator(bool useDefaultParagraphElement = false); + void insertParagraphSeparator(bool useDefaultParagraphElement = false, bool pasteBlockqutoeIntoUnquotedArea = false); void insertLineBreak(); void insertTextIntoNode(PassRefPtr<Text>, unsigned offset, const String& text); void mergeIdenticalElements(PassRefPtr<Element>, PassRefPtr<Element>); @@ -125,6 +126,7 @@ protected: HTMLElement* replaceElementWithSpanPreservingChildrenAndAttributes(PassRefPtr<HTMLElement>); void removeNodePreservingChildren(PassRefPtr<Node>); void removeNodeAndPruneAncestors(PassRefPtr<Node>); + void updatePositionForNodeRemovalPreservingChildren(Position&, Node*); void prune(PassRefPtr<Node>); void replaceTextInNode(PassRefPtr<Text>, unsigned offset, unsigned count, const String& replacementText); Position replaceSelectedTextInNode(const String&); diff --git a/Source/WebCore/editing/DeleteButtonController.cpp b/Source/WebCore/editing/DeleteButtonController.cpp index e8b05eca5..5bb05e6a0 100644 --- a/Source/WebCore/editing/DeleteButtonController.cpp +++ b/Source/WebCore/editing/DeleteButtonController.cpp @@ -92,7 +92,7 @@ static bool isDeletableElement(const Node* node) return false; RenderBox* box = toRenderBox(renderer); - LayoutRect borderBoundingBox = box->borderBoundingBox(); + IntRect borderBoundingBox = box->borderBoundingBox(); if (borderBoundingBox.width() < minimumWidth || borderBoundingBox.height() < minimumHeight) return false; diff --git a/Source/WebCore/editing/DeleteSelectionCommand.cpp b/Source/WebCore/editing/DeleteSelectionCommand.cpp index 5f5dfcfbd..227235edf 100644 --- a/Source/WebCore/editing/DeleteSelectionCommand.cpp +++ b/Source/WebCore/editing/DeleteSelectionCommand.cpp @@ -363,7 +363,7 @@ void DeleteSelectionCommand::removeNode(PassRefPtr<Node> node) // Make sure empty cell has some height, if a placeholder can be inserted. document()->updateLayoutIgnorePendingStylesheets(); RenderObject *r = node->renderer(); - if (r && r->isTableCell() && toRenderTableCell(r)->contentHeight(IncludeIntrinsicPadding) <= 0) { + if (r && r->isTableCell() && toRenderTableCell(r)->contentHeight() <= 0) { Position firstEditablePosition = firstEditablePositionInNode(node.get()); if (firstEditablePosition.isNotNull()) insertBlockPlaceholder(firstEditablePosition); @@ -688,23 +688,6 @@ void DeleteSelectionCommand::calculateTypingStyleAfterDelete() m_typingStyle->prepareToApplyAt(m_endingPosition); if (m_typingStyle->isEmpty()) m_typingStyle = 0; - VisiblePosition visibleEnd(m_endingPosition); - if (m_typingStyle && - isStartOfParagraph(visibleEnd) && - isEndOfParagraph(visibleEnd) && - lineBreakExistsAtVisiblePosition(visibleEnd)) { - // Apply style to the placeholder that is now holding open the empty paragraph. - // This makes sure that the paragraph has the right height, and that the paragraph - // takes on the right style and retains it even if you move the selection away and - // then move it back (which will clear typing style). - - setEndingSelection(visibleEnd); - applyStyle(m_typingStyle.get(), EditActionUnspecified); - // applyStyle can destroy the placeholder that was at m_endingPosition if it needs to - // move it, but it will set an endingSelection() at [movedPlaceholder, 0] if it does so. - m_endingPosition = endingSelection().start(); - m_typingStyle = 0; - } // This is where we've deleted all traces of a style but not a whole paragraph (that's handled above). // In this case if we start typing, the new characters should have the same style as the just deleted ones, // but, if we change the selection, come back and start typing that style should be lost. Also see @@ -757,7 +740,7 @@ void DeleteSelectionCommand::removeRedundantBlocks() while (node != rootNode) { if (isRemovableBlock(node)) { if (node == m_endingPosition.anchorNode()) - updatePositionForNodeRemoval(m_endingPosition, node); + updatePositionForNodeRemovalPreservingChildren(m_endingPosition, node); CompositeEditCommand::removeNodePreservingChildren(node); node = m_endingPosition.anchorNode(); diff --git a/Source/WebCore/editing/DictationAlternative.cpp b/Source/WebCore/editing/DictationAlternative.cpp new file mode 100644 index 000000000..94b4c72a3 --- /dev/null +++ b/Source/WebCore/editing/DictationAlternative.cpp @@ -0,0 +1,45 @@ +/* + * Copyright (C) 2012 Apple Inc. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY APPLE INC. ``AS IS'' AND ANY + * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL APPLE INC. OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY + * OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#include "config.h" +#include "DictationAlternative.h" + +namespace WebCore { + +DictationAlternative::DictationAlternative(unsigned start, unsigned length, uint64_t context) + : rangeStart(start) + , rangeLength(length) + , dictationContext(context) +{ +} + +DictationAlternative::DictationAlternative() + : rangeStart(0) + , rangeLength(0) + , dictationContext(0) +{ +} + +} diff --git a/Source/WebCore/editing/DictationAlternative.h b/Source/WebCore/editing/DictationAlternative.h new file mode 100644 index 000000000..08fcea386 --- /dev/null +++ b/Source/WebCore/editing/DictationAlternative.h @@ -0,0 +1,44 @@ +/* + * Copyright (C) 2012 Apple Inc. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY APPLE INC. ``AS IS'' AND ANY + * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL APPLE INC. OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY + * OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#ifndef DictationAlternative_h +#define DictationAlternative_h + +#include <stdint.h> + +namespace WebCore { +struct DictationAlternative { + DictationAlternative(unsigned start, unsigned length, uint64_t context); + DictationAlternative(); + unsigned rangeStart; + unsigned rangeLength; + + // This need to be 64 bit becauese it actually holds a pointer in WebKit. + uint64_t dictationContext; +}; + +} + +#endif // DictationAlternative_h diff --git a/Source/WebCore/editing/DictationCommand.cpp b/Source/WebCore/editing/DictationCommand.cpp new file mode 100644 index 000000000..a15800e98 --- /dev/null +++ b/Source/WebCore/editing/DictationCommand.cpp @@ -0,0 +1,142 @@ +/* + * Copyright (C) 2012 Apple Inc. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY APPLE INC. ``AS IS'' AND ANY + * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL APPLE INC. OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY + * OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#include "config.h" +#include "DictationCommand.h" + +#include "AlternativeTextController.h" +#include "Document.h" +#include "DocumentMarker.h" +#include "DocumentMarkerController.h" +#include "Frame.h" +#include "InsertParagraphSeparatorCommand.h" +#include "InsertTextCommand.h" +#include "Text.h" + +namespace WebCore { + +class DictationCommandLineOperation { +public: + DictationCommandLineOperation(DictationCommand* dictationCommand) + : m_dictationCommand(dictationCommand) + { } + + void operator()(size_t lineOffset, size_t lineLength, bool isLastLine) const + { + if (lineLength > 0) + m_dictationCommand->insertTextRunWithoutNewlines(lineOffset, lineLength); + if (!isLastLine) + m_dictationCommand->insertParagraphSeparator(); + } +private: + DictationCommand* m_dictationCommand; +}; + +class DictationMarkerSupplier : public TextInsertionMarkerSupplier { +public: + static PassRefPtr<DictationMarkerSupplier> create(const Vector<DictationAlternative>& alternatives) + { + return adoptRef(new DictationMarkerSupplier(alternatives)); + } + + virtual void addMarkersToTextNode(Text* textNode, unsigned offsetOfInsertion, const String& textToBeInserted) + { + Document* document = textNode->document(); + DocumentMarkerController* markerController =document->markers(); + for (size_t i = 0; i < m_alternatives.size(); ++i) { + const DictationAlternative& alternative = m_alternatives[i]; + markerController->addMarkerToNode(textNode, alternative.rangeStart + offsetOfInsertion, alternative.rangeLength, DocumentMarker::DictationAlternatives, DictationMarkerDetails::create(textToBeInserted.substring(alternative.rangeStart, alternative.rangeLength), alternative.dictationContext)); + markerController->addMarkerToNode(textNode, alternative.rangeStart + offsetOfInsertion, alternative.rangeLength, DocumentMarker::SpellCheckingExemption); + } + } + +protected: + DictationMarkerSupplier(const Vector<DictationAlternative>& alternatives) + : m_alternatives(alternatives) + { + } +private: + Vector<DictationAlternative> m_alternatives; +}; + +DictationCommand::DictationCommand(Document* document, const String& text, const Vector<DictationAlternative>& alternatives) + : TextInsertionBaseCommand(document) + , m_textToInsert(text) + , m_alternatives(alternatives) +{ +} + +void DictationCommand::insertText(Document* document, const String& text, const Vector<DictationAlternative>& alternatives, const VisibleSelection& selectionForInsertion) +{ + RefPtr<Frame> frame = document->frame(); + ASSERT(frame); + + VisibleSelection currentSelection = frame->selection()->selection(); + + String newText = dispatchBeforeTextInsertedEvent(text, selectionForInsertion, false); + + RefPtr<DictationCommand> cmd; + if (newText == text) + cmd = DictationCommand::create(document, newText, alternatives); + else + // If the text was modified before insertion, the location of dictation alternatives + // will not be valid anymore. We will just drop the alternatives. + cmd = DictationCommand::create(document, newText, Vector<DictationAlternative>()); + applyTextInsertionCommand(frame.get(), cmd, selectionForInsertion, currentSelection); +} + +void DictationCommand::doApply() +{ + DictationCommandLineOperation operation(this); + forEachLineInString(m_textToInsert, operation); +} + +void DictationCommand::insertTextRunWithoutNewlines(size_t lineStart, size_t lineLength) +{ + Vector<DictationAlternative> alternativesInLine; + collectDictationAlternativesInRange(lineStart, lineLength, alternativesInLine); + RefPtr<InsertTextCommand> command = InsertTextCommand::createWithMarkerSupplier(document(), m_textToInsert.substring(lineStart, lineLength), DictationMarkerSupplier::create(alternativesInLine)); + applyCommandToComposite(command, endingSelection()); +} + +void DictationCommand::insertParagraphSeparator() +{ + if (!canAppendNewLineFeedToSelection(endingSelection())) + return; + + applyCommandToComposite(InsertParagraphSeparatorCommand::create(document())); +} + +void DictationCommand::collectDictationAlternativesInRange(size_t rangeStart, size_t rangeLength, Vector<DictationAlternative>& alternatives) +{ + for (size_t i = 0; i < m_alternatives.size(); ++i) { + const DictationAlternative& alternative = m_alternatives[i]; + if (alternative.rangeStart >= rangeStart && (alternative.rangeStart + alternative.rangeLength) <= rangeStart + rangeLength) + alternatives.append(DictationAlternative(alternative.rangeStart - rangeStart, alternative.rangeLength, alternative.dictationContext)); + } + +} + +} diff --git a/Source/WebCore/editing/DictationCommand.h b/Source/WebCore/editing/DictationCommand.h new file mode 100644 index 000000000..f22ac552d --- /dev/null +++ b/Source/WebCore/editing/DictationCommand.h @@ -0,0 +1,60 @@ +/* + * Copyright (C) 2012 Apple Inc. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY APPLE INC. ``AS IS'' AND ANY + * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL APPLE INC. OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY + * OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#ifndef DictationCommand_h +#define DictationCommand_h + +#include "DictationAlternative.h" +#include "TextInsertionBaseCommand.h" + +namespace WebCore { + +class DocumentMarker; + +class DictationCommand : public TextInsertionBaseCommand { + friend class DictationCommandLineOperation; +public: + static void insertText(Document*, const String&, const Vector<DictationAlternative>& alternatives, const VisibleSelection&); + virtual bool isDictationCommand() const { return true; } +private: + static PassRefPtr<DictationCommand> create(Document* document, const String& text, const Vector<DictationAlternative>& alternatives) + { + return adoptRef(new DictationCommand(document, text, alternatives)); + } + + DictationCommand(Document*, const String& text, const Vector<DictationAlternative>& alternatives); + + virtual void doApply(); + + void insertTextRunWithoutNewlines(size_t lineStart, size_t lineLength); + void insertParagraphSeparator(); + void collectDictationAlternativesInRange(size_t rangeStart, size_t rangeLength, Vector<DictationAlternative>&); + + String m_textToInsert; + Vector<DictationAlternative> m_alternatives; +}; +} + +#endif // DictationCommand_h diff --git a/Source/WebCore/editing/EditingAllInOne.cpp b/Source/WebCore/editing/EditingAllInOne.cpp index c0299b320..9642418de 100644 --- a/Source/WebCore/editing/EditingAllInOne.cpp +++ b/Source/WebCore/editing/EditingAllInOne.cpp @@ -25,6 +25,7 @@ // This all-in-one cpp file cuts down on template bloat to allow us to build our Windows release build. +#include <AlternativeTextController.cpp> #include <AppendNodeCommand.cpp> #include <ApplyBlockElementCommand.cpp> #include <ApplyStyleCommand.cpp> @@ -35,6 +36,8 @@ #include <DeleteButtonController.cpp> #include <DeleteFromTextNodeCommand.cpp> #include <DeleteSelectionCommand.cpp> +#include <DictationAlternative.cpp> +#include <DictationCommand.cpp> #include <EditCommand.cpp> #include <EditingStyle.cpp> #include <Editor.cpp> @@ -62,15 +65,16 @@ #include <ReplaceSelectionCommand.cpp> #include <SetNodeAttributeCommand.cpp> #include <SetSelectionCommand.cpp> +#include <SimplifyMarkupCommand.cpp> #include <SmartReplace.cpp> #include <SmartReplaceCF.cpp> #include <SpellingCorrectionCommand.cpp> -#include <SpellingCorrectionController.cpp> #include <SpellChecker.cpp> #include <SplitElementCommand.cpp> #include <SplitTextNodeCommand.cpp> #include <SplitTextNodeContainingElementCommand.cpp> #include <TextCheckingHelper.cpp> +#include <TextInsertionBaseCommand.cpp> #include <TextIterator.cpp> #include <TypingCommand.cpp> #include <UnlinkCommand.cpp> diff --git a/Source/WebCore/editing/EditingBehavior.h b/Source/WebCore/editing/EditingBehavior.h index 9ed1d77dd..bbf68f503 100644 --- a/Source/WebCore/editing/EditingBehavior.h +++ b/Source/WebCore/editing/EditingBehavior.h @@ -60,10 +60,6 @@ public: // On Mac, when processing a contextual click, the object being clicked upon should be selected. bool shouldSelectOnContextualMenuClick() const { return m_type == EditingMacBehavior; } - // On Windows, moving caret left or right by word moves the caret by word in visual order. - // It moves the caret by word in logical order in other platforms. - bool shouldMoveLeftRightByWordInVisualOrder() const { return m_type == EditingWindowsBehavior; } - // On Mac and Windows, pressing backspace (when it isn't handled otherwise) should navigate back. bool shouldNavigateBackOnBackspace() const { return m_type != EditingUnixBehavior; } @@ -72,6 +68,11 @@ public: // to the other end of the line/word (Unix/Windows behavior). bool shouldExtendSelectionByWordOrLineAcrossCaret() const { return m_type != EditingMacBehavior; } + // Based on native behavior, when using ctrl(alt)+arrow to move caret by word, ctrl(alt)+left arrow moves caret to + // immediately before the word in all platforms, for example, the word break positions are: "|abc |def |hij |opq". + // But ctrl+right arrow moves caret to "abc |def |hij |opq" on Windows and "abc| def| hij| opq|" on Mac and Linux. + bool shouldSkipSpaceWhenMovingRight() const { return m_type == EditingWindowsBehavior; } + private: EditingBehaviorType m_type; }; diff --git a/Source/WebCore/editing/EditingStyle.cpp b/Source/WebCore/editing/EditingStyle.cpp index 3a360d8a9..6e441c83d 100644 --- a/Source/WebCore/editing/EditingStyle.cpp +++ b/Source/WebCore/editing/EditingStyle.cpp @@ -30,8 +30,8 @@ #include "ApplyStyleCommand.h" #include "CSSComputedStyleDeclaration.h" #include "CSSParser.h" +#include "CSSRuleList.h" #include "CSSStyleRule.h" -#include "CSSStyleSelector.h" #include "CSSValueKeywords.h" #include "CSSValueList.h" #include "Frame.h" @@ -44,6 +44,7 @@ #include "QualifiedName.h" #include "RenderStyle.h" #include "StylePropertySet.h" +#include "StyleResolver.h" #include "StyleRule.h" #include "StyledElement.h" #include "htmlediting.h" @@ -54,7 +55,7 @@ namespace WebCore { // Editing style properties must be preserved during editing operation. // e.g. when a user inserts a new paragraph, all properties listed here must be copied to the new paragraph. -static const int editingProperties[] = { +static const CSSPropertyID editingProperties[] = { CSSPropertyBackgroundColor, CSSPropertyTextDecoration, @@ -133,7 +134,7 @@ protected: HTMLElementEquivalent(CSSPropertyID); HTMLElementEquivalent(CSSPropertyID, const QualifiedName& tagName); HTMLElementEquivalent(CSSPropertyID, int primitiveValue, const QualifiedName& tagName); - const int m_propertyID; + const CSSPropertyID m_propertyID; const RefPtr<CSSPrimitiveValue> m_primitiveValue; const QualifiedName* m_tagName; // We can store a pointer because HTML tag names are const global. }; @@ -331,7 +332,7 @@ EditingStyle::EditingStyle(const CSSStyleDeclaration* style) extractFontSizeDelta(); } -EditingStyle::EditingStyle(int propertyID, const String& value) +EditingStyle::EditingStyle(CSSPropertyID propertyID, const String& value) : m_mutableStyle(0) , m_shouldUseFixedDefaultFontSize(false) , m_fontSizeDelta(NoFontDelta) @@ -410,7 +411,7 @@ void EditingStyle::removeTextFillAndStrokeColorsIfNeeded(RenderStyle* renderStyl m_mutableStyle->removeProperty(CSSPropertyWebkitTextStrokeColor); } -void EditingStyle::setProperty(int propertyID, const String& value, bool important) +void EditingStyle::setProperty(CSSPropertyID propertyID, const String& value, bool important) { if (!m_mutableStyle) m_mutableStyle = StylePropertySet::create(); @@ -602,7 +603,7 @@ void EditingStyle::collapseTextDecorationProperties() } // CSS properties that create a visual difference only when applied to text. -static const int textOnlyProperties[] = { +static const CSSPropertyID textOnlyProperties[] = { CSSPropertyTextDecoration, CSSPropertyWebkitTextDecorationsInEffect, CSSPropertyFontStyle, @@ -670,7 +671,7 @@ bool EditingStyle::conflictsWithInlineStyleOfElement(StyledElement* element, Edi unsigned propertyCount = m_mutableStyle->propertyCount(); for (unsigned i = 0; i < propertyCount; ++i) { - CSSPropertyID propertyID = static_cast<CSSPropertyID>(m_mutableStyle->propertyAt(i).id()); + CSSPropertyID propertyID = m_mutableStyle->propertyAt(i).id(); // We don't override whitespace property of a tab span because that would collapse the tab into a space. if (propertyID == CSSPropertyWhiteSpace && isTabSpanNode(element)) @@ -1029,7 +1030,7 @@ void EditingStyle::mergeStyle(const StylePropertySet* style, CSSPropertyOverride static PassRefPtr<StylePropertySet> styleFromMatchedRulesForElement(Element* element, unsigned rulesToInclude) { RefPtr<StylePropertySet> style = StylePropertySet::create(); - RefPtr<CSSRuleList> matchedRules = element->document()->styleSelector()->styleRulesForElement(element, rulesToInclude); + RefPtr<CSSRuleList> matchedRules = element->document()->styleResolver()->styleRulesForElement(element, rulesToInclude); if (matchedRules) { for (unsigned i = 0; i < matchedRules->length(); i++) { if (matchedRules->item(i)->type() == CSSRule::STYLE_RULE) { @@ -1045,7 +1046,7 @@ static PassRefPtr<StylePropertySet> styleFromMatchedRulesForElement(Element* ele void EditingStyle::mergeStyleFromRules(StyledElement* element) { RefPtr<StylePropertySet> styleFromMatchedRules = styleFromMatchedRulesForElement(element, - CSSStyleSelector::AuthorCSSRules | CSSStyleSelector::CrossOriginCSSRules); + StyleResolver::AuthorCSSRules | StyleResolver::CrossOriginCSSRules); // Styles from the inline style declaration, held in the variable "style", take precedence // over those from matched rules. if (m_mutableStyle) @@ -1083,7 +1084,7 @@ void EditingStyle::mergeStyleFromRulesForSerialization(StyledElement* element) static void removePropertiesInStyle(StylePropertySet* styleToRemovePropertiesFrom, StylePropertySet* style) { unsigned propertyCount = style->propertyCount(); - Vector<int> propertiesToRemove(propertyCount); + Vector<CSSPropertyID> propertiesToRemove(propertyCount); for (unsigned i = 0; i < propertyCount; ++i) propertiesToRemove[i] = style->propertyAt(i).id(); @@ -1097,7 +1098,7 @@ void EditingStyle::removeStyleFromRulesAndContext(StyledElement* element, Node* return; // 1. Remove style from matched rules because style remain without repeating it in inline style declaration - RefPtr<StylePropertySet> styleFromMatchedRules = styleFromMatchedRulesForElement(element, CSSStyleSelector::AllButEmptyCSSRules); + RefPtr<StylePropertySet> styleFromMatchedRules = styleFromMatchedRulesForElement(element, StyleResolver::AllButEmptyCSSRules); if (styleFromMatchedRules && !styleFromMatchedRules->isEmpty()) m_mutableStyle = getPropertiesNotIn(m_mutableStyle.get(), styleFromMatchedRules->ensureCSSStyleDeclaration()); @@ -1126,7 +1127,7 @@ void EditingStyle::removePropertiesInElementDefaultStyle(Element* element) if (!m_mutableStyle || m_mutableStyle->isEmpty()) return; - RefPtr<StylePropertySet> defaultStyle = styleFromMatchedRulesForElement(element, CSSStyleSelector::UAAndUserCSSRules); + RefPtr<StylePropertySet> defaultStyle = styleFromMatchedRulesForElement(element, StyleResolver::UAAndUserCSSRules); removePropertiesInStyle(m_mutableStyle.get(), defaultStyle.get()); } @@ -1318,7 +1319,7 @@ StyleChange::StyleChange(EditingStyle* style, const Position& position) m_cssStyle = mutableStyle->asText().stripWhiteSpace(); } -static void setTextDecorationProperty(StylePropertySet* style, const CSSValueList* newTextDecoration, int propertyID) +static void setTextDecorationProperty(StylePropertySet* style, const CSSValueList* newTextDecoration, CSSPropertyID propertyID) { if (newTextDecoration->length()) style->setProperty(propertyID, newTextDecoration->cssText(), style->propertyIsImportant(propertyID)); @@ -1394,7 +1395,7 @@ void StyleChange::extractTextStyles(Document* document, StylePropertySet* style, } } -static void diffTextDecorations(StylePropertySet* style, int propertID, CSSValue* refTextDecoration) +static void diffTextDecorations(StylePropertySet* style, CSSPropertyID propertID, CSSValue* refTextDecoration) { RefPtr<CSSValue> textDecoration = style->getPropertyCSSValue(propertID); if (!textDecoration || !textDecoration->isValueList() || !refTextDecoration || !refTextDecoration->isValueList()) @@ -1533,10 +1534,10 @@ int legacyFontSizeFromCSSValue(Document* document, CSSPrimitiveValue* value, boo { if (isCSSValueLength(value)) { int pixelFontSize = value->getIntValue(CSSPrimitiveValue::CSS_PX); - int legacyFontSize = CSSStyleSelector::legacyFontSize(document, pixelFontSize, shouldUseFixedFontDefaultSize); + int legacyFontSize = StyleResolver::legacyFontSize(document, pixelFontSize, shouldUseFixedFontDefaultSize); // Use legacy font size only if pixel value matches exactly to that of legacy font size. int cssPrimitiveEquivalent = legacyFontSize - 1 + CSSValueXSmall; - if (mode == AlwaysUseLegacyFontSize || CSSStyleSelector::fontSizeForKeyword(document, cssPrimitiveEquivalent, shouldUseFixedFontDefaultSize) == pixelFontSize) + if (mode == AlwaysUseLegacyFontSize || StyleResolver::fontSizeForKeyword(document, cssPrimitiveEquivalent, shouldUseFixedFontDefaultSize) == pixelFontSize) return legacyFontSize; return 0; diff --git a/Source/WebCore/editing/EditingStyle.h b/Source/WebCore/editing/EditingStyle.h index c213036e1..177c2a007 100644 --- a/Source/WebCore/editing/EditingStyle.h +++ b/Source/WebCore/editing/EditingStyle.h @@ -91,7 +91,7 @@ public: return adoptRef(new EditingStyle(style)); } - static PassRefPtr<EditingStyle> create(int propertyID, const String& value) + static PassRefPtr<EditingStyle> create(CSSPropertyID propertyID, const String& value) { return adoptRef(new EditingStyle(propertyID, value)); } @@ -152,10 +152,10 @@ private: EditingStyle(const Position&, PropertiesToInclude); EditingStyle(const StylePropertySet*); EditingStyle(const CSSStyleDeclaration*); - EditingStyle(int propertyID, const String& value); + EditingStyle(CSSPropertyID, const String& value); void init(Node*, PropertiesToInclude); void removeTextFillAndStrokeColorsIfNeeded(RenderStyle*); - void setProperty(int propertyID, const String& value, bool important = false); + void setProperty(CSSPropertyID, const String& value, bool important = false); void replaceFontSizeByKeywordIfPossible(RenderStyle*, CSSComputedStyleDeclaration*); void extractFontSizeDelta(); TriState triStateOfStyle(CSSStyleDeclaration* styleToCompare, ShouldIgnoreTextOnlyProperties) const; diff --git a/Source/WebCore/editing/Editor.cpp b/Source/WebCore/editing/Editor.cpp index 72e21fa24..73acc57af 100644 --- a/Source/WebCore/editing/Editor.cpp +++ b/Source/WebCore/editing/Editor.cpp @@ -28,19 +28,20 @@ #include "Editor.h" #include "AXObjectCache.h" +#include "AlternativeTextController.h" #include "ApplyStyleCommand.h" #include "CSSComputedStyleDeclaration.h" #include "CSSProperty.h" #include "CSSPropertyNames.h" -#include "CSSStyleSelector.h" #include "CSSValueKeywords.h" #include "CachedResourceLoader.h" #include "ClipboardEvent.h" #include "CompositionEvent.h" -#include "SpellingCorrectionController.h" #include "CreateLinkCommand.h" #include "DeleteButtonController.h" #include "DeleteSelectionCommand.h" +#include "DictationAlternative.h" +#include "DictationCommand.h" #include "DocumentFragment.h" #include "DocumentMarkerController.h" #include "EditingText.h" @@ -68,16 +69,17 @@ #include "TextCheckingHelper.h" #include "RemoveFormatCommand.h" #include "RenderBlock.h" -#include "RenderLayer.h" #include "RenderPart.h" #include "RenderTextControl.h" #include "RenderedPosition.h" #include "ReplaceSelectionCommand.h" #include "Settings.h" +#include "SimplifyMarkupCommand.h" #include "Sound.h" #include "SpellChecker.h" #include "SpellingCorrectionCommand.h" #include "StylePropertySet.h" +#include "StyleResolver.h" #include "Text.h" #include "TextCheckerClient.h" #include "TextCheckingHelper.h" @@ -413,6 +415,8 @@ void Editor::replaceSelectionWithFragment(PassRefPtr<DocumentFragment> fragment, applyCommand(ReplaceSelectionCommand::create(m_frame->document(), fragment, options, EditActionPaste)); revealSelectionAfterEditingOperation(); + if (m_frame->selection()->isInPasswordField()) + return; Node* nodeToCheck = m_frame->selection()->rootEditableElement(); if (!nodeToCheck) return; @@ -482,7 +486,7 @@ void Editor::respondToChangedSelection(const VisibleSelection& oldSelection) client()->respondToChangedSelection(m_frame); setStartNewKillRingSequence(true); m_deleteButtonController->respondToChangedSelection(oldSelection); - m_spellingCorrector->respondToChangedSelection(oldSelection); + m_alternativeTextController->respondToChangedSelection(oldSelection); } void Editor::respondToChangedContents(const VisibleSelection& endingSelection) @@ -731,18 +735,18 @@ void Editor::applyParagraphStyleToSelection(StylePropertySet* style, EditAction applyParagraphStyle(style, editingAction); } -bool Editor::selectionStartHasStyle(int propertyID, const String& value) const +bool Editor::selectionStartHasStyle(CSSPropertyID propertyID, const String& value) const { return EditingStyle::create(propertyID, value)->triStateOfStyle( EditingStyle::styleAtSelectionStart(m_frame->selection()->selection(), propertyID == CSSPropertyBackgroundColor).get()); } -TriState Editor::selectionHasStyle(int propertyID, const String& value) const +TriState Editor::selectionHasStyle(CSSPropertyID propertyID, const String& value) const { return EditingStyle::create(propertyID, value)->triStateOfStyle(m_frame->selection()->selection()); } -String Editor::selectionStartCSSPropertyValue(int propertyID) +String Editor::selectionStartCSSPropertyValue(CSSPropertyID propertyID) { RefPtr<EditingStyle> selectionStyle = EditingStyle::styleAtSelectionStart(m_frame->selection()->selection(), propertyID == CSSPropertyBackgroundColor); @@ -782,7 +786,7 @@ void Editor::appliedEditing(PassRefPtr<CompositeEditCommand> cmd) dispatchEditableContentChangedEvents(composition->startingRootEditableElement(), composition->endingRootEditableElement()); VisibleSelection newSelection(cmd->endingSelection()); - m_spellingCorrector->respondToAppliedEditing(cmd.get()); + m_alternativeTextController->respondToAppliedEditing(cmd.get()); // Don't clear the typing style with this selection change. We do those things elsewhere if necessary. changeSelectionAfterCommand(newSelection, false, false); @@ -812,7 +816,7 @@ void Editor::unappliedEditing(PassRefPtr<EditCommandComposition> cmd) VisibleSelection newSelection(cmd->startingSelection()); changeSelectionAfterCommand(newSelection, true, true); - m_spellingCorrector->respondToUnappliedEditing(cmd.get()); + m_alternativeTextController->respondToUnappliedEditing(cmd.get()); m_lastEditCommand = 0; if (client()) @@ -844,7 +848,7 @@ Editor::Editor(Frame* frame) , m_shouldStyleWithCSS(false) , m_killRing(adoptPtr(new KillRing)) , m_spellChecker(adoptPtr(new SpellChecker(frame))) - , m_spellingCorrector(adoptPtr(new SpellingCorrectionController(frame))) + , m_alternativeTextController(adoptPtr(new AlternativeTextController(frame))) , m_areMarkedTextMatchesHighlighted(false) , m_defaultParagraphSeparator(EditorParagraphSeparatorIsDiv) { @@ -872,6 +876,11 @@ bool Editor::insertTextForConfirmedComposition(const String& text) return m_frame->eventHandler()->handleTextInputEvent(text, 0, TextEventInputComposition); } +bool Editor::insertDictatedText(const String& text, const Vector<DictationAlternative>& dictationAlternatives, Event* triggeringEvent) +{ + return m_alternativeTextController->insertDictatedText(text, dictationAlternatives, triggeringEvent); +} + bool Editor::insertTextWithoutSendingTextEvent(const String& text, bool selectInsertedText, TextEvent* triggeringEvent) { if (text.isEmpty()) @@ -895,7 +904,7 @@ bool Editor::insertTextWithoutSendingTextEvent(const String& text, bool selectIn if (text.length() == 1 && isPunct(text[0]) && !isAmbiguousBoundaryCharacter(text[0])) shouldConsiderApplyingAutocorrection = true; - bool autocorrectionWasApplied = shouldConsiderApplyingAutocorrection && m_spellingCorrector->applyAutocorrectionBeforeTypingIfAppropriate(); + bool autocorrectionWasApplied = shouldConsiderApplyingAutocorrection && m_alternativeTextController->applyAutocorrectionBeforeTypingIfAppropriate(); // Get the selection to use for the event that triggered this insertText. // If the event handler changed the selection, we may want to use a different selection @@ -906,12 +915,16 @@ bool Editor::insertTextWithoutSendingTextEvent(const String& text, bool selectIn RefPtr<Document> document = selectionStart->document(); // Insert the text - TypingCommand::Options options = 0; - if (selectInsertedText) - options |= TypingCommand::SelectInsertedText; - if (autocorrectionWasApplied) - options |= TypingCommand::RetainAutocorrectionIndicator; - TypingCommand::insertText(document.get(), text, selection, options, triggeringEvent && triggeringEvent->isComposition() ? TypingCommand::TextCompositionConfirm : TypingCommand::TextCompositionNone); + if (triggeringEvent && triggeringEvent->isDictation()) + DictationCommand::insertText(document.get(), text, triggeringEvent->dictationAlternatives(), selection); + else { + TypingCommand::Options options = 0; + if (selectInsertedText) + options |= TypingCommand::SelectInsertedText; + if (autocorrectionWasApplied) + options |= TypingCommand::RetainAutocorrectionIndicator; + TypingCommand::insertText(document.get(), text, selection, options, triggeringEvent && triggeringEvent->isComposition() ? TypingCommand::TextCompositionConfirm : TypingCommand::TextCompositionNone); + } // Reveal the current selection if (Frame* editedFrame = document->frame()) @@ -931,7 +944,7 @@ bool Editor::insertLineBreak() if (!shouldInsertText("\n", m_frame->selection()->toNormalizedRange().get(), EditorInsertActionTyped)) return true; - bool autocorrectionIsApplied = m_spellingCorrector->applyAutocorrectionBeforeTypingIfAppropriate(); + bool autocorrectionIsApplied = m_alternativeTextController->applyAutocorrectionBeforeTypingIfAppropriate(); TypingCommand::insertLineBreak(m_frame->document(), autocorrectionIsApplied ? TypingCommand::RetainAutocorrectionIndicator : 0); revealSelectionAfterEditingOperation(); @@ -949,7 +962,7 @@ bool Editor::insertParagraphSeparator() if (!shouldInsertText("\n", m_frame->selection()->toNormalizedRange().get(), EditorInsertActionTyped)) return true; - bool autocorrectionIsApplied = m_spellingCorrector->applyAutocorrectionBeforeTypingIfAppropriate(); + bool autocorrectionIsApplied = m_alternativeTextController->applyAutocorrectionBeforeTypingIfAppropriate(); TypingCommand::insertParagraphSeparator(m_frame->document(), autocorrectionIsApplied ? TypingCommand::RetainAutocorrectionIndicator : 0); revealSelectionAfterEditingOperation(); @@ -1039,6 +1052,24 @@ void Editor::performDelete() setStartNewKillRingSequence(false); } +void Editor::simplifyMarkup(Node* startNode, Node* endNode) +{ + if (!startNode) + return; + if (endNode) { + if (startNode->document() != endNode->document()) + return; + // check if start node is before endNode + Node* node = startNode; + while (node && node != endNode) + node = node->traverseNextNode(); + if (!node) + return; + } + + applyCommand(SimplifyMarkupCommand::create(m_frame->document(), startNode, (endNode) ? endNode->traverseNextNode() : 0)); +} + void Editor::copyURL(const KURL& url, const String& title) { Pasteboard::generalPasteboard()->writeURL(url, title, m_frame); @@ -1173,7 +1204,7 @@ void Editor::toggleAutomaticTextReplacement() bool Editor::isAutomaticSpellingCorrectionEnabled() { - return m_spellingCorrector->isAutomaticSpellingCorrectionEnabled(); + return m_alternativeTextController->isAutomaticSpellingCorrectionEnabled(); } void Editor::toggleAutomaticSpellingCorrection() @@ -1328,7 +1359,7 @@ void Editor::setComposition(const String& text, SetCompositionMode mode) // If text is empty, then delete the old composition here. If text is non-empty, InsertTextCommand::input // will delete the old composition with an optimized replace operation. - if (text.isEmpty()) + if (text.isEmpty() && mode != CancelComposition) TypingCommand::deleteSelection(m_frame->document(), 0); m_compositionNode = 0; @@ -1772,7 +1803,7 @@ void Editor::markMisspellingsAfterTypingToWord(const VisiblePosition &wordStart, #endif if (unifiedTextCheckerEnabled()) { - m_spellingCorrector->applyPendingCorrection(selectionAfterTyping); + m_alternativeTextController->applyPendingCorrection(selectionAfterTyping); TextCheckingTypeMask textCheckingOptions = 0; @@ -1908,10 +1939,11 @@ void Editor::markBadGrammar(const VisibleSelection& selection) void Editor::markAllMisspellingsAndBadGrammarInRanges(TextCheckingTypeMask textCheckingOptions, Range* spellingRange, Range* grammarRange) { + ASSERT(m_frame); ASSERT(unifiedTextCheckerEnabled()); // There shouldn't be pending autocorrection at this moment. - ASSERT(!m_spellingCorrector->hasPendingCorrection()); + ASSERT(!m_alternativeTextController->hasPendingCorrection()); bool shouldMarkGrammar = textCheckingOptions & TextCheckingTypeGrammar; bool shouldShowCorrectionPanel = textCheckingOptions & TextCheckingTypeShowCorrectionPanel; @@ -2004,7 +2036,7 @@ void Editor::markAndReplaceFor(PassRefPtr<SpellCheckRequest> request, const Vect if (shouldMarkSpelling && result->type == TextCheckingTypeSpelling && resultLocation >= paragraph.checkingStart() && resultLocation + resultLength <= spellingRangeEndOffset && !resultEndsAtAmbiguousBoundary) { ASSERT(resultLength > 0 && resultLocation >= 0); RefPtr<Range> misspellingRange = paragraph.subrange(resultLocation, resultLength); - if (!m_spellingCorrector->isSpellingMarkerAllowed(misspellingRange)) + 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)) { @@ -2047,7 +2079,7 @@ void Editor::markAndReplaceFor(PassRefPtr<SpellCheckRequest> request, const Vect continue; String replacedString = plainText(rangeToReplace.get()); - bool existingMarkersPermitReplacement = m_spellingCorrector->processMarkersOnTextToBeReplacedByResult(result, rangeToReplace.get(), replacedString); + bool existingMarkersPermitReplacement = m_alternativeTextController->processMarkersOnTextToBeReplacedByResult(result, rangeToReplace.get(), replacedString); if (!existingMarkersPermitReplacement) continue; @@ -2058,7 +2090,7 @@ void Editor::markAndReplaceFor(PassRefPtr<SpellCheckRequest> request, const Vect // 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_spellingCorrector->show(rangeToReplace, result->replacement); + m_alternativeTextController->show(rangeToReplace, result->replacement); break; } // If this function is called for showing correction panel, we ignore other correction or replacement. @@ -2094,7 +2126,7 @@ void Editor::markAndReplaceFor(PassRefPtr<SpellCheckRequest> request, const Vect // Add a marker so that corrections can easily be undone and won't be re-corrected. if (result->type == TextCheckingTypeCorrection) - m_spellingCorrector->markCorrection(paragraph.subrange(resultLocation, replacementLength), replacedString); + m_alternativeTextController->markCorrection(paragraph.subrange(resultLocation, replacementLength), replacedString); } } } @@ -2127,12 +2159,12 @@ void Editor::changeBackToReplacedString(const String& replacedString) if (!shouldInsertText(replacedString, selection.get(), EditorInsertActionPasted)) return; - m_spellingCorrector->recordAutocorrectionResponseReversed(replacedString, selection); + m_alternativeTextController->recordAutocorrectionResponseReversed(replacedString, selection); TextCheckingParagraph paragraph(selection); replaceSelectionWithText(replacedString, false, false); RefPtr<Range> changedRange = paragraph.subrange(paragraph.checkingStart(), replacedString.length()); changedRange->startContainer()->document()->markers()->addMarker(changedRange.get(), DocumentMarker::Replacement, String()); - m_spellingCorrector->markReversed(changedRange.get()); + m_alternativeTextController->markReversed(changedRange.get()); } @@ -2156,12 +2188,12 @@ void Editor::markMisspellingsAndBadGrammar(const VisibleSelection& spellingSelec void Editor::unappliedSpellCorrection(const VisibleSelection& selectionOfCorrected, const String& corrected, const String& correction) { - m_spellingCorrector->respondToUnappliedSpellCorrection(selectionOfCorrected, corrected, correction); + m_alternativeTextController->respondToUnappliedSpellCorrection(selectionOfCorrected, corrected, correction); } void Editor::updateMarkersForWordsAffectedByEditing(bool doNotRemoveIfSelectionAtWordBoundary) { - if (!m_spellingCorrector->shouldRemoveMarkersUponEditing()) + if (!m_alternativeTextController->shouldRemoveMarkersUponEditing()) return; // We want to remove the markers from a word if an editing command will change the word. This can happen in one of @@ -2225,13 +2257,13 @@ void Editor::updateMarkersForWordsAffectedByEditing(bool doNotRemoveIfSelectionA Document* document = m_frame->document(); RefPtr<Range> wordRange = Range::create(document, startOfFirstWord.deepEquivalent(), endOfLastWord.deepEquivalent()); - document->markers()->removeMarkers(wordRange.get(), DocumentMarker::Spelling | DocumentMarker::CorrectionIndicator | DocumentMarker::SpellCheckingExemption, DocumentMarkerController::RemovePartiallyOverlappingMarker); + document->markers()->removeMarkers(wordRange.get(), DocumentMarker::Spelling | DocumentMarker::CorrectionIndicator | DocumentMarker::SpellCheckingExemption | DocumentMarker::DictationAlternatives, DocumentMarkerController::RemovePartiallyOverlappingMarker); document->markers()->clearDescriptionOnMarkersIntersectingRange(wordRange.get(), DocumentMarker::Replacement); } void Editor::deletedAutocorrectionAtPosition(const Position& position, const String& originalString) { - m_spellingCorrector->deletedAutocorrectionAtPosition(position, originalString); + m_alternativeTextController->deletedAutocorrectionAtPosition(position, originalString); } PassRefPtr<Range> Editor::rangeForPoint(const IntPoint& windowPoint) @@ -2245,7 +2277,7 @@ PassRefPtr<Range> Editor::rangeForPoint(const IntPoint& windowPoint) FrameView* frameView = frame->view(); if (!frameView) return 0; - LayoutPoint framePoint = frameView->windowToContents(windowPoint); + IntPoint framePoint = frameView->windowToContents(windowPoint); VisibleSelection selection(frame->visiblePositionForPoint(framePoint)); return avoidIntersectionWithNode(selection.toNormalizedRange().get(), m_deleteButtonController->containerElement()); } @@ -2356,20 +2388,20 @@ void Editor::addToKillRing(Range* range, bool prepend) m_shouldStartNewKillRingSequence = false; } -void Editor::startCorrectionPanelTimer() +void Editor::startAlternativeTextUITimer() { - m_spellingCorrector->startCorrectionPanelTimer(CorrectionPanelInfo::PanelTypeCorrection); + m_alternativeTextController->startAlternativeTextUITimer(AlternativeTextTypeCorrection); } -void Editor::handleCorrectionPanelResult(const String& correction) +void Editor::handleAlternativeTextUIResult(const String& correction) { - m_spellingCorrector->handleCorrectionPanelResult(correction); + m_alternativeTextController->handleAlternativeTextUIResult(correction); } void Editor::dismissCorrectionPanelAsIgnored() { - m_spellingCorrector->dismiss(ReasonForDismissingCorrectionPanelIgnored); + m_alternativeTextController->dismiss(ReasonForDismissingAlternativeTextIgnored); } bool Editor::insideVisibleArea(const LayoutPoint& point) const @@ -2553,11 +2585,11 @@ IntRect Editor::firstRectForRange(Range* range) const ASSERT(range->startContainer()); ASSERT(range->endContainer()); - LayoutRect startCaretRect = RenderedPosition(VisiblePosition(range->startPosition()).deepEquivalent(), DOWNSTREAM).absoluteRect(extraWidthToEndOfLine); + IntRect startCaretRect = RenderedPosition(VisiblePosition(range->startPosition()).deepEquivalent(), DOWNSTREAM).absoluteRect(&extraWidthToEndOfLine); if (startCaretRect == LayoutRect()) return IntRect(); - LayoutRect endCaretRect = RenderedPosition(VisiblePosition(range->endPosition()).deepEquivalent(), UPSTREAM).absoluteRect(); + IntRect endCaretRect = RenderedPosition(VisiblePosition(range->endPosition()).deepEquivalent(), UPSTREAM).absoluteRect(); if (endCaretRect == LayoutRect()) return IntRect(); @@ -2697,7 +2729,7 @@ PassRefPtr<Range> Editor::findStringAndScrollToVisible(const String& target, Ran if (!nextMatch) return 0; - nextMatch->firstNode()->renderer()->enclosingLayer()->scrollRectToVisible(nextMatch->boundingBox(), + nextMatch->firstNode()->renderer()->scrollRectToVisible(nextMatch->boundingBox(), ScrollAlignment::alignCenterIfNeeded, ScrollAlignment::alignCenterIfNeeded); return nextMatch.release(); @@ -2865,7 +2897,7 @@ unsigned Editor::countMatchesForText(const String& target, Range* range, FindOpt PaintBehavior oldBehavior = m_frame->view()->paintBehavior(); m_frame->view()->setPaintBehavior(oldBehavior | PaintBehaviorFlattenCompositingLayers); - m_frame->view()->paintContents(&context, visibleRect); + m_frame->view()->paintContents(&context, enclosingIntRect(visibleRect)); m_frame->view()->setPaintBehavior(oldBehavior); } } @@ -2885,7 +2917,7 @@ void Editor::setMarkedTextMatchesAreHighlighted(bool flag) void Editor::respondToChangedSelection(const VisibleSelection& oldSelection, FrameSelection::SetSelectionOptions options) { - m_spellingCorrector->stopPendingCorrection(oldSelection); + m_alternativeTextController->stopPendingCorrection(oldSelection); bool closeTyping = options & FrameSelection::CloseTyping; bool isContinuousSpellCheckingEnabled = this->isContinuousSpellCheckingEnabled(); @@ -2920,11 +2952,18 @@ void Editor::respondToChangedSelection(const VisibleSelection& oldSelection, Fra } #if !PLATFORM(MAC) || (PLATFORM(MAC) && (defined(BUILDING_ON_LEOPARD) || defined(BUILDING_ON_SNOW_LEOPARD))) +#if PLATFORM(CHROMIUM) + if (!m_frame->settings() || !m_frame->settings()->asynchronousSpellCheckingEnabled()) { + if (RefPtr<Range> wordRange = newAdjacentWords.toNormalizedRange()) + m_frame->document()->markers()->removeMarkers(wordRange.get(), DocumentMarker::Spelling); + } +#else // This only erases markers that are in the first unit (word or sentence) of the selection. // Perhaps peculiar, but it matches AppKit on these Mac OS X versions. if (RefPtr<Range> wordRange = newAdjacentWords.toNormalizedRange()) m_frame->document()->markers()->removeMarkers(wordRange.get(), DocumentMarker::Spelling); #endif +#endif if (RefPtr<Range> sentenceRange = newSelectedSentence.toNormalizedRange()) m_frame->document()->markers()->removeMarkers(sentenceRange.get(), DocumentMarker::Grammar); } diff --git a/Source/WebCore/editing/Editor.h b/Source/WebCore/editing/Editor.h index 3c2544a71..7c875e874 100644 --- a/Source/WebCore/editing/Editor.h +++ b/Source/WebCore/editing/Editor.h @@ -28,6 +28,7 @@ #include "ClipboardAccessPolicy.h" #include "Color.h" +#include "DictationAlternative.h" #include "DocumentMarker.h" #include "EditAction.h" #include "EditingBehavior.h" @@ -61,7 +62,7 @@ class Pasteboard; class SimpleFontData; class SpellChecker; class SpellCheckRequest; -class SpellingCorrectionController; +class AlternativeTextController; class StylePropertySet; class Text; class TextCheckerClient; @@ -136,9 +137,9 @@ public: void respondToChangedSelection(const VisibleSelection& oldSelection); void respondToChangedContents(const VisibleSelection& endingSelection); - bool selectionStartHasStyle(int propertyID, const String& value) const; - TriState selectionHasStyle(int propertyID, const String& value) const; - String selectionStartCSSPropertyValue(int propertyID); + bool selectionStartHasStyle(CSSPropertyID, const String& value) const; + TriState selectionHasStyle(CSSPropertyID, const String& value) const; + String selectionStartCSSPropertyValue(CSSPropertyID); TriState selectionUnorderedListState() const; TriState selectionOrderedListState() const; @@ -202,9 +203,14 @@ public: bool insertText(const String&, Event* triggeringEvent); bool insertTextForConfirmedComposition(const String& text); + bool insertDictatedText(const String&, const Vector<DictationAlternative>& dictationAlternatives, Event* triggeringEvent); bool insertTextWithoutSendingTextEvent(const String&, bool selectInsertedText, TextEvent* triggeringEvent); bool insertLineBreak(); bool insertParagraphSeparator(); + +#if PLATFORM(MAC) + bool insertParagraphSeparatorInQuotedContent(); +#endif bool isContinuousSpellCheckingEnabled(); void toggleContinuousSpellChecking(); @@ -300,7 +306,7 @@ public: void setStartNewKillRingSequence(bool); - PassRefPtr<Range> rangeForPoint(const LayoutPoint& windowPoint); + PassRefPtr<Range> rangeForPoint(const IntPoint& windowPoint); void clear(); @@ -319,9 +325,9 @@ public: void addToKillRing(Range*, bool prepend); - void startCorrectionPanelTimer(); + void startAlternativeTextUITimer(); // If user confirmed a correction in the correction panel, correction has non-zero length, otherwise it means that user has dismissed the panel. - void handleCorrectionPanelResult(const String& correction); + void handleAlternativeTextUIResult(const String& correction); void dismissCorrectionPanelAsIgnored(); void pasteAsFragment(PassRefPtr<DocumentFragment>, bool smartReplace, bool matchStyle); @@ -374,6 +380,8 @@ public: void takeFindStringFromSelection(); void writeSelectionToPasteboard(const String& pasteboardName, const Vector<String>& pasteboardTypes); void readSelectionFromPasteboard(const String& pasteboardName); + String stringSelectionForPasteboard(); + PassRefPtr<SharedBuffer> dataSelectionForPasteboard(const String& pasteboardName); #endif void replaceSelectionWithFragment(PassRefPtr<DocumentFragment>, bool selectReplacement, bool smartReplace, bool matchStyle); @@ -381,6 +389,8 @@ public: bool selectionStartHasMarkerFor(DocumentMarker::MarkerType, int from, int length) const; void updateMarkersForWordsAffectedByEditing(bool onlyHandleWordsContainingSelection); void deletedAutocorrectionAtPosition(const Position&, const String& originalString); + + void simplifyMarkup(Node* startNode, Node* endNode); void deviceScaleFactorChanged(); @@ -401,7 +411,7 @@ private: bool m_shouldStyleWithCSS; OwnPtr<KillRing> m_killRing; OwnPtr<SpellChecker> m_spellChecker; - OwnPtr<SpellingCorrectionController> m_spellingCorrector; + OwnPtr<AlternativeTextController> m_alternativeTextController; VisibleSelection m_mark; bool m_areMarkedTextMatchesHighlighted; EditorParagraphSeparator m_defaultParagraphSeparator; diff --git a/Source/WebCore/editing/EditorCommand.cpp b/Source/WebCore/editing/EditorCommand.cpp index b9f34b980..a2f147ea2 100644 --- a/Source/WebCore/editing/EditorCommand.cpp +++ b/Source/WebCore/editing/EditorCommand.cpp @@ -114,14 +114,14 @@ static bool applyCommandToFrame(Frame* frame, EditorCommandSource source, EditAc return false; } -static bool executeApplyStyle(Frame* frame, EditorCommandSource source, EditAction action, int propertyID, const String& propertyValue) +static bool executeApplyStyle(Frame* frame, EditorCommandSource source, EditAction action, CSSPropertyID propertyID, const String& propertyValue) { RefPtr<StylePropertySet> style = StylePropertySet::create(); style->setProperty(propertyID, propertyValue); return applyCommandToFrame(frame, source, action, style.get()); } -static bool executeApplyStyle(Frame* frame, EditorCommandSource source, EditAction action, int propertyID, int propertyValue) +static bool executeApplyStyle(Frame* frame, EditorCommandSource source, EditAction action, CSSPropertyID propertyID, int propertyValue) { RefPtr<StylePropertySet> style = StylePropertySet::create(); style->setProperty(propertyID, propertyValue); @@ -131,7 +131,7 @@ static bool executeApplyStyle(Frame* frame, EditorCommandSource source, EditActi // FIXME: executeToggleStyleInList does not handle complicated cases such as <b><u>hello</u>world</b> properly. // This function must use Editor::selectionHasStyle to determine the current style but we cannot fix this // until https://bugs.webkit.org/show_bug.cgi?id=27818 is resolved. -static bool executeToggleStyleInList(Frame* frame, EditorCommandSource source, EditAction action, int propertyID, CSSValue* value) +static bool executeToggleStyleInList(Frame* frame, EditorCommandSource source, EditAction action, CSSPropertyID propertyID, CSSValue* value) { ExceptionCode ec = 0; RefPtr<EditingStyle> selectionStyle = EditingStyle::styleAtSelectionStart(frame->selection()->selection()); @@ -156,7 +156,7 @@ static bool executeToggleStyleInList(Frame* frame, EditorCommandSource source, E return applyCommandToFrame(frame, source, action, newMutableStyle.get()); } -static bool executeToggleStyle(Frame* frame, EditorCommandSource source, EditAction action, int propertyID, const char* offValue, const char* onValue) +static bool executeToggleStyle(Frame* frame, EditorCommandSource source, EditAction action, CSSPropertyID propertyID, const char* offValue, const char* onValue) { // Style is considered present when // Mac: present at the beginning of selection @@ -172,7 +172,7 @@ static bool executeToggleStyle(Frame* frame, EditorCommandSource source, EditAct return applyCommandToFrame(frame, source, action, style->style()); } -static bool executeApplyParagraphStyle(Frame* frame, EditorCommandSource source, EditAction action, int propertyID, const String& propertyValue) +static bool executeApplyParagraphStyle(Frame* frame, EditorCommandSource source, EditAction action, CSSPropertyID propertyID, const String& propertyValue) { RefPtr<StylePropertySet> style = StylePropertySet::create(); style->setProperty(propertyID, propertyValue); @@ -224,14 +224,14 @@ static bool expandSelectionToGranularity(Frame* frame, TextGranularity granulari return true; } -static TriState stateStyle(Frame* frame, int propertyID, const char* desiredValue) +static TriState stateStyle(Frame* frame, CSSPropertyID propertyID, const char* desiredValue) { if (frame->editor()->behavior().shouldToggleStyleBasedOnStartOfSelection()) return frame->editor()->selectionStartHasStyle(propertyID, desiredValue) ? TrueTriState : FalseTriState; return frame->editor()->selectionHasStyle(propertyID, desiredValue); } -static String valueStyle(Frame* frame, int propertyID) +static String valueStyle(Frame* frame, CSSPropertyID propertyID) { // FIXME: Rather than retrieving the style at the start of the current selection, // we should retrieve the style present throughout the selection for non-Mac platforms. diff --git a/Source/WebCore/editing/FrameSelection.cpp b/Source/WebCore/editing/FrameSelection.cpp index de2d65d7e..1a7468ee7 100644 --- a/Source/WebCore/editing/FrameSelection.cpp +++ b/Source/WebCore/editing/FrameSelection.cpp @@ -50,7 +50,6 @@ #include "InlineTextBox.h" #include "Page.h" #include "Range.h" -#include "RenderLayer.h" #include "RenderText.h" #include "RenderTextControl.h" #include "RenderTheme.h" @@ -76,7 +75,7 @@ using namespace HTMLNames; static inline LayoutUnit NoXPosForVerticalArrowNavigation() { - return std::numeric_limits<LayoutUnit>::min(); + return MIN_LAYOUT_UNIT; } CaretBase::CaretBase(CaretVisibility visibility) @@ -641,12 +640,15 @@ VisiblePosition FrameSelection::modifyMovingRight(TextGranularity granularity) } else pos = VisiblePosition(m_selection.extent(), m_selection.affinity()).right(true); break; - case WordGranularity: - if (visualWordMovementEnabled() - || (m_frame && m_frame->editor()->behavior().shouldMoveLeftRightByWordInVisualOrder())) { - pos = rightWordPosition(VisiblePosition(m_selection.extent(), m_selection.affinity())); - break; - } + case WordGranularity: { +#if USE(ICU_UNICODE) + // Visual word movement relies on isWordTextBreak which is not implemented in WinCE and QT. + // https://bugs.webkit.org/show_bug.cgi?id=81136. + bool skipsSpaceWhenMovingRight = m_frame && m_frame->editor()->behavior().shouldSkipSpaceWhenMovingRight(); + pos = rightWordPosition(VisiblePosition(m_selection.extent(), m_selection.affinity()), skipsSpaceWhenMovingRight); + break; +#endif + } case SentenceGranularity: case LineGranularity: case ParagraphGranularity: @@ -808,12 +810,13 @@ VisiblePosition FrameSelection::modifyMovingLeft(TextGranularity granularity) else pos = VisiblePosition(m_selection.extent(), m_selection.affinity()).left(true); break; - case WordGranularity: - if (visualWordMovementEnabled() - || (m_frame && m_frame->editor()->behavior().shouldMoveLeftRightByWordInVisualOrder())) { - pos = leftWordPosition(VisiblePosition(m_selection.extent(), m_selection.affinity())); - break; - } + case WordGranularity: { +#if USE(ICU_UNICODE) + bool skipsSpaceWhenMovingRight = m_frame && m_frame->editor()->behavior().shouldSkipSpaceWhenMovingRight(); + pos = leftWordPosition(VisiblePosition(m_selection.extent(), m_selection.affinity()), skipsSpaceWhenMovingRight); + break; +#endif + } case SentenceGranularity: case LineGranularity: case ParagraphGranularity: @@ -982,9 +985,9 @@ bool FrameSelection::modify(EAlteration alter, SelectionDirection direction, Tex } // FIXME: Maybe baseline would be better? -static bool absoluteCaretY(const VisiblePosition &c, LayoutUnit &y) +static bool absoluteCaretY(const VisiblePosition &c, int &y) { - LayoutRect rect = c.absoluteCaretBounds(); + IntRect rect = c.absoluteCaretBounds(); if (rect.isEmpty()) return false; y = rect.y() + rect.height() / 2; @@ -1023,12 +1026,12 @@ bool FrameSelection::modify(EAlteration alter, unsigned verticalDistance, Vertic break; } - LayoutUnit startY; + int startY; if (!absoluteCaretY(pos, startY)) return false; if (direction == DirectionUp) startY = -startY; - LayoutUnit lastY = startY; + int lastY = startY; VisiblePosition result; VisiblePosition next; @@ -1040,12 +1043,12 @@ bool FrameSelection::modify(EAlteration alter, unsigned verticalDistance, Vertic if (next.isNull() || next == p) break; - LayoutUnit nextY; + int nextY; if (!absoluteCaretY(next, nextY)) break; if (direction == DirectionUp) nextY = -nextY; - if (nextY - startY > static_cast<LayoutUnit>(verticalDistance)) + if (nextY - startY > static_cast<int>(verticalDistance)) break; if (nextY >= lastY) { lastY = nextY; @@ -1242,11 +1245,11 @@ LayoutRect FrameSelection::localCaretRect() return localCaretRectWithoutUpdate(); } -LayoutRect CaretBase::absoluteBoundsForLocalRect(Node* node, const LayoutRect& rect) const +IntRect CaretBase::absoluteBoundsForLocalRect(Node* node, const LayoutRect& rect) const { RenderObject* caretPainter = caretRenderer(node); if (!caretPainter) - return LayoutRect(); + return IntRect(); LayoutRect localRect(rect); if (caretPainter->isBox()) @@ -1254,7 +1257,7 @@ LayoutRect CaretBase::absoluteBoundsForLocalRect(Node* node, const LayoutRect& r return caretPainter->localToAbsoluteQuad(FloatRect(localRect)).enclosingBoundingBox(); } -LayoutRect FrameSelection::absoluteCaretBounds() +IntRect FrameSelection::absoluteCaretBounds() { recomputeCaretRect(); return m_absCaretBounds; @@ -1270,7 +1273,7 @@ static LayoutRect repaintRectForCaret(LayoutRect caret) return caret; } -LayoutRect CaretBase::caretRepaintRect(Node* node) const +IntRect CaretBase::caretRepaintRect(Node* node) const { return absoluteBoundsForLocalRect(node, repaintRectForCaret(localCaretRectWithoutUpdate())); } @@ -1292,19 +1295,22 @@ bool FrameSelection::recomputeCaretRect() if (oldRect == newRect && !m_absCaretBoundsDirty) return false; - LayoutRect oldAbsCaretBounds = m_absCaretBounds; + IntRect oldAbsCaretBounds = m_absCaretBounds; // FIXME: Rename m_caretRect to m_localCaretRect. m_absCaretBounds = absoluteBoundsForLocalRect(m_selection.start().deprecatedNode(), localCaretRectWithoutUpdate()); m_absCaretBoundsDirty = false; if (oldAbsCaretBounds == m_absCaretBounds) return false; - - LayoutRect oldAbsoluteCaretRepaintBounds = m_absoluteCaretRepaintBounds; + +#if ENABLE(TEXT_CARET) + IntRect oldAbsoluteCaretRepaintBounds = m_absoluteCaretRepaintBounds; +#endif + // We believe that we need to inflate the local rect before transforming it to obtain the repaint bounds. m_absoluteCaretRepaintBounds = caretRepaintRect(m_selection.start().deprecatedNode()); -#if ENABLE(TEXT_CARET) +#if ENABLE(TEXT_CARET) if (RenderView* view = toRenderView(m_frame->document()->renderer())) { // FIXME: make caret repainting container-aware. view->repaintRectangleInViewAndCompositedLayers(oldAbsoluteCaretRepaintBounds, false); @@ -1469,7 +1475,7 @@ bool FrameSelection::contains(const LayoutPoint& point) HitTestRequest request(HitTestRequest::ReadOnly | HitTestRequest::Active); HitTestResult result(point); - document->renderView()->layer()->hitTest(request, result); + document->renderView()->hitTest(request, result); Node* innerNode = result.innerNode(); if (!innerNode || !innerNode->renderer()) return false; @@ -1626,7 +1632,7 @@ void FrameSelection::focusedOrActiveStateChanged() // Update for caps lock state m_frame->eventHandler()->capsLockStateMayHaveChanged(); - // Because CSSStyleSelector::checkOneSelector() and + // Because StyleResolver::checkOneSelector() and // RenderTheme::isFocused() check if the frame is active, we have to // update style and theme state that depended on those. if (Node* node = m_frame->document()->focusedNode()) { @@ -1945,10 +1951,8 @@ void FrameSelection::revealSelection(const ScrollAlignment& alignment, bool reve // FIXME: This code only handles scrolling the startContainer's layer, but // the selection rect could intersect more than just that. // See <rdar://problem/4799899>. - if (RenderLayer* layer = start.deprecatedNode()->renderer()->enclosingLayer()) { - layer->scrollRectToVisible(rect, alignment, alignment); + if (start.deprecatedNode()->renderer()->scrollRectToVisible(rect, alignment, alignment)) updateAppearance(); - } } } diff --git a/Source/WebCore/editing/FrameSelection.h b/Source/WebCore/editing/FrameSelection.h index b1877f6d0..b4d92e33b 100644 --- a/Source/WebCore/editing/FrameSelection.h +++ b/Source/WebCore/editing/FrameSelection.h @@ -59,8 +59,8 @@ protected: void invalidateCaretRect(Node*, bool caretRectChanged = false); void clearCaretRect(); bool updateCaretRect(Document*, const VisiblePosition& caretPosition); - LayoutRect absoluteBoundsForLocalRect(Node*, const LayoutRect&) const; - LayoutRect caretRepaintRect(Node*) const; + IntRect absoluteBoundsForLocalRect(Node*, const LayoutRect&) const; + IntRect caretRepaintRect(Node*) const; bool shouldRepaintCaret(const RenderView*, bool isContentEditable) const; void paintCaret(Node*, GraphicsContext*, const LayoutPoint&, const LayoutRect& clipRect) const; RenderObject* caretRenderer(Node*) const; @@ -180,7 +180,7 @@ public: LayoutRect localCaretRect(); // Bounds of (possibly transformed) caret in absolute coords - LayoutRect absoluteCaretBounds(); + IntRect absoluteCaretBounds(); void setCaretRectNeedsUpdate() { CaretBase::setCaretRectNeedsUpdate(); } void willBeModified(EAlteration, SelectionDirection); @@ -295,8 +295,10 @@ private: RefPtr<EditingStyle> m_typingStyle; Timer<FrameSelection> m_caretBlinkTimer; - LayoutRect m_absCaretBounds; // absolute bounding rect for the caret - LayoutRect m_absoluteCaretRepaintBounds; + // The painted bounds of the caret in absolute coordinates + IntRect m_absCaretBounds; + // Similar to above, but inflated to ensure proper repaint (see https://bugs.webkit.org/show_bug.cgi?id=19086) + IntRect m_absoluteCaretRepaintBounds; bool m_absCaretBoundsDirty : 1; bool m_caretPaint : 1; bool m_isCaretBlinkingSuspended : 1; diff --git a/Source/WebCore/editing/IndentOutdentCommand.cpp b/Source/WebCore/editing/IndentOutdentCommand.cpp index e2cbec4c5..f0c413b9c 100644 --- a/Source/WebCore/editing/IndentOutdentCommand.cpp +++ b/Source/WebCore/editing/IndentOutdentCommand.cpp @@ -114,7 +114,7 @@ void IndentOutdentCommand::indentIntoBlockquote(const Position& start, const Pos insertNodeAt(targetBlockquote, start); else insertNodeBefore(targetBlockquote, outerBlock); - startOfContents = positionAfterNode(targetBlockquote.get()); + startOfContents = positionInParentAfterNode(targetBlockquote.get()); } moveParagraphWithClones(startOfContents, end, targetBlockquote.get(), outerBlock.get()); diff --git a/Source/WebCore/editing/InsertParagraphSeparatorCommand.cpp b/Source/WebCore/editing/InsertParagraphSeparatorCommand.cpp index 46f3c8a5d..0f12dce37 100644 --- a/Source/WebCore/editing/InsertParagraphSeparatorCommand.cpp +++ b/Source/WebCore/editing/InsertParagraphSeparatorCommand.cpp @@ -58,9 +58,10 @@ static Element* highestVisuallyEquivalentDivBelowRoot(Element* startBlock) return curBlock; } -InsertParagraphSeparatorCommand::InsertParagraphSeparatorCommand(Document *document, bool mustUseDefaultParagraphElement) +InsertParagraphSeparatorCommand::InsertParagraphSeparatorCommand(Document *document, bool mustUseDefaultParagraphElement, bool pasteBlockqutoeIntoUnquotedArea) : CompositeEditCommand(document) , m_mustUseDefaultParagraphElement(mustUseDefaultParagraphElement) + , m_pasteBlockqutoeIntoUnquotedArea(pasteBlockqutoeIntoUnquotedArea) { } @@ -226,8 +227,10 @@ void InsertParagraphSeparatorCommand::doApply() } else { // We can get here if we pasted a copied portion of a blockquote with a newline at the end and are trying to paste it // into an unquoted area. We then don't want the newline within the blockquote or else it will also be quoted. - if (Node* highestBlockquote = highestEnclosingNodeOfType(canonicalPos, &isMailBlockquote)) - startBlock = static_cast<Element*>(highestBlockquote); + if (m_pasteBlockqutoeIntoUnquotedArea) { + if (Node* highestBlockquote = highestEnclosingNodeOfType(canonicalPos, &isMailBlockquote)) + startBlock = static_cast<Element*>(highestBlockquote); + } // Most of the time we want to stay at the nesting level of the startBlock (e.g., when nesting within lists). However, // for div nodes, this can result in nested div tags that are hard to break out of. diff --git a/Source/WebCore/editing/InsertParagraphSeparatorCommand.h b/Source/WebCore/editing/InsertParagraphSeparatorCommand.h index 2eae77d37..9f7210824 100644 --- a/Source/WebCore/editing/InsertParagraphSeparatorCommand.h +++ b/Source/WebCore/editing/InsertParagraphSeparatorCommand.h @@ -34,13 +34,13 @@ class EditingStyle; class InsertParagraphSeparatorCommand : public CompositeEditCommand { public: - static PassRefPtr<InsertParagraphSeparatorCommand> create(Document* document, bool useDefaultParagraphElement = false) + static PassRefPtr<InsertParagraphSeparatorCommand> create(Document* document, bool useDefaultParagraphElement = false, bool pasteBlockqutoeIntoUnquotedArea = false) { - return adoptRef(new InsertParagraphSeparatorCommand(document, useDefaultParagraphElement)); + return adoptRef(new InsertParagraphSeparatorCommand(document, useDefaultParagraphElement, pasteBlockqutoeIntoUnquotedArea)); } private: - InsertParagraphSeparatorCommand(Document*, bool useDefaultParagraphElement); + InsertParagraphSeparatorCommand(Document*, bool useDefaultParagraphElement, bool pasteBlockqutoeIntoUnquotedArea); virtual void doApply(); @@ -56,6 +56,7 @@ private: RefPtr<EditingStyle> m_style; bool m_mustUseDefaultParagraphElement; + bool m_pasteBlockqutoeIntoUnquotedArea; }; } diff --git a/Source/WebCore/editing/InsertTextCommand.cpp b/Source/WebCore/editing/InsertTextCommand.cpp index 39fa2a897..cf2497fd2 100644 --- a/Source/WebCore/editing/InsertTextCommand.cpp +++ b/Source/WebCore/editing/InsertTextCommand.cpp @@ -46,6 +46,15 @@ InsertTextCommand::InsertTextCommand(Document* document, const String& text, boo { } +InsertTextCommand::InsertTextCommand(Document* document, const String& text, PassRefPtr<TextInsertionMarkerSupplier> markerSupplier) + : CompositeEditCommand(document) + , m_text(text) + , m_selectInsertedText(false) + , m_rebalanceType(RebalanceLeadingAndTrailingWhitespaces) + , m_markerSupplier(markerSupplier) +{ +} + Position InsertTextCommand::positionInsideTextNode(const Position& p) { Position pos = p; @@ -167,6 +176,8 @@ void InsertTextCommand::doApply() insertTextIntoNode(textNode, offset, m_text); endPosition = Position(textNode, offset + m_text.length()); + if (m_markerSupplier) + m_markerSupplier->addMarkersToTextNode(textNode.get(), offset, m_text); if (m_rebalanceType == RebalanceLeadingAndTrailingWhitespaces) { // The insertion may require adjusting adjacent whitespace, if it is present. diff --git a/Source/WebCore/editing/InsertTextCommand.h b/Source/WebCore/editing/InsertTextCommand.h index 1adba44ae..e5b2e222f 100644 --- a/Source/WebCore/editing/InsertTextCommand.h +++ b/Source/WebCore/editing/InsertTextCommand.h @@ -30,6 +30,17 @@ namespace WebCore { +class DocumentMarkerController; +class Text; + +class TextInsertionMarkerSupplier : public RefCounted<TextInsertionMarkerSupplier> { +public: + virtual ~TextInsertionMarkerSupplier() { } + virtual void addMarkersToTextNode(Text*, unsigned offsetOfInsertion, const String& textInserted) = 0; +protected: + TextInsertionMarkerSupplier() { } +}; + class InsertTextCommand : public CompositeEditCommand { public: enum RebalanceType { @@ -43,9 +54,15 @@ public: return adoptRef(new InsertTextCommand(document, text, selectInsertedText, rebalanceType)); } + static PassRefPtr<InsertTextCommand> createWithMarkerSupplier(Document* document, const String& text, PassRefPtr<TextInsertionMarkerSupplier> markerSupplier) + { + return adoptRef(new InsertTextCommand(document, text, markerSupplier)); + } + private: InsertTextCommand(Document*, const String& text, bool selectInsertedText, RebalanceType); + InsertTextCommand(Document*, const String& text, PassRefPtr<TextInsertionMarkerSupplier>); void deleteCharacter(); @@ -61,6 +78,7 @@ private: String m_text; bool m_selectInsertedText; RebalanceType m_rebalanceType; + RefPtr<TextInsertionMarkerSupplier> m_markerSupplier; }; } // namespace WebCore diff --git a/Source/WebCore/editing/MarkupAccumulator.cpp b/Source/WebCore/editing/MarkupAccumulator.cpp index 476904ebc..15752bcba 100644 --- a/Source/WebCore/editing/MarkupAccumulator.cpp +++ b/Source/WebCore/editing/MarkupAccumulator.cpp @@ -37,14 +37,13 @@ #include "KURL.h" #include "ProcessingInstruction.h" #include "XMLNSNames.h" -#include <wtf/text/StringBuilder.h> #include <wtf/unicode/CharacterNames.h> namespace WebCore { using namespace HTMLNames; -void appendCharactersReplacingEntities(StringBuilder& out, const UChar* content, size_t length, EntityMask entityMask) +void appendCharactersReplacingEntities(StringBuilder& result, const UChar* content, size_t length, EntityMask entityMask) { DEFINE_STATIC_LOCAL(const String, ampReference, ("&")); DEFINE_STATIC_LOCAL(const String, ltReference, ("<")); @@ -64,14 +63,14 @@ void appendCharactersReplacingEntities(StringBuilder& out, const UChar* content, for (size_t i = 0; i < length; ++i) { for (size_t m = 0; m < WTF_ARRAY_LENGTH(entityMaps); ++m) { if (content[i] == entityMaps[m].entity && entityMaps[m].mask & entityMask) { - out.append(content + positionAfterLastEntity, i - positionAfterLastEntity); - out.append(entityMaps[m].reference); + result.append(content + positionAfterLastEntity, i - positionAfterLastEntity); + result.append(entityMaps[m].reference); positionAfterLastEntity = i + 1; break; } } } - out.append(content + positionAfterLastEntity, length - positionAfterLastEntity); + result.append(content + positionAfterLastEntity, length - positionAfterLastEntity); } MarkupAccumulator::MarkupAccumulator(Vector<Node*>* nodes, EAbsoluteURLs resolveUrlsMethod, const Range* range) @@ -85,18 +84,15 @@ MarkupAccumulator::~MarkupAccumulator() { } -String MarkupAccumulator::serializeNodes(Node* node, Node* nodeToSkip, EChildrenOnly childrenOnly) +String MarkupAccumulator::serializeNodes(Node* targetNode, Node* nodeToSkip, EChildrenOnly childrenOnly) { - StringBuilder out; - serializeNodesWithNamespaces(node, nodeToSkip, childrenOnly, 0); - out.reserveCapacity(length()); - concatenateMarkup(out); - return out.toString(); + serializeNodesWithNamespaces(targetNode, nodeToSkip, childrenOnly, 0); + return m_markup.toString(); } -void MarkupAccumulator::serializeNodesWithNamespaces(Node* node, Node* nodeToSkip, EChildrenOnly childrenOnly, const Namespaces* namespaces) +void MarkupAccumulator::serializeNodesWithNamespaces(Node* targetNode, Node* nodeToSkip, EChildrenOnly childrenOnly, const Namespaces* namespaces) { - if (node == nodeToSkip) + if (targetNode == nodeToSkip) return; Namespaces namespaceHash; @@ -104,15 +100,15 @@ void MarkupAccumulator::serializeNodesWithNamespaces(Node* node, Node* nodeToSki namespaceHash = *namespaces; if (!childrenOnly) - appendStartTag(node, &namespaceHash); + appendStartTag(targetNode, &namespaceHash); - if (!(node->document()->isHTMLDocument() && elementCannotHaveEndTag(node))) { - for (Node* current = node->firstChild(); current; current = current->nextSibling()) + if (!(targetNode->document()->isHTMLDocument() && elementCannotHaveEndTag(targetNode))) { + for (Node* current = targetNode->firstChild(); current; current = current->nextSibling()) serializeNodesWithNamespaces(current, nodeToSkip, IncludeNode, &namespaceHash); } if (!childrenOnly) - appendEndTag(node); + appendEndTag(targetNode); } String MarkupAccumulator::resolveURLIfNeeded(const Element* element, const String& urlString) const @@ -134,23 +130,19 @@ String MarkupAccumulator::resolveURLIfNeeded(const Element* element, const Strin void MarkupAccumulator::appendString(const String& string) { - m_succeedingMarkup.append(string); + m_markup.append(string); } void MarkupAccumulator::appendStartTag(Node* node, Namespaces* namespaces) { - StringBuilder markup; - appendStartMarkup(markup, node, namespaces); - appendString(markup.toString()); + appendStartMarkup(m_markup, node, namespaces); if (m_nodes) m_nodes->append(node); } void MarkupAccumulator::appendEndTag(Node* node) { - StringBuilder markup; - appendEndMarkup(markup, node); - appendString(markup.toString()); + appendEndMarkup(m_markup, node); } size_t MarkupAccumulator::totalLength(const Vector<String>& strings) @@ -161,13 +153,9 @@ size_t MarkupAccumulator::totalLength(const Vector<String>& strings) return length; } -// FIXME: This is a very inefficient way of accumulating the markup. -// We're converting results of appendStartMarkup and appendEndMarkup from StringBuilder to String -// and then back to StringBuilder and again to String here. -void MarkupAccumulator::concatenateMarkup(StringBuilder& out) +void MarkupAccumulator::concatenateMarkup(StringBuilder& result) { - for (size_t i = 0; i < m_succeedingMarkup.size(); ++i) - out.append(m_succeedingMarkup[i]); + result.append(m_markup); } void MarkupAccumulator::appendAttributeValue(StringBuilder& result, const String& attribute, bool documentIsHTML) @@ -184,13 +172,13 @@ void MarkupAccumulator::appendQuotedURLAttributeValue(StringBuilder& result, con { ASSERT(element->isURLAttribute(const_cast<Attribute*>(&attribute))); const String resolvedURLString = resolveURLIfNeeded(element, attribute.value()); - UChar quoteChar = '\"'; + UChar quoteChar = '"'; String strippedURLString = resolvedURLString.stripWhiteSpace(); if (protocolIsJavaScript(strippedURLString)) { // minimal escaping for javascript urls if (strippedURLString.contains('"')) { if (strippedURLString.contains('\'')) - strippedURLString.replace('\"', """); + strippedURLString.replace('"', """); else quoteChar = '\''; } @@ -206,7 +194,7 @@ void MarkupAccumulator::appendQuotedURLAttributeValue(StringBuilder& result, con result.append(quoteChar); } -void MarkupAccumulator::appendNodeValue(StringBuilder& out, const Node* node, const Range* range, EntityMask entityMask) +void MarkupAccumulator::appendNodeValue(StringBuilder& result, const Node* node, const Range* range, EntityMask entityMask) { String str = node->nodeValue(); const UChar* characters = str.characters(); @@ -223,7 +211,7 @@ void MarkupAccumulator::appendNodeValue(StringBuilder& out, const Node* node, co } } - appendCharactersReplacingEntities(out, characters, length, entityMask); + appendCharactersReplacingEntities(result, characters, length, entityMask); } bool MarkupAccumulator::shouldAddNamespaceElement(const Element* element) @@ -246,13 +234,13 @@ bool MarkupAccumulator::shouldAddNamespaceAttribute(const Attribute& attribute, namespaces.set(emptyAtom.impl(), attribute.value().impl()); return false; } - + QualifiedName xmlnsPrefixAttr(xmlnsAtom, attribute.localName(), XMLNSNames::xmlnsNamespaceURI); if (attribute.name() == xmlnsPrefixAttr) { namespaces.set(attribute.localName().impl(), attribute.value().impl()); return false; } - + return true; } @@ -261,7 +249,7 @@ void MarkupAccumulator::appendNamespace(StringBuilder& result, const AtomicStrin namespaces.checkConsistency(); if (namespaceURI.isEmpty()) return; - + // Use emptyAtoms's impl() for both null and empty strings since the HashMap can't handle 0 as a key AtomicStringImpl* pre = prefix.isEmpty() ? emptyAtom.impl() : prefix.impl(); AtomicStringImpl* foundNS = namespaces.get(pre); @@ -286,24 +274,56 @@ EntityMask MarkupAccumulator::entityMaskForText(Text* text) const const QualifiedName* parentName = 0; if (text->parentElement()) parentName = &static_cast<Element*>(text->parentElement())->tagQName(); - + if (parentName && (*parentName == scriptTag || *parentName == styleTag || *parentName == xmpTag)) return EntityMaskInCDATA; return text->document()->isHTMLDocument() ? EntityMaskInHTMLPCDATA : EntityMaskInPCDATA; } -void MarkupAccumulator::appendText(StringBuilder& out, Text* text) +void MarkupAccumulator::appendText(StringBuilder& result, Text* text) { - appendNodeValue(out, text, m_range, entityMaskForText(text)); + appendNodeValue(result, text, m_range, entityMaskForText(text)); } -void MarkupAccumulator::appendComment(StringBuilder& out, const String& comment) +void MarkupAccumulator::appendComment(StringBuilder& result, const String& comment) { // FIXME: Comment content is not escaped, but XMLSerializer (and possibly other callers) should raise an exception if it includes "-->". - out.append("<!--"); - out.append(comment); - out.append("-->"); + static const char commentBegin[] = "<!--"; + result.append(commentBegin, sizeof(commentBegin) - 1); + result.append(comment); + static const char commentEnd[] = "-->"; + result.append(commentEnd, sizeof(commentEnd) - 1); +} + +void MarkupAccumulator::appendXMLDeclaration(StringBuilder& result, const Document* document) +{ + if (!document->hasXMLDeclaration()) + return; + + static const char xmlDeclStart[] = "<?xml version=\""; + result.append(xmlDeclStart, sizeof(xmlDeclStart) - 1); + result.append(document->xmlVersion()); + const String& encoding = document->xmlEncoding(); + if (!encoding.isEmpty()) { + static const char xmlEncoding[] = "\" encoding=\""; + result.append(xmlEncoding, sizeof(xmlEncoding) - 1); + result.append(encoding); + } + if (document->xmlStandaloneStatus() != Document::StandaloneUnspecified) { + static const char xmlStandalone[] = "\" standalone=\""; + result.append(xmlStandalone, sizeof(xmlStandalone) - 1); + if (document->xmlStandalone()) { + static const char standaloneYes[] = "yes"; + result.append(standaloneYes, sizeof(standaloneYes) - 1); + } else { + static const char standaloneNo[] = "no"; + result.append(standaloneNo, sizeof(standaloneNo) - 1); + } + } + + static const char xmlDeclEnd[] = "\"?>"; + result.append(xmlDeclEnd, sizeof(xmlDeclEnd) - 1); } void MarkupAccumulator::appendDocumentType(StringBuilder& result, const DocumentType* n) @@ -311,105 +331,114 @@ void MarkupAccumulator::appendDocumentType(StringBuilder& result, const Document if (n->name().isEmpty()) return; - result.append("<!DOCTYPE "); + static const char doctypeString[] = "<!DOCTYPE "; + result.append(doctypeString, sizeof(doctypeString) - 1); result.append(n->name()); if (!n->publicId().isEmpty()) { - result.append(" PUBLIC \""); + static const char publicString[] = " PUBLIC \""; + result.append(publicString, sizeof(publicString) - 1); result.append(n->publicId()); - result.append("\""); + result.append('"'); if (!n->systemId().isEmpty()) { - result.append(" \""); + result.append(' '); + result.append('"'); result.append(n->systemId()); - result.append("\""); + result.append('"'); } } else if (!n->systemId().isEmpty()) { - result.append(" SYSTEM \""); + static const char systemString[] = " SYSTEM \""; + result.append(systemString, sizeof(systemString) - 1); result.append(n->systemId()); - result.append("\""); + result.append('"'); } if (!n->internalSubset().isEmpty()) { - result.append(" ["); + result.append(' '); + result.append('['); result.append(n->internalSubset()); - result.append("]"); + result.append(']'); } - result.append(">"); + result.append('>'); } -void MarkupAccumulator::appendProcessingInstruction(StringBuilder& out, const String& target, const String& data) +void MarkupAccumulator::appendProcessingInstruction(StringBuilder& result, const String& target, const String& data) { // FIXME: PI data is not escaped, but XMLSerializer (and possibly other callers) this should raise an exception if it includes "?>". - out.append("<?"); - out.append(target); - out.append(" "); - out.append(data); - out.append("?>"); + result.append('<'); + result.append('?'); + result.append(target); + result.append(' '); + result.append(data); + result.append('?'); + result.append('>'); } -void MarkupAccumulator::appendElement(StringBuilder& out, Element* element, Namespaces* namespaces) +void MarkupAccumulator::appendElement(StringBuilder& result, Element* element, Namespaces* namespaces) { - appendOpenTag(out, element, namespaces); + appendOpenTag(result, element, namespaces); if (element->hasAttributes()) { unsigned length = element->attributeCount(); for (unsigned int i = 0; i < length; i++) - appendAttribute(out, element, *element->attributeItem(i), namespaces); + appendAttribute(result, element, *element->attributeItem(i), namespaces); } // Give an opportunity to subclasses to add their own attributes. - appendCustomAttributes(out, element, namespaces); + appendCustomAttributes(result, element, namespaces); - appendCloseTag(out, element); + appendCloseTag(result, element); } -void MarkupAccumulator::appendOpenTag(StringBuilder& out, Element* element, Namespaces* namespaces) +void MarkupAccumulator::appendOpenTag(StringBuilder& result, Element* element, Namespaces* namespaces) { - out.append('<'); - out.append(element->nodeNamePreservingCase()); + result.append('<'); + result.append(element->nodeNamePreservingCase()); if (!element->document()->isHTMLDocument() && namespaces && shouldAddNamespaceElement(element)) - appendNamespace(out, element->prefix(), element->namespaceURI(), *namespaces); + appendNamespace(result, element->prefix(), element->namespaceURI(), *namespaces); } -void MarkupAccumulator::appendCloseTag(StringBuilder& out, Element* element) +void MarkupAccumulator::appendCloseTag(StringBuilder& result, Element* element) { if (shouldSelfClose(element)) { if (element->isHTMLElement()) - out.append(' '); // XHTML 1.0 <-> HTML compatibility. - out.append('/'); + result.append(' '); // XHTML 1.0 <-> HTML compatibility. + result.append('/'); } - out.append('>'); + result.append('>'); } -void MarkupAccumulator::appendAttribute(StringBuilder& out, Element* element, const Attribute& attribute, Namespaces* namespaces) +void MarkupAccumulator::appendAttribute(StringBuilder& result, Element* element, const Attribute& attribute, Namespaces* namespaces) { bool documentIsHTML = element->document()->isHTMLDocument(); - out.append(' '); + result.append(' '); if (documentIsHTML) - out.append(attribute.name().localName()); + result.append(attribute.name().localName()); else - out.append(attribute.name().toString()); + result.append(attribute.name().toString()); - out.append('='); + result.append('='); if (element->isURLAttribute(const_cast<Attribute*>(&attribute))) - appendQuotedURLAttributeValue(out, element, attribute); + appendQuotedURLAttributeValue(result, element, attribute); else { - out.append('\"'); - appendAttributeValue(out, attribute.value(), documentIsHTML); - out.append('\"'); + result.append('"'); + appendAttributeValue(result, attribute.value(), documentIsHTML); + result.append('"'); } if (!documentIsHTML && namespaces && shouldAddNamespaceAttribute(attribute, *namespaces)) - appendNamespace(out, attribute.prefix(), attribute.namespaceURI(), *namespaces); + appendNamespace(result, attribute.prefix(), attribute.namespaceURI(), *namespaces); } -void MarkupAccumulator::appendCDATASection(StringBuilder& out, const String& section) +void MarkupAccumulator::appendCDATASection(StringBuilder& result, const String& section) { // FIXME: CDATA content is not escaped, but XMLSerializer (and possibly other callers) should raise an exception if it includes "]]>". - out.append("<![CDATA["); - out.append(section); - out.append("]]>"); + static const char cdataBegin[] = "<![CDATA["; + result.append(cdataBegin, sizeof(cdataBegin) - 1); + result.append(section); + static const char cdataEnd[] = "]]>"; + result.append(cdataEnd, sizeof(cdataEnd) - 1); } void MarkupAccumulator::appendStartMarkup(StringBuilder& result, const Node* node, Namespaces* namespaces) @@ -425,6 +454,8 @@ void MarkupAccumulator::appendStartMarkup(StringBuilder& result, const Node* nod appendComment(result, static_cast<const Comment*>(node)->data()); break; case Node::DOCUMENT_NODE: + appendXMLDeclaration(result, static_cast<const Document*>(node)); + break; case Node::DOCUMENT_FRAGMENT_NODE: break; case Node::DOCUMENT_TYPE_NODE: @@ -469,7 +500,7 @@ bool MarkupAccumulator::elementCannotHaveEndTag(const Node* node) { if (!node->isHTMLElement()) return false; - + // FIXME: ieForbidsInsertHTML may not be the right function to call here // ieForbidsInsertHTML is used to disallow setting innerHTML/outerHTML // or createContextualFragment. It does not necessarily align with diff --git a/Source/WebCore/editing/MarkupAccumulator.h b/Source/WebCore/editing/MarkupAccumulator.h index a31f91285..367b79fa6 100644 --- a/Source/WebCore/editing/MarkupAccumulator.h +++ b/Source/WebCore/editing/MarkupAccumulator.h @@ -30,6 +30,7 @@ #include "markup.h" #include <wtf/HashMap.h> #include <wtf/Vector.h> +#include <wtf/text/StringBuilder.h> namespace WebCore { @@ -66,54 +67,55 @@ struct EntityDescription { // FIXME: Noncopyable? class MarkupAccumulator { public: - MarkupAccumulator(Vector<Node*>*, EAbsoluteURLs resolveUrlsMethod, const Range* = 0); + MarkupAccumulator(Vector<Node*>*, EAbsoluteURLs, const Range* = 0); virtual ~MarkupAccumulator(); - String serializeNodes(Node* node, Node* nodeToSkip, EChildrenOnly childrenOnly); + String serializeNodes(Node* targetNode, Node* nodeToSkip, EChildrenOnly); - static void appendComment(StringBuilder& out, const String& comment); + static void appendComment(StringBuilder&, const String&); protected: virtual void appendString(const String&); void appendStartTag(Node*, Namespaces* = 0); virtual void appendEndTag(Node*); static size_t totalLength(const Vector<String>&); - size_t length() const { return totalLength(m_succeedingMarkup); } - void concatenateMarkup(StringBuilder& out); - void appendAttributeValue(StringBuilder& result, const String& attribute, bool documentIsHTML); + size_t length() const { return m_markup.length(); } + void concatenateMarkup(StringBuilder&); + void appendAttributeValue(StringBuilder&, const String&, bool); virtual void appendCustomAttributes(StringBuilder&, Element*, Namespaces*); - void appendNodeValue(StringBuilder& out, const Node*, const Range*, EntityMask); + void appendNodeValue(StringBuilder&, const Node*, const Range*, EntityMask); bool shouldAddNamespaceElement(const Element*); bool shouldAddNamespaceAttribute(const Attribute&, Namespaces&); - void appendNamespace(StringBuilder& result, const AtomicString& prefix, const AtomicString& namespaceURI, Namespaces&); + void appendNamespace(StringBuilder&, const AtomicString& prefix, const AtomicString& namespaceURI, Namespaces&); EntityMask entityMaskForText(Text*) const; - virtual void appendText(StringBuilder& out, Text*); - void appendDocumentType(StringBuilder& result, const DocumentType*); - void appendProcessingInstruction(StringBuilder& out, const String& target, const String& data); - virtual void appendElement(StringBuilder& out, Element*, Namespaces*); - void appendOpenTag(StringBuilder& out, Element*, Namespaces*); - void appendCloseTag(StringBuilder& out, Element*); - void appendAttribute(StringBuilder& out, Element*, const Attribute&, Namespaces*); - void appendCDATASection(StringBuilder& out, const String& section); - void appendStartMarkup(StringBuilder& result, const Node*, Namespaces*); + virtual void appendText(StringBuilder&, Text*); + void appendXMLDeclaration(StringBuilder&, const Document*); + void appendDocumentType(StringBuilder&, const DocumentType*); + void appendProcessingInstruction(StringBuilder&, const String& target, const String& data); + virtual void appendElement(StringBuilder&, Element*, Namespaces*); + void appendOpenTag(StringBuilder&, Element*, Namespaces*); + void appendCloseTag(StringBuilder&, Element*); + void appendAttribute(StringBuilder&, Element*, const Attribute&, Namespaces*); + void appendCDATASection(StringBuilder&, const String&); + void appendStartMarkup(StringBuilder&, const Node*, Namespaces*); bool shouldSelfClose(const Node*); - bool elementCannotHaveEndTag(const Node* node); - void appendEndMarkup(StringBuilder& result, const Node*); + bool elementCannotHaveEndTag(const Node*); + void appendEndMarkup(StringBuilder&, const Node*); Vector<Node*>* const m_nodes; const Range* const m_range; private: - String resolveURLIfNeeded(const Element*, const String& urlString) const; - void appendQuotedURLAttributeValue(StringBuilder& result, const Element*, const Attribute&); - void serializeNodesWithNamespaces(Node*, Node* nodeToSkip, EChildrenOnly, const Namespaces*); + String resolveURLIfNeeded(const Element*, const String&) const; + void appendQuotedURLAttributeValue(StringBuilder&, const Element*, const Attribute&); + void serializeNodesWithNamespaces(Node* targetNode, Node* nodeToSkip, EChildrenOnly, const Namespaces*); - Vector<String> m_succeedingMarkup; + StringBuilder m_markup; const EAbsoluteURLs m_resolveURLsMethod; }; // FIXME: This method should be integrated with MarkupAccumulator. -void appendCharactersReplacingEntities(StringBuilder& out, const UChar* content, size_t length, EntityMask); +void appendCharactersReplacingEntities(StringBuilder&, const UChar*, size_t, EntityMask); } diff --git a/Source/WebCore/editing/RenderedPosition.cpp b/Source/WebCore/editing/RenderedPosition.cpp index 8c900232a..7490d71df 100644 --- a/Source/WebCore/editing/RenderedPosition.cpp +++ b/Source/WebCore/editing/RenderedPosition.cpp @@ -224,13 +224,13 @@ Position RenderedPosition::positionAtRightBoundaryOfBiDiRun() const return createLegacyEditingPosition(prevLeafChild()->renderer()->node(), prevLeafChild()->caretRightmostOffset()); } -LayoutRect RenderedPosition::absoluteRect(LayoutUnit* extraWidthToEndOfLine) const +IntRect RenderedPosition::absoluteRect(LayoutUnit* extraWidthToEndOfLine) const { if (isNull()) - return LayoutRect(); + return IntRect(); - LayoutRect localRect = m_renderer->localCaretRect(m_inlineBox, m_offset, extraWidthToEndOfLine); - return localRect == LayoutRect() ? LayoutRect() : m_renderer->localToAbsoluteQuad(FloatRect(localRect)).enclosingBoundingBox(); + IntRect localRect = pixelSnappedIntRect(m_renderer->localCaretRect(m_inlineBox, m_offset, extraWidthToEndOfLine)); + return localRect == IntRect() ? IntRect() : m_renderer->localToAbsoluteQuad(FloatRect(localRect)).enclosingBoundingBox(); } bool renderObjectContainsPosition(RenderObject* target, const Position& position) diff --git a/Source/WebCore/editing/RenderedPosition.h b/Source/WebCore/editing/RenderedPosition.h index eef01a1f4..721344a15 100644 --- a/Source/WebCore/editing/RenderedPosition.h +++ b/Source/WebCore/editing/RenderedPosition.h @@ -67,8 +67,7 @@ public: Position positionAtLeftBoundaryOfBiDiRun() const; Position positionAtRightBoundaryOfBiDiRun() const; - LayoutRect absoluteRect() const { return absoluteRect(0); } - LayoutRect absoluteRect(LayoutUnit& extraWidthToEndOfLine) const { return absoluteRect(&extraWidthToEndOfLine); } + IntRect absoluteRect(LayoutUnit* extraWidthToEndOfLine = 0) const; private: bool operator==(const RenderedPosition&) const { return false; } @@ -81,8 +80,6 @@ private: bool atLeftBoundaryOfBidiRun(ShouldMatchBidiLevel, unsigned char bidiLevelOfRun) const; bool atRightBoundaryOfBidiRun(ShouldMatchBidiLevel, unsigned char bidiLevelOfRun) const; - LayoutRect absoluteRect(LayoutUnit* extraWidthToEndOfLine) const; - RenderObject* m_renderer; InlineBox* m_inlineBox; int m_offset; diff --git a/Source/WebCore/editing/ReplaceSelectionCommand.cpp b/Source/WebCore/editing/ReplaceSelectionCommand.cpp index af5a8be13..2c699fe67 100644 --- a/Source/WebCore/editing/ReplaceSelectionCommand.cpp +++ b/Source/WebCore/editing/ReplaceSelectionCommand.cpp @@ -49,6 +49,7 @@ #include "RenderInline.h" #include "RenderObject.h" #include "RenderText.h" +#include "SimplifyMarkupCommand.h" #include "SmartReplace.h" #include "StylePropertySet.h" #include "TextIterator.h" @@ -60,8 +61,6 @@ namespace WebCore { -typedef Vector<RefPtr<Node> > NodeVector; - using namespace HTMLNames; enum EFragmentType { EmptyFragment, SingleTextNodeFragment, TreeFragment }; @@ -123,9 +122,16 @@ static Position positionAvoidingPrecedingNodes(Position pos) // same. E.g., // <div>foo^</div>^ // The two positions above are the same visual position, but we want to stay in the same block. - Node* stopNode = pos.deprecatedNode()->enclosingBlockFlowElement(); - while (stopNode != pos.deprecatedNode() && VisiblePosition(pos) == VisiblePosition(pos.next())) - pos = pos.next(); + Node* enclosingBlockNode = enclosingBlock(pos.containerNode()); + for (Position nextPosition = pos; nextPosition.containerNode() != enclosingBlockNode; pos = nextPosition) { + if (pos.containerNode()->nonShadowBoundaryParentNode()) + nextPosition = positionInParentAfterNode(pos.containerNode()); + + if (nextPosition == pos + || enclosingBlock(nextPosition.containerNode()) != enclosingBlockNode + || VisiblePosition(pos) != VisiblePosition(nextPosition)) + break; + } return pos; } @@ -438,11 +444,12 @@ static bool isHeaderElement(Node* a) if (!a) return false; - return a->hasTagName(h1Tag) || - a->hasTagName(h2Tag) || - a->hasTagName(h3Tag) || - a->hasTagName(h4Tag) || - a->hasTagName(h5Tag); + return a->hasTagName(h1Tag) + || a->hasTagName(h2Tag) + || a->hasTagName(h3Tag) + || a->hasTagName(h4Tag) + || a->hasTagName(h5Tag) + || a->hasTagName(h6Tag); } static bool haveSameTagName(Node* a, Node* b) @@ -545,54 +552,6 @@ void ReplaceSelectionCommand::removeRedundantStylesAndKeepStyleSpanInline(Insert } } -void ReplaceSelectionCommand::removeRedundantMarkup(InsertedNodes& insertedNodes) -{ - Node* pastEndNode = insertedNodes.pastLastLeaf(); - Node* rootNode = insertedNodes.firstNodeInserted()->parentNode(); - Vector<Node*> nodesToRemove; - - // Walk through the inserted nodes, to see if there are elements that could be removed - // without affecting the style. The goal is to produce leaner markup even when starting - // from a verbose fragment. - // We look at inline elements as well as non top level divs that don't have attributes. - for (Node* node = insertedNodes.firstNodeInserted(); node && node != pastEndNode; node = node->traverseNextNode()) { - if (node->firstChild() || (node->isTextNode() && node->nextSibling())) - continue; - - Node* startingNode = node->parentNode(); - RenderStyle* startingStyle = startingNode->renderStyle(); - if (!startingStyle) - continue; - Node* currentNode = startingNode; - Node* topNodeWithStartingStyle = 0; - while (currentNode != rootNode) { - if (currentNode->parentNode() != rootNode && isRemovableBlock(currentNode)) - nodesToRemove.append(currentNode); - - currentNode = currentNode->parentNode(); - if (!currentNode->renderer() || !currentNode->renderer()->isRenderInline() || toRenderInline(currentNode->renderer())->alwaysCreateLineBoxes()) - continue; - - if (currentNode && currentNode->firstChild() != currentNode->lastChild()) { - topNodeWithStartingStyle = 0; - break; - } - - unsigned context; - if (currentNode->renderStyle()->diff(startingStyle, context) == StyleDifferenceEqual) - topNodeWithStartingStyle = currentNode; - - } - if (topNodeWithStartingStyle) { - for (Node* node = startingNode; node != topNodeWithStartingStyle; node = node->parentNode()) - nodesToRemove.append(node); - } - } - // we perform all the DOM mutations at once. - for (size_t i = 0; i < nodesToRemove.size(); ++i) - removeNodePreservingChildren(nodesToRemove[i]); -} - static inline bool nodeHasVisibleRenderText(Text* text) { return text->renderer() && toRenderText(text->renderer())->renderedTextLength() > 0; @@ -930,7 +889,7 @@ void ReplaceSelectionCommand::doApply() // Adjust insertionPos to prevent nesting. // If the start was in a Mail blockquote, we will have already handled adjusting insertionPos above. - if (m_preventNesting && startBlock && !startIsInsideMailBlockquote) { + if (m_preventNesting && startBlock && !isTableCell(startBlock) && !startIsInsideMailBlockquote) { ASSERT(startBlock != currentRoot); VisiblePosition visibleInsertionPos(insertionPos); if (isEndOfBlock(visibleInsertionPos) && !(isStartOfBlock(visibleInsertionPos) && fragment.hasInterchangeNewlineAtEnd())) @@ -959,6 +918,10 @@ void ReplaceSelectionCommand::doApply() bool handledStyleSpans = handleStyleSpansBeforeInsertion(fragment, insertionPos); + // We're finished if there is nothing to add. + if (fragment.isEmpty() || !fragment.firstChild()) + return; + // If we are not trying to match the destination style we prefer a position // that is outside inline elements that provide style. // This way we can produce a less verbose markup. @@ -981,11 +944,7 @@ void ReplaceSelectionCommand::doApply() // FIXME: When pasting rich content we're often prevented from heading down the fast path by style spans. Try // again here if they've been removed. - - // We're finished if there is nothing to add. - if (fragment.isEmpty() || !fragment.firstChild()) - return; - + // 1) Insert the content. // 2) Remove redundant styles and style tags, this inner <b> for example: <b>foo <b>bar</b> baz</b>. // 3) Merge the start of the added content with the content before the position being pasted into. @@ -1062,7 +1021,7 @@ void ReplaceSelectionCommand::doApply() removeRedundantStylesAndKeepStyleSpanInline(insertedNodes); if (m_sanitizeFragment) - removeRedundantMarkup(insertedNodes); + applyCommandToComposite(SimplifyMarkupCommand::create(document(), insertedNodes.firstNodeInserted(), insertedNodes.pastLastLeaf())); // Setup m_startOfInsertedContent and m_endOfInsertedContent. This should be the last two lines of code that access insertedNodes. m_startOfInsertedContent = firstPositionInOrBeforeNode(insertedNodes.firstNodeInserted()); @@ -1118,10 +1077,12 @@ void ReplaceSelectionCommand::doApply() RefPtr<Node> newListItem = createListItemElement(document()); insertNodeAfter(newListItem, enclosingNode); setEndingSelection(VisiblePosition(firstPositionInNode(newListItem.get()))); - } else + } else { // Use a default paragraph element (a plain div) for the empty paragraph, using the last paragraph // block's style seems to annoy users. - insertParagraphSeparator(true); + insertParagraphSeparator(true, !startIsInsideMailBlockquote && highestEnclosingNodeOfType(endOfInsertedContent.deepEquivalent(), + isMailBlockquote, CannotCrossEditingBoundary, insertedNodes.firstNodeInserted()->parentNode())); + } // Select up to the paragraph separator that was added. lastPositionToSelect = endingSelection().visibleStart().deepEquivalent(); diff --git a/Source/WebCore/editing/ReplaceSelectionCommand.h b/Source/WebCore/editing/ReplaceSelectionCommand.h index da053b834..3a0b5a516 100644 --- a/Source/WebCore/editing/ReplaceSelectionCommand.h +++ b/Source/WebCore/editing/ReplaceSelectionCommand.h @@ -68,7 +68,7 @@ private: Node* firstNodeInserted() const { return m_firstNodeInserted.get(); } Node* lastLeafInserted() const { return m_lastNodeInserted->lastDescendant(); } - Node* pastLastLeaf() const { return m_firstNodeInserted ? lastLeafInserted()->traverseNextNode() : 0; } + Node* pastLastLeaf() const { return m_lastNodeInserted ? lastLeafInserted()->traverseNextNode() : 0; } private: RefPtr<Node> m_firstNodeInserted; @@ -89,7 +89,6 @@ private: void removeUnrenderedTextNodesAtEnds(InsertedNodes&); void removeRedundantStylesAndKeepStyleSpanInline(InsertedNodes&); - void removeRedundantMarkup(InsertedNodes&); void handleStyleSpans(InsertedNodes&); void handlePasteAsQuotationNode(); diff --git a/Source/WebCore/editing/SimplifyMarkupCommand.cpp b/Source/WebCore/editing/SimplifyMarkupCommand.cpp new file mode 100644 index 000000000..ebe386e67 --- /dev/null +++ b/Source/WebCore/editing/SimplifyMarkupCommand.cpp @@ -0,0 +1,91 @@ +/* + * Copyright (C) 2012 Apple Computer, Inc. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY APPLE COMPUTER, INC. ``AS IS'' AND ANY + * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL APPLE COMPUTER, INC. OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY + * OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#include "config.h" +#include "SimplifyMarkupCommand.h" + +#include "NodeRenderStyle.h" +#include "RenderInline.h" +#include "RenderObject.h" +#include "RenderStyle.h" + +namespace WebCore { + +SimplifyMarkupCommand::SimplifyMarkupCommand(Document* document, Node* firstNode, Node* nodeAfterLast) + : CompositeEditCommand(document), m_firstNode(firstNode), m_nodeAfterLast(nodeAfterLast) +{ +} + +void SimplifyMarkupCommand::doApply() +{ + Node* rootNode = m_firstNode->parentNode(); + Vector<Node*> nodesToRemove; + + // Walk through the inserted nodes, to see if there are elements that could be removed + // without affecting the style. The goal is to produce leaner markup even when starting + // from a verbose fragment. + // We look at inline elements as well as non top level divs that don't have attributes. + for (Node* node = m_firstNode.get(); node && node != m_nodeAfterLast; node = node->traverseNextNode()) { + if (node->firstChild() || (node->isTextNode() && node->nextSibling())) + continue; + + Node* startingNode = node->parentNode(); + RenderStyle* startingStyle = startingNode->renderStyle(); + if (!startingStyle) + continue; + Node* currentNode = startingNode; + Node* topNodeWithStartingStyle = 0; + while (currentNode != rootNode) { + if (currentNode->parentNode() != rootNode && isRemovableBlock(currentNode)) + nodesToRemove.append(currentNode); + + currentNode = currentNode->parentNode(); + if (!currentNode) + break; + + if (!currentNode->renderer() || !currentNode->renderer()->isRenderInline() || toRenderInline(currentNode->renderer())->alwaysCreateLineBoxes()) + continue; + + if (currentNode->firstChild() != currentNode->lastChild()) { + topNodeWithStartingStyle = 0; + break; + } + + unsigned context; + if (currentNode->renderStyle()->diff(startingStyle, context) == StyleDifferenceEqual) + topNodeWithStartingStyle = currentNode; + + } + if (topNodeWithStartingStyle) { + for (Node* node = startingNode; node != topNodeWithStartingStyle; node = node->parentNode()) + nodesToRemove.append(node); + } + } + // we perform all the DOM mutations at once. + for (size_t i = 0; i < nodesToRemove.size(); ++i) + removeNodePreservingChildren(nodesToRemove[i]); +} + +} // namespace WebCore diff --git a/Source/WebCore/editing/SimplifyMarkupCommand.h b/Source/WebCore/editing/SimplifyMarkupCommand.h new file mode 100644 index 000000000..21ff1adc4 --- /dev/null +++ b/Source/WebCore/editing/SimplifyMarkupCommand.h @@ -0,0 +1,51 @@ +/* + * Copyright (C) 2012 Apple Inc. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY APPLE COMPUTER, INC. ``AS IS'' AND ANY + * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL APPLE COMPUTER, INC. OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY + * OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#ifndef SimplifyMarkupCommand_h +#define SimplifyMarkupCommand_h + +#include "CompositeEditCommand.h" + +namespace WebCore { + +class SimplifyMarkupCommand : public CompositeEditCommand { +public: + static PassRefPtr<SimplifyMarkupCommand> create(Document* document, Node* firstNode, Node* nodeAfterLast) + { + return adoptRef(new SimplifyMarkupCommand(document, firstNode, nodeAfterLast)); + } + +private: + SimplifyMarkupCommand(Document*, Node* firstNode, Node* nodeAfterLast); + + virtual void doApply(); + + RefPtr<Node> m_firstNode; + RefPtr<Node> m_nodeAfterLast; +}; + +} // namespace WebCore + +#endif // SimplifyMarkupCommand_h diff --git a/Source/WebCore/editing/SpellChecker.cpp b/Source/WebCore/editing/SpellChecker.cpp index e1243b14d..8107fdbaa 100644 --- a/Source/WebCore/editing/SpellChecker.cpp +++ b/Source/WebCore/editing/SpellChecker.cpp @@ -118,7 +118,12 @@ bool SpellChecker::canCheckAsynchronously(Range* range) const bool SpellChecker::isCheckable(Range* range) const { - return range && range->firstNode() && range->firstNode()->renderer(); + if (!range || !range->firstNode() || !range->firstNode()->renderer()) + return false; + const Node* node = range->startContainer(); + if (node && node->isElementNode() && !toElement(node)->isSpellCheckingEnabled()) + return false; + return true; } void SpellChecker::requestCheckingFor(PassRefPtr<SpellCheckRequest> request) @@ -183,5 +188,24 @@ void SpellChecker::didCheck(int sequence, const Vector<TextCheckingResult>& resu m_timerToProcessQueuedRequest.startOneShot(0); } +void SpellChecker::didCheckSucceeded(int sequence, const Vector<TextCheckingResult>& results) +{ + if (m_processingRequest->sequence() == sequence) { + unsigned markers = 0; + if (m_processingRequest->mask() & TextCheckingTypeSpelling) + markers |= DocumentMarker::Spelling; + if (m_processingRequest->mask() & TextCheckingTypeGrammar) + markers |= DocumentMarker::Grammar; + if (markers) + m_frame->document()->markers()->removeMarkers(m_processingRequest->checkingRange().get(), markers); + } + didCheck(sequence, results); +} + +void SpellChecker::didCheckCanceled(int sequence) +{ + Vector<TextCheckingResult> results; + didCheck(sequence, results); +} } // namespace WebCore diff --git a/Source/WebCore/editing/SpellChecker.h b/Source/WebCore/editing/SpellChecker.h index 07f51e431..835a57c55 100644 --- a/Source/WebCore/editing/SpellChecker.h +++ b/Source/WebCore/editing/SpellChecker.h @@ -81,7 +81,8 @@ public: bool isCheckable(Range*) const; void requestCheckingFor(PassRefPtr<SpellCheckRequest>); - void didCheck(int sequence, const Vector<TextCheckingResult>&); + void didCheckSucceeded(int sequence, const Vector<TextCheckingResult>&); + void didCheckCanceled(int sequence); int lastRequestSequence() const { @@ -101,6 +102,7 @@ private: void timerFiredToProcessQueuedRequest(Timer<SpellChecker>*); void invokeRequest(PassRefPtr<SpellCheckRequest>); void enqueueRequest(PassRefPtr<SpellCheckRequest>); + void didCheck(int sequence, const Vector<TextCheckingResult>&); Frame* m_frame; int m_lastRequestSequence; diff --git a/Source/WebCore/editing/SpellingCorrectionCommand.cpp b/Source/WebCore/editing/SpellingCorrectionCommand.cpp index 3cf2d732c..225cf9351 100644 --- a/Source/WebCore/editing/SpellingCorrectionCommand.cpp +++ b/Source/WebCore/editing/SpellingCorrectionCommand.cpp @@ -26,12 +26,12 @@ #include "config.h" #include "SpellingCorrectionCommand.h" +#include "AlternativeTextController.h" #include "Document.h" #include "DocumentFragment.h" #include "Frame.h" #include "ReplaceSelectionCommand.h" #include "SetSelectionCommand.h" -#include "SpellingCorrectionController.h" #include "TextIterator.h" #include "markup.h" diff --git a/Source/WebCore/editing/SurroundingText.cpp b/Source/WebCore/editing/SurroundingText.cpp new file mode 100644 index 000000000..df9aa7e80 --- /dev/null +++ b/Source/WebCore/editing/SurroundingText.cpp @@ -0,0 +1,88 @@ +/* + * Copyright (C) 2012 Google Inc. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above + * copyright notice, this list of conditions and the following disclaimer + * in the documentation and/or other materials provided with the + * distribution. + * * Neither the name of Google Inc. nor the names of its + * contributors may be used to endorse or promote products derived from + * this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#include "config.h" +#include "SurroundingText.h" + +#include "Document.h" +#include "Range.h" +#include "TextIterator.h" +#include "VisiblePosition.h" +#include "VisibleSelection.h" +#include "visible_units.h" + +namespace WebCore { + +SurroundingText::SurroundingText(const VisiblePosition& visiblePosition, unsigned maxLength) + : m_positionOffsetInContent(0) +{ + const unsigned halfMaxLength = maxLength / 2; + CharacterIterator forwardIterator(makeRange(visiblePosition, endOfDocument(visiblePosition)).get(), TextIteratorStopsOnFormControls); + forwardIterator.advance(maxLength - halfMaxLength); + + Position position = visiblePosition.deepEquivalent().parentAnchoredEquivalent(); + Document* document = position.document(); + if (!Range::create(document, position, forwardIterator.range()->startPosition())->text().length()) + return; + + BackwardsCharacterIterator backwardsIterator(makeRange(startOfDocument(visiblePosition), visiblePosition).get(), TextIteratorStopsOnFormControls); + backwardsIterator.advance(halfMaxLength); + + m_positionOffsetInContent = Range::create(document, backwardsIterator.range()->endPosition(), position)->text().length(); + m_contentRange = Range::create(document, backwardsIterator.range()->endPosition(), forwardIterator.range()->startPosition()); +} + +PassRefPtr<Range> SurroundingText::rangeFromContentOffsets(unsigned startOffsetInContent, unsigned endOffsetInContent) +{ + if (startOffsetInContent >= endOffsetInContent || endOffsetInContent > content().length()) + return 0; + + CharacterIterator iterator(m_contentRange.get()); + iterator.advance(startOffsetInContent); + + Position start = iterator.range()->startPosition(); + iterator.advance(endOffsetInContent - startOffsetInContent); + Position end = iterator.range()->startPosition(); + return Range::create(start.document(), start, end); +} + +String SurroundingText::content() const +{ + if (m_contentRange) + return m_contentRange->text(); + return String(); +} + +unsigned SurroundingText::positionOffsetInContent() const +{ + return m_positionOffsetInContent; +} + +} // namespace WebCore diff --git a/Source/WebCore/editing/SurroundingText.h b/Source/WebCore/editing/SurroundingText.h new file mode 100644 index 000000000..6069875a1 --- /dev/null +++ b/Source/WebCore/editing/SurroundingText.h @@ -0,0 +1,59 @@ +/* + * Copyright (C) 2012 Google Inc. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above + * copyright notice, this list of conditions and the following disclaimer + * in the documentation and/or other materials provided with the + * distribution. + * * Neither the name of Google Inc. nor the names of its + * contributors may be used to endorse or promote products derived from + * this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#ifndef SurroundingText_h +#define SurroundingText_h + +#include "PlatformString.h" + +namespace WebCore { + +class Range; +class VisiblePosition; + +class SurroundingText { + WTF_MAKE_NONCOPYABLE(SurroundingText); +public: + SurroundingText(const VisiblePosition&, unsigned maxLength); + + String content() const; + unsigned positionOffsetInContent() const; + + PassRefPtr<Range> rangeFromContentOffsets(unsigned startOffsetInContent, unsigned endOffsetInContent); + +private: + RefPtr<Range> m_contentRange; + size_t m_positionOffsetInContent; +}; + +} // namespace WebCore + +#endif // SurroundingText_h + diff --git a/Source/WebCore/editing/TextCheckingHelper.h b/Source/WebCore/editing/TextCheckingHelper.h index 47a61b360..4887ed5c1 100644 --- a/Source/WebCore/editing/TextCheckingHelper.h +++ b/Source/WebCore/editing/TextCheckingHelper.h @@ -22,11 +22,15 @@ #define TextCheckingHelper_h #include "EditorClient.h" +#include "ExceptionCode.h" +#include "TextChecking.h" +#include <wtf/text/WTFString.h> namespace WebCore { class Range; class Position; +struct TextCheckingResult; class TextCheckingParagraph { public: diff --git a/Source/WebCore/editing/TextInsertionBaseCommand.cpp b/Source/WebCore/editing/TextInsertionBaseCommand.cpp new file mode 100644 index 000000000..e35494770 --- /dev/null +++ b/Source/WebCore/editing/TextInsertionBaseCommand.cpp @@ -0,0 +1,86 @@ +/* + * Copyright (C) 2012 Apple Inc. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY APPLE INC. ``AS IS'' AND ANY + * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL APPLE INC. OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY + * OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#include "config.h" +#include "TextInsertionBaseCommand.h" + +#include "BeforeTextInsertedEvent.h" +#include "Document.h" +#include "Element.h" +#include "Frame.h" +#include "Node.h" + +namespace WebCore { + +TextInsertionBaseCommand::TextInsertionBaseCommand(Document* document) + : CompositeEditCommand(document) +{ +} + +void TextInsertionBaseCommand::applyTextInsertionCommand(Frame* frame, PassRefPtr<TextInsertionBaseCommand> command, const VisibleSelection& selectionForInsertion, const VisibleSelection& endingSelection) +{ + bool changeSelection = selectionForInsertion != endingSelection; + if (changeSelection) { + command->setStartingSelection(selectionForInsertion); + command->setEndingSelection(selectionForInsertion); + } + applyCommand(command); + if (changeSelection) { + command->setEndingSelection(endingSelection); + frame->selection()->setSelection(endingSelection); + } +} + +String dispatchBeforeTextInsertedEvent(const String& text, const VisibleSelection& selectionForInsertion, bool insertionIsForUpdatingComposition) +{ + if (insertionIsForUpdatingComposition) + return text; + + String newText = text; + if (Node* startNode = selectionForInsertion.start().containerNode()) { + if (startNode->rootEditableElement()) { + // Send BeforeTextInsertedEvent. The event handler will update text if necessary. + ExceptionCode ec = 0; + RefPtr<BeforeTextInsertedEvent> evt = BeforeTextInsertedEvent::create(text); + startNode->rootEditableElement()->dispatchEvent(evt, ec); + newText = evt->text(); + } + } + return newText; +} + +bool canAppendNewLineFeedToSelection(const VisibleSelection& selection) +{ + Node* node = selection.rootEditableElement(); + if (!node) + return false; + + RefPtr<BeforeTextInsertedEvent> event = BeforeTextInsertedEvent::create(String("\n")); + ExceptionCode ec = 0; + node->dispatchEvent(event, ec); + return event->text().length(); +} + +} diff --git a/Source/WebCore/editing/TextInsertionBaseCommand.h b/Source/WebCore/editing/TextInsertionBaseCommand.h new file mode 100644 index 000000000..9dff6dc4e --- /dev/null +++ b/Source/WebCore/editing/TextInsertionBaseCommand.h @@ -0,0 +1,71 @@ +/* + * Copyright (C) 2012 Apple Inc. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY APPLE INC. ``AS IS'' AND ANY + * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL APPLE INC. OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY + * OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#ifndef TextInsertionBaseCommand_h +#define TextInsertionBaseCommand_h + +#include "CompositeEditCommand.h" +#include <wtf/text/WTFString.h> + +namespace WebCore { + +class Document; +class VisibleSelection; + +class TextInsertionBaseCommand : public CompositeEditCommand { +public: + virtual ~TextInsertionBaseCommand() { }; + +protected: + TextInsertionBaseCommand(Document*); + static void applyTextInsertionCommand(Frame*, PassRefPtr<TextInsertionBaseCommand>, const VisibleSelection& selectionForInsertion, const VisibleSelection& endingSelection); +}; + +String dispatchBeforeTextInsertedEvent(const String& text, const VisibleSelection& selectionForInsertion, bool insertionIsForUpdatingComposition); +bool canAppendNewLineFeedToSelection(const VisibleSelection&); + +// LineOperation should define member function "opeartor (size_t lineOffset, size_t lineLength, bool isLastLine)". +// lienLength doesn't include the newline character. So the value of lineLength could be 0. +template <class LineOperation> +void forEachLineInString(const String& string, const LineOperation& operation) +{ + unsigned offset = 0; + size_t newline; + while ((newline = string.find('\n', offset)) != notFound) { + operation(offset, newline - offset, false); + offset = newline + 1; + } + if (!offset) + operation(0, string.length(), true); + else { + unsigned length = string.length(); + if (length != offset) + operation(offset, length - offset, true); + } +} + +} + +#endif diff --git a/Source/WebCore/editing/TextIterator.cpp b/Source/WebCore/editing/TextIterator.cpp index e0bf8c3a0..85b559514 100644 --- a/Source/WebCore/editing/TextIterator.cpp +++ b/Source/WebCore/editing/TextIterator.cpp @@ -43,6 +43,7 @@ #include "TextBreakIterator.h" #include "VisiblePosition.h" #include "visible_units.h" +#include <wtf/text/CString.h> #include <wtf/unicode/CharacterNames.h> #if USE(ICU_UNICODE) && !UCONFIG_NO_COLLATION @@ -261,6 +262,8 @@ TextIterator::TextIterator() , m_handledFirstLetter(false) , m_ignoresStyleVisibility(false) , m_emitsObjectReplacementCharacters(false) + , m_stopsOnFormControls(false) + , m_shouldStop(false) { } @@ -282,6 +285,8 @@ TextIterator::TextIterator(const Range* r, TextIteratorBehavior behavior) , m_handledFirstLetter(false) , m_ignoresStyleVisibility(behavior & TextIteratorIgnoresStyleVisibility) , m_emitsObjectReplacementCharacters(behavior & TextIteratorEmitsObjectReplacementCharacters) + , m_stopsOnFormControls(behavior & TextIteratorStopsOnFormControls) + , m_shouldStop(false) { if (!r) return; @@ -341,6 +346,9 @@ TextIterator::~TextIterator() void TextIterator::advance() { + if (m_shouldStop) + return; + // reset the run information m_positionNode = 0; m_textLength = 0; @@ -373,6 +381,9 @@ void TextIterator::advance() } while (m_node && m_node != m_pastEndNode) { + if (!m_shouldStop && m_stopsOnFormControls && HTMLFormControlElement::enclosingFormControlElement(m_node)) + m_shouldStop = true; + // if the range ends at offset 0 of an element, represent the // position, but not the content, of that element e.g. if the // node is a blockflow element, emit a newline that @@ -395,7 +406,9 @@ void TextIterator::advance() else if (renderer && (renderer->isImage() || renderer->isWidget() || (renderer->node() && renderer->node()->isElementNode() && (static_cast<Element*>(renderer->node())->isFormControlElement() - || static_cast<Element*>(renderer->node())->hasTagName(legendTag))))) + || static_cast<Element*>(renderer->node())->hasTagName(legendTag) + || static_cast<Element*>(renderer->node())->hasTagName(meterTag) + || static_cast<Element*>(renderer->node())->hasTagName(progressTag))))) m_handledNode = handleReplacedElement(); else m_handledNode = handleNonTextNode(); @@ -1071,6 +1084,8 @@ SimplifiedBackwardsTextIterator::SimplifiedBackwardsTextIterator() , m_singleCharacterBuffer(0) , m_havePassedStartNode(false) , m_shouldHandleFirstLetter(false) + , m_stopsOnFormControls(false) + , m_shouldStop(false) { } @@ -1094,8 +1109,10 @@ SimplifiedBackwardsTextIterator::SimplifiedBackwardsTextIterator(const Range* r, , m_singleCharacterBuffer(0) , m_havePassedStartNode(false) , m_shouldHandleFirstLetter(false) + , m_stopsOnFormControls(behavior & TextIteratorStopsOnFormControls) + , m_shouldStop(false) { - ASSERT(m_behavior == TextIteratorDefaultBehavior); + ASSERT(m_behavior == TextIteratorDefaultBehavior || m_behavior == TextIteratorStopsOnFormControls); if (!r) return; @@ -1148,6 +1165,14 @@ void SimplifiedBackwardsTextIterator::advance() { ASSERT(m_positionNode); + if (m_shouldStop) + return; + + if (m_stopsOnFormControls && HTMLFormControlElement::enclosingFormControlElement(m_node)) { + m_shouldStop = true; + return; + } + m_positionNode = 0; m_textLength = 0; @@ -2511,9 +2536,8 @@ UChar* plainTextToMallocAllocatedBuffer(const Range* r, unsigned& bufferLength, { UChar* result = 0; - // Do this in pieces to avoid massive reallocations if there is a large amount of text. - // Use system malloc for buffers since they can consume lots of memory and current TCMalloc is unable return it back to OS. - static const unsigned cMaxSegmentSize = 1 << 16; + // The initial buffer size can be critical for performance: https://bugs.webkit.org/show_bug.cgi?id=81192 + static const unsigned cMaxSegmentSize = 1 << 15; bufferLength = 0; typedef pair<UChar*, unsigned> TextSegment; OwnPtr<Vector<TextSegment> > textSegments; diff --git a/Source/WebCore/editing/TextIterator.h b/Source/WebCore/editing/TextIterator.h index 08ff095a6..bfcfae356 100644 --- a/Source/WebCore/editing/TextIterator.h +++ b/Source/WebCore/editing/TextIterator.h @@ -43,7 +43,8 @@ enum TextIteratorBehavior { TextIteratorEmitsTextsWithoutTranscoding = 1 << 2, TextIteratorIgnoresStyleVisibility = 1 << 3, TextIteratorEmitsObjectReplacementCharacters = 1 << 4, - TextIteratorEmitsOriginalText = 1 << 5 + TextIteratorEmitsOriginalText = 1 << 5, + TextIteratorStopsOnFormControls = 1 << 6 }; // FIXME: Can't really answer this question correctly without knowing the white-space mode. @@ -89,7 +90,7 @@ public: ~TextIterator(); explicit TextIterator(const Range*, TextIteratorBehavior = TextIteratorDefaultBehavior); - bool atEnd() const { return !m_positionNode; } + bool atEnd() const { return !m_positionNode || m_shouldStop; } void advance(); int length() const { return m_textLength; } @@ -185,6 +186,10 @@ private: bool m_ignoresStyleVisibility; // Used when emitting the special 0xFFFC character is required. bool m_emitsObjectReplacementCharacters; + // Used when the iteration should stop if form controls are reached. + bool m_stopsOnFormControls; + // Used when m_stopsOnFormControls is set to determine if the iterator should keep advancing. + bool m_shouldStop; }; // Iterates through the DOM range, returning all the text, and 0-length boundaries @@ -195,7 +200,7 @@ public: SimplifiedBackwardsTextIterator(); explicit SimplifiedBackwardsTextIterator(const Range*, TextIteratorBehavior = TextIteratorDefaultBehavior); - bool atEnd() const { return !m_positionNode; } + bool atEnd() const { return !m_positionNode || m_shouldStop; } void advance(); int length() const { return m_textLength; } @@ -247,6 +252,12 @@ private: // Should handle first-letter renderer in the next call to handleTextNode. bool m_shouldHandleFirstLetter; + + // Used when the iteration should stop if form controls are reached. + bool m_stopsOnFormControls; + + // Used when m_stopsOnFormControls is set to determine if the iterator should keep advancing. + bool m_shouldStop; }; // Builds on the text iterator, adding a character position so we can walk one diff --git a/Source/WebCore/editing/TypingCommand.cpp b/Source/WebCore/editing/TypingCommand.cpp index 9ae395541..f3c42329f 100644 --- a/Source/WebCore/editing/TypingCommand.cpp +++ b/Source/WebCore/editing/TypingCommand.cpp @@ -26,7 +26,6 @@ #include "config.h" #include "TypingCommand.h" -#include "BeforeTextInsertedEvent.h" #include "BreakBlockquoteCommand.h" #include "DeleteSelectionCommand.h" #include "Document.h" @@ -48,20 +47,35 @@ namespace WebCore { using namespace HTMLNames; -static bool canAppendNewLineFeed(const VisibleSelection& selection) +class TypingCommandLineOperation { - Node* node = selection.rootEditableElement(); - if (!node) - return false; - - RefPtr<BeforeTextInsertedEvent> event = BeforeTextInsertedEvent::create(String("\n")); - ExceptionCode ec = 0; - node->dispatchEvent(event, ec); - return event->text().length(); -} +public: + TypingCommandLineOperation(TypingCommand* typingCommand, bool selectInsertedText, const String& text) + : m_typingCommand(typingCommand) + , m_selectInsertedText(selectInsertedText) + , m_text(text) + { } + + void operator()(size_t lineOffset, size_t lineLength, bool isLastLine) const + { + if (isLastLine) { + if (!lineOffset || lineLength > 0) + m_typingCommand->insertTextRunWithoutNewlines(m_text.substring(lineOffset, lineLength), m_selectInsertedText); + } else { + if (lineLength > 0) + m_typingCommand->insertTextRunWithoutNewlines(m_text.substring(lineOffset, lineLength), false); + m_typingCommand->insertParagraphSeparator(); + } + } + +private: + TypingCommand* m_typingCommand; + bool m_selectInsertedText; + const String& m_text; +}; TypingCommand::TypingCommand(Document *document, ETypingCommand commandType, const String &textToInsert, Options options, TextGranularity granularity, TextCompositionType compositionType) - : CompositeEditCommand(document) + : TextInsertionBaseCommand(document) , m_commandType(commandType) , m_textToInsert(textToInsert) , m_openForMoreTyping(true) @@ -161,17 +175,8 @@ void TypingCommand::insertText(Document* document, const String& text, const Vis ASSERT(frame); VisibleSelection currentSelection = frame->selection()->selection(); - bool changeSelection = currentSelection != selectionForInsertion; - String newText = text; - if (Node* startNode = selectionForInsertion.start().containerNode()) { - if (startNode->rootEditableElement() && compositionType != TextCompositionUpdate) { - // Send BeforeTextInsertedEvent. The event handler will update text if necessary. - ExceptionCode ec = 0; - RefPtr<BeforeTextInsertedEvent> evt = BeforeTextInsertedEvent::create(text); - startNode->rootEditableElement()->dispatchEvent(evt, ec); - newText = evt->text(); - } - } + + String newText = dispatchBeforeTextInsertedEvent(text, selectionForInsertion, compositionType == TextCompositionUpdate); // Set the starting and ending selection appropriately if we are using a selection // that is different from the current selection. In the future, we should change EditCommand @@ -190,15 +195,7 @@ void TypingCommand::insertText(Document* document, const String& text, const Vis } RefPtr<TypingCommand> cmd = TypingCommand::create(document, InsertText, newText, options, compositionType); - if (changeSelection) { - cmd->setStartingSelection(selectionForInsertion); - cmd->setEndingSelection(selectionForInsertion); - } - applyCommand(cmd); - if (changeSelection) { - cmd->setEndingSelection(currentSelection); - frame->selection()->setSelection(currentSelection); - } + applyTextInsertionCommand(frame.get(), cmd, selectionForInsertion, currentSelection); } void TypingCommand::insertLineBreak(Document *document, Options options) @@ -327,7 +324,7 @@ void TypingCommand::markMisspellingsAfterTyping(ETypingCommand commandType) strippedPreviousWord = plainText(range.get()).stripWhiteSpace(); frame->editor()->markMisspellingsAfterTypingToWord(p1, endingSelection(), !strippedPreviousWord.isEmpty()); } else if (commandType == TypingCommand::InsertText) - frame->editor()->startCorrectionPanelTimer(); + frame->editor()->startAlternativeTextUITimer(); } } @@ -358,21 +355,8 @@ void TypingCommand::insertText(const String &text, bool selectInsertedText) // an existing selection; at the moment they can either put the caret after what's inserted or // select what's inserted, but there's no way to "extend selection" to include both an old selection // that ends just before where we want to insert text and the newly inserted text. - unsigned offset = 0; - size_t newline; - while ((newline = text.find('\n', offset)) != notFound) { - if (newline != offset) - insertTextRunWithoutNewlines(text.substring(offset, newline - offset), false); - insertParagraphSeparator(); - offset = newline + 1; - } - if (!offset) - insertTextRunWithoutNewlines(text, selectInsertedText); - else { - unsigned length = text.length(); - if (length != offset) - insertTextRunWithoutNewlines(text.substring(offset, length - offset), selectInsertedText); - } + TypingCommandLineOperation operation(this, selectInsertedText, text); + forEachLineInString(text, operation); } void TypingCommand::insertTextRunWithoutNewlines(const String &text, bool selectInsertedText) @@ -387,7 +371,7 @@ void TypingCommand::insertTextRunWithoutNewlines(const String &text, bool select void TypingCommand::insertLineBreak() { - if (!canAppendNewLineFeed(endingSelection())) + if (!canAppendNewLineFeedToSelection(endingSelection())) return; applyCommandToComposite(InsertLineBreakCommand::create(document())); @@ -396,7 +380,7 @@ void TypingCommand::insertLineBreak() void TypingCommand::insertParagraphSeparator() { - if (!canAppendNewLineFeed(endingSelection())) + if (!canAppendNewLineFeedToSelection(endingSelection())) return; applyCommandToComposite(InsertParagraphSeparatorCommand::create(document())); diff --git a/Source/WebCore/editing/TypingCommand.h b/Source/WebCore/editing/TypingCommand.h index 1f8c181df..950eec80a 100644 --- a/Source/WebCore/editing/TypingCommand.h +++ b/Source/WebCore/editing/TypingCommand.h @@ -26,11 +26,11 @@ #ifndef TypingCommand_h #define TypingCommand_h -#include "CompositeEditCommand.h" +#include "TextInsertionBaseCommand.h" namespace WebCore { -class TypingCommand : public CompositeEditCommand { +class TypingCommand : public TextInsertionBaseCommand { public: enum ETypingCommand { DeleteSelection, diff --git a/Source/WebCore/editing/VisiblePosition.cpp b/Source/WebCore/editing/VisiblePosition.cpp index 81168d7b7..05666d34c 100644 --- a/Source/WebCore/editing/VisiblePosition.cpp +++ b/Source/WebCore/editing/VisiblePosition.cpp @@ -595,7 +595,7 @@ UChar32 VisiblePosition::characterAfter() const return ch; } -IntRect VisiblePosition::localCaretRect(RenderObject*& renderer) const +LayoutRect VisiblePosition::localCaretRect(RenderObject*& renderer) const { if (m_deepPosition.isNull()) { renderer = 0; @@ -605,7 +605,7 @@ IntRect VisiblePosition::localCaretRect(RenderObject*& renderer) const renderer = node->renderer(); if (!renderer) - return IntRect(); + return LayoutRect(); InlineBox* inlineBox; int caretOffset; @@ -620,7 +620,7 @@ IntRect VisiblePosition::localCaretRect(RenderObject*& renderer) const IntRect VisiblePosition::absoluteCaretBounds() const { RenderObject* renderer; - IntRect localRect = localCaretRect(renderer); + LayoutRect localRect = localCaretRect(renderer); if (localRect.isEmpty() || !renderer) return IntRect(); @@ -630,7 +630,7 @@ IntRect VisiblePosition::absoluteCaretBounds() const int VisiblePosition::lineDirectionPointForBlockDirectionNavigation() const { RenderObject* renderer; - IntRect localRect = localCaretRect(renderer); + LayoutRect localRect = localCaretRect(renderer); if (localRect.isEmpty() || !renderer) return 0; diff --git a/Source/WebCore/editing/VisibleSelection.cpp b/Source/WebCore/editing/VisibleSelection.cpp index e64739d76..09bef9026 100644 --- a/Source/WebCore/editing/VisibleSelection.cpp +++ b/Source/WebCore/editing/VisibleSelection.cpp @@ -30,6 +30,7 @@ #include "Element.h" #include "htmlediting.h" #include "TextIterator.h" +#include "TreeScopeAdjuster.h" #include "VisiblePosition.h" #include "visible_units.h" #include "Range.h" @@ -461,22 +462,18 @@ void VisibleSelection::adjustSelectionToAvoidCrossingShadowBoundaries() if (m_base.isNull() || m_start.isNull() || m_end.isNull()) return; - Node* startRootNode = m_start.anchorNode()->nonBoundaryShadowTreeRootNode(); - Node* endRootNode = m_end.anchorNode()->nonBoundaryShadowTreeRootNode(); - - if (!startRootNode && !endRootNode) - return; - - if (startRootNode == endRootNode) + if (m_start.anchorNode()->treeScope() == m_end.anchorNode()->treeScope()) return; if (m_baseIsFirst) { - m_extent = startRootNode ? lastPositionInNode(startRootNode) : positionBeforeNode(endRootNode->shadowAncestorNode()); + m_extent = TreeScopeAdjuster(m_start.anchorNode()->treeScope()).adjustPositionBefore(m_end); m_end = m_extent; } else { - m_extent = endRootNode ? firstPositionInNode(endRootNode) : positionAfterNode(startRootNode->shadowAncestorNode()); + m_extent = TreeScopeAdjuster(m_end.anchorNode()->treeScope()).adjustPositionAfter(m_start); m_start = m_extent; } + + ASSERT(m_start.anchorNode()->treeScope() == m_end.anchorNode()->treeScope()); } void VisibleSelection::adjustSelectionToAvoidCrossingEditingBoundaries() diff --git a/Source/WebCore/editing/gtk/FrameSelectionGtk.cpp b/Source/WebCore/editing/gtk/FrameSelectionGtk.cpp index e5b2b9bd5..d792cd40c 100644 --- a/Source/WebCore/editing/gtk/FrameSelectionGtk.cpp +++ b/Source/WebCore/editing/gtk/FrameSelectionGtk.cpp @@ -22,11 +22,12 @@ #include "AXObjectCache.h" #include "Frame.h" -#include "RefPtr.h" #include "WebKitAccessibleWrapperAtk.h" #include <gtk/gtk.h> +#include <wtf/RefPtr.h> + namespace WebCore { static void emitTextSelectionChange(AccessibilityObject* object, VisibleSelection selection, int offset) diff --git a/Source/WebCore/editing/mac/EditorMac.mm b/Source/WebCore/editing/mac/EditorMac.mm index 85133dd4f..2c63a24ee 100644 --- a/Source/WebCore/editing/mac/EditorMac.mm +++ b/Source/WebCore/editing/mac/EditorMac.mm @@ -30,19 +30,25 @@ #import "ClipboardMac.h" #import "CachedResourceLoader.h" #import "DocumentFragment.h" +#import "DOMRangeInternal.h" #import "EditingText.h" #import "Editor.h" #import "EditorClient.h" #import "Frame.h" #import "FrameView.h" +#import "HTMLConverter.h" #import "HTMLNames.h" +#import "LegacyWebArchive.h" #import "Pasteboard.h" #import "PasteboardStrategy.h" #import "PlatformStrategies.h" +#import "Range.h" #import "RenderBlock.h" #import "RuntimeApplicationChecks.h" #import "Sound.h" +#import "TypingCommand.h" #import "htmlediting.h" +#import "WebNSAttributedStringExtras.h" namespace WebCore { @@ -51,7 +57,7 @@ using namespace HTMLNames; PassRefPtr<Clipboard> Editor::newGeneralClipboard(ClipboardAccessPolicy policy, Frame* frame) { return ClipboardMac::create(Clipboard::CopyAndPaste, - policy == ClipboardWritable ? platformStrategies()->pasteboardStrategy()->uniqueName() : String(NSGeneralPboard), policy, frame); + policy == ClipboardWritable ? platformStrategies()->pasteboardStrategy()->uniqueName() : String(NSGeneralPboard), policy, ClipboardMac::CopyAndPasteGeneric, frame); } void Editor::showFontPanel() @@ -97,6 +103,14 @@ void Editor::pasteWithPasteboard(Pasteboard* pasteboard, bool allowPlainText) m_frame->editor()->client()->setInsertionPasteboard(String()); } +bool Editor::insertParagraphSeparatorInQuotedContent() +{ + // FIXME: Why is this missing calls to canEdit, canEditRichly, etc... + TypingCommand::insertParagraphSeparatorInQuotedContent(m_frame->document()); + revealSelectionAfterEditingOperation(); + return true; +} + static RenderStyle* styleForSelectionStart(Frame* frame, Node *&nodeToRemove) { nodeToRemove = 0; @@ -291,7 +305,7 @@ void Editor::takeFindStringFromSelection() void Editor::writeSelectionToPasteboard(const String& pasteboardName, const Vector<String>& pasteboardTypes) { Pasteboard pasteboard(pasteboardName); - pasteboard.writeSelectionForTypes(pasteboardTypes, selectedRange().get(), true, m_frame); + pasteboard.writeSelectionForTypes(pasteboardTypes, true, m_frame); } void Editor::readSelectionFromPasteboard(const String& pasteboardName) @@ -303,4 +317,14 @@ void Editor::readSelectionFromPasteboard(const String& pasteboardName) pasteAsPlainTextWithPasteboard(&pasteboard); } +String Editor::stringSelectionForPasteboard() +{ + return Pasteboard::getStringSelection(m_frame); +} + +PassRefPtr<SharedBuffer> Editor::dataSelectionForPasteboard(const String& pasteboardType) +{ + return Pasteboard::getDataSelection(m_frame, pasteboardType); +} + } // namespace WebCore diff --git a/Source/WebCore/editing/mac/FrameSelectionMac.mm b/Source/WebCore/editing/mac/FrameSelectionMac.mm index 791cac578..a1a272b46 100644 --- a/Source/WebCore/editing/mac/FrameSelectionMac.mm +++ b/Source/WebCore/editing/mac/FrameSelectionMac.mm @@ -63,8 +63,8 @@ void FrameSelection::notifyAccessibilityForSelectionChange() if (!frameView) return; - LayoutRect selectionRect = absoluteCaretBounds(); - LayoutRect viewRect = renderView->viewRect(); + IntRect selectionRect = absoluteCaretBounds(); + IntRect viewRect = pixelSnappedIntRect(renderView->viewRect()); selectionRect = frameView->contentsToScreen(selectionRect); viewRect = frameView->contentsToScreen(viewRect); diff --git a/Source/WebCore/editing/markup.cpp b/Source/WebCore/editing/markup.cpp index 05cde4a63..bb49b372f 100644 --- a/Source/WebCore/editing/markup.cpp +++ b/Source/WebCore/editing/markup.cpp @@ -36,7 +36,6 @@ #include "CSSRule.h" #include "CSSRuleList.h" #include "CSSStyleRule.h" -#include "CSSStyleSelector.h" #include "CSSValue.h" #include "CSSValueKeywords.h" #include "ChildListMutationScope.h" @@ -55,6 +54,7 @@ #include "Range.h" #include "RenderObject.h" #include "StylePropertySet.h" +#include "StyleResolver.h" #include "TextIterator.h" #include "VisibleSelection.h" #include "XMLNSNames.h" @@ -70,7 +70,7 @@ namespace WebCore { using namespace HTMLNames; -static bool propertyMissingOrEqualToNone(StylePropertySet*, int propertyID); +static bool propertyMissingOrEqualToNone(StylePropertySet*, CSSPropertyID); class AttributeChange { public: @@ -466,7 +466,7 @@ static inline Node* ancestorToRetainStructureAndAppearanceWithNoRenderer(Node* c return ancestorToRetainStructureAndAppearanceForBlock(commonAncestorBlock); } -static bool propertyMissingOrEqualToNone(StylePropertySet* style, int propertyID) +static bool propertyMissingOrEqualToNone(StylePropertySet* style, CSSPropertyID propertyID) { if (!style) return false; @@ -859,7 +859,7 @@ PassRefPtr<DocumentFragment> createFragmentFromText(Range* context, const String if (renderer && renderer->style()->preserveNewline()) { fragment->appendChild(document->createTextNode(string), ec); ASSERT(!ec); - if (string.endsWith("\n")) { + if (string.endsWith('\n')) { RefPtr<Element> element = createBreakElement(document); element->setAttribute(classAttr, AppleInterchangeNewline); fragment->appendChild(element.release(), ec); diff --git a/Source/WebCore/editing/visible_units.cpp b/Source/WebCore/editing/visible_units.cpp index db8925946..f225d0b49 100644 --- a/Source/WebCore/editing/visible_units.cpp +++ b/Source/WebCore/editing/visible_units.cpp @@ -32,7 +32,6 @@ #include "InlineTextBox.h" #include "Position.h" #include "RenderBlock.h" -#include "RenderLayer.h" #include "RenderObject.h" #include "RenderedPosition.h" #include "Text.h" @@ -49,6 +48,423 @@ namespace WebCore { using namespace HTMLNames; using namespace WTF::Unicode; +static Node* previousLeafWithSameEditability(Node* node, EditableType editableType) +{ + bool editable = node->rendererIsEditable(editableType); + node = node->previousLeafNode(); + while (node) { + if (editable == node->rendererIsEditable(editableType)) + return node; + node = node->previousLeafNode(); + } + return 0; +} + +static Node* enclosingNodeWithNonInlineRenderer(Node* node) +{ + for (; node; node = node->parentNode()) { + if (node->renderer() && !node->renderer()->isInline()) + return node; + } + return 0; +} + +static Node* nextLeafWithSameEditability(Node* node, int offset) +{ + bool editable = node->rendererIsEditable(); + ASSERT(offset >= 0); + Node* child = node->childNode(offset); + node = child ? child->nextLeafNode() : node->lastDescendant()->nextLeafNode(); + while (node) { + if (editable == node->rendererIsEditable()) + return node; + node = node->nextLeafNode(); + } + return 0; +} + +static Node* nextLeafWithSameEditability(Node* node, EditableType editableType = ContentIsEditable) +{ + if (!node) + return 0; + + bool editable = node->rendererIsEditable(editableType); + node = node->nextLeafNode(); + while (node) { + if (editable == node->rendererIsEditable(editableType)) + return node; + node = node->nextLeafNode(); + } + return 0; +} + +// FIXME: consolidate with code in previousLinePosition. +static const RootInlineBox* previousRootInlineBox(const InlineBox* box, const VisiblePosition& visiblePosition) +{ + Node* highestRoot = highestEditableRoot(visiblePosition.deepEquivalent(), ContentIsEditable); + + if (!box->renderer() || !box->renderer()->node()) + return 0; + + Node* node = box->renderer()->node(); + Node* enclosingBlockNode = enclosingNodeWithNonInlineRenderer(node); + Node* previousNode = previousLeafWithSameEditability(node, ContentIsEditable); + + while (previousNode && enclosingBlockNode == enclosingNodeWithNonInlineRenderer(previousNode)) + previousNode = previousLeafWithSameEditability(previousNode, ContentIsEditable); + + while (previousNode && !previousNode->isShadowRoot()) { + if (highestEditableRoot(firstPositionInOrBeforeNode(previousNode), ContentIsEditable) != highestRoot) + break; + + Position pos = previousNode->hasTagName(brTag) ? positionBeforeNode(previousNode) : + createLegacyEditingPosition(previousNode, caretMaxOffset(previousNode)); + + if (pos.isCandidate()) { + RenderedPosition renderedPos(pos, DOWNSTREAM); + RootInlineBox* root = renderedPos.rootBox(); + if (root) + return root; + } + + previousNode = previousLeafWithSameEditability(previousNode, ContentIsEditable); + } + return 0; +} + +static const RootInlineBox* nextRootInlineBox(const InlineBox* box, const VisiblePosition& visiblePosition) +{ + Node* highestRoot = highestEditableRoot(visiblePosition.deepEquivalent(), ContentIsEditable); + + if (!box->renderer() || !box->renderer()->node()) + return 0; + + Node* node = box->renderer()->node(); + Node* enclosingBlockNode = enclosingNodeWithNonInlineRenderer(node); + Node* nextNode = nextLeafWithSameEditability(node, ContentIsEditable); + while (nextNode && enclosingBlockNode == enclosingNodeWithNonInlineRenderer(nextNode)) + nextNode = nextLeafWithSameEditability(nextNode, ContentIsEditable); + + while (nextNode && !nextNode->isShadowRoot()) { + if (highestEditableRoot(firstPositionInOrBeforeNode(nextNode), ContentIsEditable) != highestRoot) + break; + + Position pos; + pos = createLegacyEditingPosition(nextNode, caretMinOffset(nextNode)); + + if (pos.isCandidate()) { + RenderedPosition renderedPos(pos, DOWNSTREAM); + RootInlineBox* root = renderedPos.rootBox(); + if (root) + return root; + } + + nextNode = nextLeafWithSameEditability(nextNode, ContentIsEditable); + } + return 0; +} + +class CachedLogicallyOrderedLeafBoxes { +public: + CachedLogicallyOrderedLeafBoxes(); + + const InlineTextBox* previousTextBox(const RootInlineBox*, const InlineTextBox*); + const InlineTextBox* nextTextBox(const RootInlineBox*, const InlineTextBox*); + + size_t size() const { return m_leafBoxes.size(); } + const InlineBox* firstBox() const { return m_leafBoxes[0]; } + +private: + const Vector<InlineBox*>& collectBoxes(const RootInlineBox*); + int boxIndexInLeaves(const InlineTextBox*); + + const RootInlineBox* m_rootInlineBox; + Vector<InlineBox*> m_leafBoxes; +}; + +CachedLogicallyOrderedLeafBoxes::CachedLogicallyOrderedLeafBoxes() : m_rootInlineBox(0) { }; + +const InlineTextBox* CachedLogicallyOrderedLeafBoxes::previousTextBox(const RootInlineBox* root, const InlineTextBox* box) +{ + if (!root) + return 0; + + collectBoxes(root); + + // If box is null, root is box's previous RootInlineBox, and previousBox is the last logical box in root. + int boxIndex = m_leafBoxes.size() - 1; + if (box) + boxIndex = boxIndexInLeaves(box) - 1; + + for (int i = boxIndex; i >= 0; --i) { + if (m_leafBoxes[i]->isInlineTextBox()) + return toInlineTextBox(m_leafBoxes[i]); + } + + return 0; +} + +const InlineTextBox* CachedLogicallyOrderedLeafBoxes::nextTextBox(const RootInlineBox* root, const InlineTextBox* box) +{ + if (!root) + return 0; + + collectBoxes(root); + + // If box is null, root is box's next RootInlineBox, and nextBox is the first logical box in root. + // Otherwise, root is box's RootInlineBox, and nextBox is the next logical box in the same line. + size_t nextBoxIndex = 0; + if (box) + nextBoxIndex = boxIndexInLeaves(box) + 1; + + for (size_t i = nextBoxIndex; i < m_leafBoxes.size(); ++i) { + if (m_leafBoxes[i]->isInlineTextBox()) + return toInlineTextBox(m_leafBoxes[i]); + } + + return 0; +} + +const Vector<InlineBox*>& CachedLogicallyOrderedLeafBoxes::collectBoxes(const RootInlineBox* root) +{ + if (m_rootInlineBox != root) { + m_rootInlineBox = root; + m_leafBoxes.clear(); + root->collectLeafBoxesInLogicalOrder(m_leafBoxes); + } + return m_leafBoxes; +} + +int CachedLogicallyOrderedLeafBoxes::boxIndexInLeaves(const InlineTextBox* box) +{ + for (size_t i = 0; i < m_leafBoxes.size(); ++i) { + if (box == m_leafBoxes[i]) + return i; + } + return 0; +} + +static const InlineTextBox* logicallyPreviousBox(const VisiblePosition& visiblePosition, const InlineTextBox* textBox, + bool& previousBoxInDifferentBlock, CachedLogicallyOrderedLeafBoxes& leafBoxes) +{ + const InlineBox* startBox = textBox; + + const InlineTextBox* previousBox = leafBoxes.previousTextBox(startBox->root(), textBox); + if (previousBox) + return previousBox; + + previousBox = leafBoxes.previousTextBox(startBox->root()->prevRootBox(), 0); + if (previousBox) + return previousBox; + + while (1) { + const RootInlineBox* previousRoot = previousRootInlineBox(startBox, visiblePosition); + if (!previousRoot) + break; + + previousBox = leafBoxes.previousTextBox(previousRoot, 0); + if (previousBox) { + previousBoxInDifferentBlock = true; + return previousBox; + } + + if (!leafBoxes.size()) + break; + startBox = leafBoxes.firstBox(); + } + return 0; +} + + +static const InlineTextBox* logicallyNextBox(const VisiblePosition& visiblePosition, const InlineTextBox* textBox, + bool& nextBoxInDifferentBlock, CachedLogicallyOrderedLeafBoxes& leafBoxes) +{ + const InlineBox* startBox = textBox; + + const InlineTextBox* nextBox = leafBoxes.nextTextBox(startBox->root(), textBox); + if (nextBox) + return nextBox; + + nextBox = leafBoxes.nextTextBox(startBox->root()->nextRootBox(), 0); + if (nextBox) + return nextBox; + + while (1) { + const RootInlineBox* nextRoot = nextRootInlineBox(startBox, visiblePosition); + if (!nextRoot) + break; + + nextBox = leafBoxes.nextTextBox(nextRoot, 0); + if (nextBox) { + nextBoxInDifferentBlock = true; + return nextBox; + } + + if (!leafBoxes.size()) + break; + startBox = leafBoxes.firstBox(); + } + return 0; +} + +static TextBreakIterator* wordBreakIteratorForMinOffsetBoundary(const VisiblePosition& visiblePosition, const InlineTextBox* textBox, + int& previousBoxLength, bool& previousBoxInDifferentBlock, Vector<UChar, 1024>& string, CachedLogicallyOrderedLeafBoxes& leafBoxes) +{ + previousBoxInDifferentBlock = false; + + // FIXME: Handle the case when we don't have an inline text box. + const InlineTextBox* previousBox = logicallyPreviousBox(visiblePosition, textBox, previousBoxInDifferentBlock, leafBoxes); + + int len = 0; + string.clear(); + if (previousBox) { + previousBoxLength = previousBox->len(); + string.append(previousBox->textRenderer()->text()->characters() + previousBox->start(), previousBoxLength); + len += previousBoxLength; + } + string.append(textBox->textRenderer()->text()->characters() + textBox->start(), textBox->len()); + len += textBox->len(); + + return wordBreakIterator(string.data(), len); +} + +static TextBreakIterator* wordBreakIteratorForMaxOffsetBoundary(const VisiblePosition& visiblePosition, const InlineTextBox* textBox, + bool& nextBoxInDifferentBlock, Vector<UChar, 1024>& string, CachedLogicallyOrderedLeafBoxes& leafBoxes) +{ + nextBoxInDifferentBlock = false; + + // FIXME: Handle the case when we don't have an inline text box. + const InlineTextBox* nextBox = logicallyNextBox(visiblePosition, textBox, nextBoxInDifferentBlock, leafBoxes); + + int len = 0; + string.clear(); + string.append(textBox->textRenderer()->text()->characters() + textBox->start(), textBox->len()); + len += textBox->len(); + if (nextBox) { + string.append(nextBox->textRenderer()->text()->characters() + nextBox->start(), nextBox->len()); + len += nextBox->len(); + } + + return wordBreakIterator(string.data(), len); +} + +static bool isLogicalStartOfWord(TextBreakIterator* iter, int position, bool hardLineBreak) +{ + bool boundary = hardLineBreak ? true : isTextBreak(iter, position); + if (!boundary) + return false; + + textBreakFollowing(iter, position); + // isWordTextBreak returns true after moving across a word and false after moving across a punctuation/space. + return isWordTextBreak(iter); +} + +static bool islogicalEndOfWord(TextBreakIterator* iter, int position, bool hardLineBreak) +{ + bool boundary = isTextBreak(iter, position); + return (hardLineBreak || boundary) && isWordTextBreak(iter); +} + +enum CursorMovementDirection { MoveLeft, MoveRight }; + +static VisiblePosition visualWordPosition(const VisiblePosition& visiblePosition, CursorMovementDirection direction, + bool skipsSpaceWhenMovingRight) +{ + if (visiblePosition.isNull()) + return VisiblePosition(); + + TextDirection blockDirection = directionOfEnclosingBlock(visiblePosition.deepEquivalent()); + InlineBox* previouslyVisitedBox = 0; + VisiblePosition current = visiblePosition; + TextBreakIterator* iter = 0; + + CachedLogicallyOrderedLeafBoxes leafBoxes; + Vector<UChar, 1024> string; + + while (1) { + VisiblePosition adjacentCharacterPosition = direction == MoveRight ? current.right(true) : current.left(true); + if (adjacentCharacterPosition == current || adjacentCharacterPosition.isNull()) + return VisiblePosition(); + + InlineBox* box; + int offsetInBox; + adjacentCharacterPosition.deepEquivalent().getInlineBoxAndOffset(UPSTREAM, box, offsetInBox); + + if (!box) + break; + if (!box->isInlineTextBox()) { + current = adjacentCharacterPosition; + continue; + } + + InlineTextBox* textBox = toInlineTextBox(box); + int previousBoxLength = 0; + bool previousBoxInDifferentBlock = false; + bool nextBoxInDifferentBlock = false; + bool movingIntoNewBox = previouslyVisitedBox != box; + + if (offsetInBox == box->caretMinOffset()) + iter = wordBreakIteratorForMinOffsetBoundary(visiblePosition, textBox, previousBoxLength, previousBoxInDifferentBlock, string, leafBoxes); + else if (offsetInBox == box->caretMaxOffset()) + iter = wordBreakIteratorForMaxOffsetBoundary(visiblePosition, textBox, nextBoxInDifferentBlock, string, leafBoxes); + else if (movingIntoNewBox) { + iter = wordBreakIterator(textBox->textRenderer()->text()->characters() + textBox->start(), textBox->len()); + previouslyVisitedBox = box; + } + + if (!iter) + break; + + textBreakFirst(iter); + int offsetInIterator = offsetInBox - textBox->start() + previousBoxLength; + + bool isWordBreak; + bool boxHasSameDirectionalityAsBlock = box->direction() == blockDirection; + bool movingBackward = (direction == MoveLeft && box->direction() == LTR) || (direction == MoveRight && box->direction() == RTL); + if ((skipsSpaceWhenMovingRight && boxHasSameDirectionalityAsBlock) + || (!skipsSpaceWhenMovingRight && movingBackward)) { + bool logicalStartInRenderer = offsetInBox == static_cast<int>(textBox->start()) && previousBoxInDifferentBlock; + isWordBreak = isLogicalStartOfWord(iter, offsetInIterator, logicalStartInRenderer); + } else { + bool logicalEndInRenderer = offsetInBox == static_cast<int>(textBox->start() + textBox->len()) && nextBoxInDifferentBlock; + isWordBreak = islogicalEndOfWord(iter, offsetInIterator, logicalEndInRenderer); + } + + if (isWordBreak) + return adjacentCharacterPosition; + + current = adjacentCharacterPosition; + } + return VisiblePosition(); +} + +VisiblePosition leftWordPosition(const VisiblePosition& visiblePosition, bool skipsSpaceWhenMovingRight) +{ + VisiblePosition leftWordBreak = visualWordPosition(visiblePosition, MoveLeft, skipsSpaceWhenMovingRight); + leftWordBreak = visiblePosition.honorEditingBoundaryAtOrBefore(leftWordBreak); + + // FIXME: How should we handle a non-editable position? + if (leftWordBreak.isNull() && isEditablePosition(visiblePosition.deepEquivalent())) { + TextDirection blockDirection = directionOfEnclosingBlock(visiblePosition.deepEquivalent()); + leftWordBreak = blockDirection == LTR ? startOfEditableContent(visiblePosition) : endOfEditableContent(visiblePosition); + } + return leftWordBreak; +} + +VisiblePosition rightWordPosition(const VisiblePosition& visiblePosition, bool skipsSpaceWhenMovingRight) +{ + VisiblePosition rightWordBreak = visualWordPosition(visiblePosition, MoveRight, skipsSpaceWhenMovingRight); + rightWordBreak = visiblePosition.honorEditingBoundaryAtOrBefore(rightWordBreak); + + // FIXME: How should we handle a non-editable position? + if (rightWordBreak.isNull() && isEditablePosition(visiblePosition.deepEquivalent())) { + TextDirection blockDirection = directionOfEnclosingBlock(visiblePosition.deepEquivalent()); + rightWordBreak = blockDirection == LTR ? endOfEditableContent(visiblePosition) : startOfEditableContent(visiblePosition); + } + return rightWordBreak; +} + + enum BoundarySearchContextAvailability { DontHaveMoreContext, MayHaveMoreContext }; typedef unsigned (*BoundarySearchFunction)(const UChar*, unsigned length, unsigned offset, BoundarySearchContextAvailability, bool& needMoreContext); @@ -517,28 +933,6 @@ bool isEndOfLine(const VisiblePosition &p) return p.isNotNull() && p == endOfLine(p); } -// The first leaf before node that has the same editability as node. -static Node* previousLeafWithSameEditability(Node* node, EditableType editableType) -{ - bool editable = node->rendererIsEditable(editableType); - Node* n = node->previousLeafNode(); - while (n) { - if (editable == n->rendererIsEditable(editableType)) - return n; - n = n->previousLeafNode(); - } - return 0; -} - -static Node* enclosingNodeWithNonInlineRenderer(Node* n) -{ - for (Node* p = n; p; p = p->parentNode()) { - if (p->renderer() && !p->renderer()->isInline()) - return p; - } - return 0; -} - static inline IntPoint absoluteLineDirectionPointToLocalPointInBlock(RootInlineBox* root, int lineDirectionPoint) { ASSERT(root); @@ -625,34 +1019,6 @@ VisiblePosition previousLinePosition(const VisiblePosition &visiblePosition, int return VisiblePosition(firstPositionInNode(rootElement), DOWNSTREAM); } -static Node* nextLeafWithSameEditability(Node* node, int offset) -{ - bool editable = node->rendererIsEditable(); - ASSERT(offset >= 0); - Node* child = node->childNode(offset); - Node* n = child ? child->nextLeafNode() : node->lastDescendant()->nextLeafNode(); - while (n) { - if (editable == n->rendererIsEditable()) - return n; - n = n->nextLeafNode(); - } - return 0; -} - -static Node* nextLeafWithSameEditability(Node* node, EditableType editableType = ContentIsEditable) -{ - if (!node) - return 0; - - bool editable = node->rendererIsEditable(editableType); - Node* n = node->nextLeafNode(); - while (n) { - if (editable == n->rendererIsEditable(editableType)) - return n; - n = n->nextLeafNode(); - } - return 0; -} VisiblePosition nextLinePosition(const VisiblePosition &visiblePosition, int lineDirectionPoint, EditableType editableType) { @@ -1099,543 +1465,4 @@ VisiblePosition rightBoundaryOfLine(const VisiblePosition& c, TextDirection dire return direction == LTR ? logicalEndOfLine(c) : logicalStartOfLine(c); } -static const int invalidOffset = -1; -static const int offsetNotFound = -1; - -static bool positionIsInBox(const VisiblePosition& wordBreak, const InlineBox* box, int& offsetOfWordBreak) -{ - if (wordBreak.isNull()) - return false; - - InlineBox* boxOfWordBreak; - wordBreak.getInlineBoxAndOffset(boxOfWordBreak, offsetOfWordBreak); - return box == boxOfWordBreak; -} - -static VisiblePosition previousWordBreakInBoxInsideBlockWithSameDirectionality(const InlineBox* box, const VisiblePosition& previousWordBreak, int& offsetOfWordBreak) -{ - // In a LTR block, the word break should be on the left boundary of a word. - // In a RTL block, the word break should be on the right boundary of a word. - // Because nextWordPosition() returns the word break on the right boundary of the word for LTR text, - // we need to use previousWordPosition() to traverse words within the inline boxes from right to left - // to find the previous word break (i.e. the first word break on the left). The same applies to RTL text. - - bool hasSeenWordBreakInThisBox = previousWordBreak.isNotNull(); - - VisiblePosition wordBreak; - - if (hasSeenWordBreakInThisBox) - wordBreak = previousWordBreak; - else { - wordBreak = createLegacyEditingPosition(box->renderer()->node(), box->caretMaxOffset()); - - // Return the rightmost word boundary of LTR box or leftmost word boundary of RTL box if - // it is not in the previously visited boxes. For example, given a logical text - // "abc def hij opq", there are 2 boxes: the "abc def " (starts at 0 and length is 8) - // and the "hij opq" (starts at 12 and length is 7). The word breaks are - // "abc |def | hij |opq". We normally catch the word break between "def" and "hij" when - // we visit the box that contains "hij opq", but this word break doesn't exist in the box - // that contains "hij opq" when there are multiple spaces. So we detect it when we're - // traversing the box that contains "abc def " instead. - - if ((box->isLeftToRightDirection() && box->nextLeafChild()) - || (!box->isLeftToRightDirection() && box->prevLeafChild())) { - - VisiblePosition positionAfterWord = nextBoundary(wordBreak, nextWordPositionBoundary); - if (positionAfterWord.isNotNull()) { - VisiblePosition positionBeforeWord = previousBoundary(positionAfterWord, previousWordPositionBoundary); - - if (positionIsInBox(positionBeforeWord, box, offsetOfWordBreak)) - return positionBeforeWord; - } - } - } - - wordBreak = previousBoundary(wordBreak, previousWordPositionBoundary); - if (previousWordBreak == wordBreak) - return VisiblePosition(); - - return positionIsInBox(wordBreak, box, offsetOfWordBreak) ? wordBreak : VisiblePosition(); -} - -static VisiblePosition leftmostPositionInRTLBoxInLTRBlock(const InlineBox* box) -{ - // FIXME: Probably need to take care of bidi level too. - Node* node = box->renderer()->node(); - InlineBox* previousLeaf = box->prevLeafChild(); - InlineBox* nextLeaf = box->nextLeafChild(); - - if (previousLeaf && !previousLeaf->isLeftToRightDirection()) - return createLegacyEditingPosition(node, box->caretMaxOffset()); - - if (nextLeaf && !nextLeaf->isLeftToRightDirection()) { - if (previousLeaf) - return createLegacyEditingPosition(previousLeaf->renderer()->node(), previousLeaf->caretMaxOffset()); - - InlineBox* lastRTLLeaf; - do { - lastRTLLeaf = nextLeaf; - nextLeaf = nextLeaf->nextLeafChild(); - } while (nextLeaf && !nextLeaf->isLeftToRightDirection()); - return createLegacyEditingPosition(lastRTLLeaf->renderer()->node(), lastRTLLeaf->caretMinOffset()); - } - - return createLegacyEditingPosition(node, box->caretMinOffset()); -} - -static VisiblePosition rightmostPositionInLTRBoxInRTLBlock(const InlineBox* box) -{ - // FIXME: Probably need to take care of bidi level too. - Node* node = box->renderer()->node(); - InlineBox* previousLeaf = box->prevLeafChild(); - InlineBox* nextLeaf = box->nextLeafChild(); - - if (nextLeaf && nextLeaf->isLeftToRightDirection()) - return createLegacyEditingPosition(node, box->caretMaxOffset()); - - if (previousLeaf && previousLeaf->isLeftToRightDirection()) { - if (nextLeaf) - return createLegacyEditingPosition(nextLeaf->renderer()->node(), nextLeaf->caretMaxOffset()); - - InlineBox* firstLTRLeaf; - do { - firstLTRLeaf = previousLeaf; - previousLeaf = previousLeaf->prevLeafChild(); - } while (previousLeaf && previousLeaf->isLeftToRightDirection()); - return createLegacyEditingPosition(firstLTRLeaf->renderer()->node(), firstLTRLeaf->caretMinOffset()); - } - - return createLegacyEditingPosition(node, box->caretMinOffset()); -} - -static VisiblePosition lastWordBreakInBox(const InlineBox* box, int& offsetOfWordBreak) -{ - // Add the leftmost word break for RTL box or rightmost word break for LTR box. - InlineBox* previousLeaf = box->prevLeafChild(); - InlineBox* nextLeaf = box->nextLeafChild(); - VisiblePosition boundaryPosition; - if (box->direction() == RTL && (!previousLeaf || previousLeaf->isLeftToRightDirection())) - boundaryPosition = leftmostPositionInRTLBoxInLTRBlock(box); - else if (box->direction() == LTR && (!nextLeaf || !nextLeaf->isLeftToRightDirection())) - boundaryPosition = rightmostPositionInLTRBoxInRTLBlock(box); - - if (boundaryPosition.isNull()) - return VisiblePosition(); - - VisiblePosition wordBreak = nextBoundary(boundaryPosition, nextWordPositionBoundary); - if (wordBreak.isNull()) - wordBreak = boundaryPosition; - else if (wordBreak != boundaryPosition) - wordBreak = previousBoundary(wordBreak, previousWordPositionBoundary); - - return positionIsInBox(wordBreak, box, offsetOfWordBreak) ? wordBreak : VisiblePosition(); -} - -static bool positionIsVisuallyOrderedInBoxInBlockWithDifferentDirectionality(const VisiblePosition& wordBreak, const InlineBox* box, int& offsetOfWordBreak) -{ - int previousOffset = offsetOfWordBreak; - return positionIsInBox(wordBreak, box, offsetOfWordBreak) - && (previousOffset == invalidOffset || previousOffset < offsetOfWordBreak); -} - -static VisiblePosition nextWordBreakInBoxInsideBlockWithDifferentDirectionality( - const InlineBox* box, const VisiblePosition& previousWordBreak, int& offsetOfWordBreak, bool& isLastWordBreakInBox) -{ - // FIXME: Probably need to take care of bidi level too. - - // In a LTR block, the word break should be on the left boundary of a word. - // In a RTL block, the word break should be on the right boundary of a word. - // Because previousWordPosition() returns the word break on the right boundary of the word for RTL text, - // we need to use nextWordPosition() to traverse words within the inline boxes from right to left to find the next word break. - // The same applies to LTR text, in which words are traversed within the inline boxes from left to right. - - bool hasSeenWordBreakInThisBox = previousWordBreak.isNotNull(); - VisiblePosition wordBreak = hasSeenWordBreakInThisBox ? previousWordBreak : - createLegacyEditingPosition(box->renderer()->node(), box->caretMinOffset()); - - wordBreak = nextBoundary(wordBreak, nextWordPositionBoundary); - - // Given RTL box "ABC DEF" either follows a LTR box or is the first visual box in an LTR block as an example, - // the visual display of the RTL box is: "(0)J(10)I(9)H(8) (7)F(6)E(5)D(4) (3)C(2)B(1)A(11)", - // where the number in parenthesis represents offset in visiblePosition. - // Start at offset 0, the first word break is at offset 3, the 2nd word break is at offset 7, and the 3rd word break should be at offset 0. - // But nextWordPosition() of offset 7 is offset 11, which should be ignored, - // and the position at offset 0 should be manually added as the last word break within the box. - if (wordBreak != previousWordBreak && positionIsVisuallyOrderedInBoxInBlockWithDifferentDirectionality(wordBreak, box, offsetOfWordBreak)) { - isLastWordBreakInBox = false; - return wordBreak; - } - - isLastWordBreakInBox = true; - return lastWordBreakInBox(box, offsetOfWordBreak); -} - -struct WordBoundaryEntry { - WordBoundaryEntry() - : offsetInInlineBox(invalidOffset) - { - } - - WordBoundaryEntry(const VisiblePosition& position, int offset) - : visiblePosition(position) - , offsetInInlineBox(offset) - { - } - - VisiblePosition visiblePosition; - int offsetInInlineBox; -}; - -typedef Vector<WordBoundaryEntry, 50> WordBoundaryVector; - -static void collectWordBreaksInBoxInsideBlockWithSameDirectionality(const InlineBox* box, WordBoundaryVector& orderedWordBoundaries) -{ - orderedWordBoundaries.clear(); - - VisiblePosition wordBreak; - int offsetOfWordBreak = invalidOffset; - while (1) { - wordBreak = previousWordBreakInBoxInsideBlockWithSameDirectionality(box, wordBreak, offsetOfWordBreak); - if (wordBreak.isNull()) - break; - WordBoundaryEntry wordBoundaryEntry(wordBreak, offsetOfWordBreak); - orderedWordBoundaries.append(wordBoundaryEntry); - } -} - -static void collectWordBreaksInBoxInsideBlockWithDifferntDirectionality(const InlineBox* box, WordBoundaryVector& orderedWordBoundaries) -{ - orderedWordBoundaries.clear(); - - VisiblePosition wordBreak; - int offsetOfWordBreak = invalidOffset; - bool isLastWordBreakInBox = false; - while (1) { - wordBreak = nextWordBreakInBoxInsideBlockWithDifferentDirectionality(box, wordBreak, offsetOfWordBreak, isLastWordBreakInBox); - if (wordBreak.isNotNull()) { - WordBoundaryEntry wordBoundaryEntry(wordBreak, offsetOfWordBreak); - orderedWordBoundaries.append(wordBoundaryEntry); - } - if (isLastWordBreakInBox) - break; - } -} - -static void collectWordBreaksInBox(const InlineBox* box, WordBoundaryVector& orderedWordBoundaries, TextDirection blockDirection) -{ - if (box->direction() == blockDirection) - collectWordBreaksInBoxInsideBlockWithSameDirectionality(box, orderedWordBoundaries); - else - collectWordBreaksInBoxInsideBlockWithDifferntDirectionality(box, orderedWordBoundaries); -} - -static VisiblePosition previousWordBoundaryInBox(const InlineBox* box, int offset) -{ - int offsetOfWordBreak = 0; - VisiblePosition wordBreak; - while (true) { - wordBreak = previousWordBreakInBoxInsideBlockWithSameDirectionality(box, wordBreak, offsetOfWordBreak); - if (wordBreak.isNull()) - break; - if (offset == invalidOffset || offsetOfWordBreak != offset) - return wordBreak; - } - return VisiblePosition(); -} - -static VisiblePosition nextWordBoundaryInBox(const InlineBox* box, int offset) -{ - int offsetOfWordBreak = 0; - VisiblePosition wordBreak; - bool isLastWordBreakInBox = false; - do { - wordBreak = nextWordBreakInBoxInsideBlockWithDifferentDirectionality(box, wordBreak, offsetOfWordBreak, isLastWordBreakInBox); - if (wordBreak.isNotNull() && (offset == invalidOffset || offsetOfWordBreak != offset)) - return wordBreak; - } while (!isLastWordBreakInBox); - return VisiblePosition(); -} - -static VisiblePosition visuallyLastWordBoundaryInBox(const InlineBox* box, int offset, TextDirection blockDirection) -{ - WordBoundaryVector orderedWordBoundaries; - collectWordBreaksInBox(box, orderedWordBoundaries, blockDirection); - if (!orderedWordBoundaries.size()) - return VisiblePosition(); - if (offset == invalidOffset || orderedWordBoundaries[orderedWordBoundaries.size() - 1].offsetInInlineBox != offset) - return orderedWordBoundaries[orderedWordBoundaries.size() - 1].visiblePosition; - if (orderedWordBoundaries.size() > 1) - return orderedWordBoundaries[orderedWordBoundaries.size() - 2].visiblePosition; - return VisiblePosition(); -} - -static int greatestOffsetUnder(int offset, bool boxAndBlockAreInSameDirection, const WordBoundaryVector& orderedWordBoundaries) -{ - if (!orderedWordBoundaries.size()) - return offsetNotFound; - // FIXME: binary search. - if (boxAndBlockAreInSameDirection) { - for (unsigned i = 0; i < orderedWordBoundaries.size(); ++i) { - if (orderedWordBoundaries[i].offsetInInlineBox < offset) - return i; - } - return offsetNotFound; - } - for (int i = orderedWordBoundaries.size() - 1; i >= 0; --i) { - if (orderedWordBoundaries[i].offsetInInlineBox < offset) - return i; - } - return offsetNotFound; -} - -static int smallestOffsetAbove(int offset, bool boxAndBlockAreInSameDirection, const WordBoundaryVector& orderedWordBoundaries) -{ - if (!orderedWordBoundaries.size()) - return offsetNotFound; - // FIXME: binary search. - if (boxAndBlockAreInSameDirection) { - for (int i = orderedWordBoundaries.size() - 1; i >= 0; --i) { - if (orderedWordBoundaries[i].offsetInInlineBox > offset) - return i; - } - return offsetNotFound; - } - for (unsigned i = 0; i < orderedWordBoundaries.size(); ++i) { - if (orderedWordBoundaries[i].offsetInInlineBox > offset) - return i; - } - return offsetNotFound; -} - -static const RootInlineBox* previousRootInlineBox(const InlineBox* box) -{ - Node* node = box->renderer()->node(); - Node* enclosingBlockNode = enclosingNodeWithNonInlineRenderer(node); - Node* previousNode = node->previousLeafNode(); - while (previousNode && enclosingBlockNode == enclosingNodeWithNonInlineRenderer(previousNode)) - previousNode = previousNode->previousLeafNode(); - - while (previousNode && !previousNode->isShadowRoot()) { - Position pos = createLegacyEditingPosition(previousNode, caretMaxOffset(previousNode)); - - if (pos.isCandidate()) { - RenderedPosition renderedPos(pos, DOWNSTREAM); - RootInlineBox* root = renderedPos.rootBox(); - if (root) - return root; - } - - previousNode = previousNode->previousLeafNode(); - } - return 0; -} - -static const RootInlineBox* nextRootInlineBox(const InlineBox* box) -{ - Node* node = box->renderer()->node(); - Node* enclosingBlockNode = enclosingNodeWithNonInlineRenderer(node); - Node* nextNode = node->nextLeafNode(); - while (nextNode && enclosingBlockNode == enclosingNodeWithNonInlineRenderer(nextNode)) - nextNode = nextNode->nextLeafNode(); - - while (nextNode && !nextNode->isShadowRoot()) { - Position pos; - pos = createLegacyEditingPosition(nextNode, caretMinOffset(nextNode)); - - if (pos.isCandidate()) { - RenderedPosition renderedPos(pos, DOWNSTREAM); - RootInlineBox* root = renderedPos.rootBox(); - if (root) - return root; - } - - nextNode = nextNode->nextLeafNode(); - } - return 0; -} - -static const InlineBox* leftInlineBox(const InlineBox* box, TextDirection blockDirection) -{ - if (box->prevLeafChild()) - return box->prevLeafChild(); - - const RootInlineBox* rootBox = box->root(); - const bool isBlockLTR = blockDirection == LTR; - const InlineFlowBox* leftLineBox = isBlockLTR ? rootBox->prevLineBox() : rootBox->nextLineBox(); - if (leftLineBox) - return leftLineBox->lastLeafChild(); - - const RootInlineBox* leftRootInlineBox = isBlockLTR ? previousRootInlineBox(box) : - nextRootInlineBox(box); - return leftRootInlineBox ? leftRootInlineBox->lastLeafChild() : 0; -} - -static const InlineBox* rightInlineBox(const InlineBox* box, TextDirection blockDirection) -{ - if (box->nextLeafChild()) - return box->nextLeafChild(); - - const RootInlineBox* rootBox = box->root(); - const bool isBlockLTR = blockDirection == LTR; - const InlineFlowBox* rightLineBox = isBlockLTR ? rootBox->nextLineBox() : rootBox->prevLineBox(); - if (rightLineBox) - return rightLineBox->firstLeafChild(); - - const RootInlineBox* rightRootInlineBox = isBlockLTR ? nextRootInlineBox(box) : - previousRootInlineBox(box); - return rightRootInlineBox ? rightRootInlineBox->firstLeafChild() : 0; -} - -static VisiblePosition leftWordBoundary(const InlineBox* box, int offset, TextDirection blockDirection) -{ - VisiblePosition wordBreak; - for (const InlineBox* adjacentBox = box; adjacentBox; adjacentBox = leftInlineBox(adjacentBox, blockDirection)) { - if (blockDirection == LTR) { - if (adjacentBox->isLeftToRightDirection()) - wordBreak = previousWordBoundaryInBox(adjacentBox, adjacentBox == box ? offset : invalidOffset); - else - wordBreak = nextWordBoundaryInBox(adjacentBox, adjacentBox == box ? offset : invalidOffset); - } else - wordBreak = visuallyLastWordBoundaryInBox(adjacentBox, adjacentBox == box ? offset : invalidOffset, blockDirection); - if (wordBreak.isNotNull()) - return wordBreak; - } - return VisiblePosition(); -} - -static VisiblePosition rightWordBoundary(const InlineBox* box, int offset, TextDirection blockDirection) -{ - - VisiblePosition wordBreak; - for (const InlineBox* adjacentBox = box; adjacentBox; adjacentBox = rightInlineBox(adjacentBox, blockDirection)) { - if (blockDirection == RTL) { - if (adjacentBox->isLeftToRightDirection()) - wordBreak = nextWordBoundaryInBox(adjacentBox, adjacentBox == box ? offset : invalidOffset); - else - wordBreak = previousWordBoundaryInBox(adjacentBox, adjacentBox == box ? offset : invalidOffset); - } else - wordBreak = visuallyLastWordBoundaryInBox(adjacentBox, adjacentBox == box ? offset : invalidOffset, blockDirection); - if (!wordBreak.isNull()) - return wordBreak; - } - return VisiblePosition(); -} - -static bool positionIsInBoxButNotOnBoundary(const VisiblePosition& wordBreak, const InlineBox* box) -{ - int offsetOfWordBreak; - return positionIsInBox(wordBreak, box, offsetOfWordBreak) - && offsetOfWordBreak != box->caretMaxOffset() && offsetOfWordBreak != box->caretMinOffset(); -} - -static VisiblePosition leftWordPositionIgnoringEditingBoundary(const VisiblePosition& visiblePosition) -{ - InlineBox* box; - int offset; - visiblePosition.getInlineBoxAndOffset(box, offset); - - if (!box) - return VisiblePosition(); - - TextDirection blockDirection = directionOfEnclosingBlock(visiblePosition.deepEquivalent()); - - // FIXME: If the box's directionality is the same as that of the enclosing block, when the offset is at the box boundary - // and the direction is towards inside the box, do I still need to make it a special case? For example, a LTR box inside a LTR block, - // when offset is at box's caretMinOffset and the direction is DirectionRight, should it be taken care as a general case? - if (offset == box->caretLeftmostOffset()) - return leftWordBoundary(leftInlineBox(box, blockDirection), invalidOffset, blockDirection); - if (offset == box->caretRightmostOffset()) - return leftWordBoundary(box, offset, blockDirection); - - - VisiblePosition wordBreak; - if (blockDirection == LTR) { - if (box->direction() == blockDirection) - wordBreak = previousBoundary(visiblePosition, previousWordPositionBoundary); - else - wordBreak = nextBoundary(visiblePosition, nextWordPositionBoundary); - } - if (wordBreak.isNotNull() && positionIsInBoxButNotOnBoundary(wordBreak, box)) - return wordBreak; - - WordBoundaryVector orderedWordBoundaries; - collectWordBreaksInBox(box, orderedWordBoundaries, blockDirection); - - int index = box->isLeftToRightDirection() ? greatestOffsetUnder(offset, blockDirection == LTR, orderedWordBoundaries) - : smallestOffsetAbove(offset, blockDirection == RTL, orderedWordBoundaries); - if (index >= 0) - return orderedWordBoundaries[index].visiblePosition; - - return leftWordBoundary(leftInlineBox(box, blockDirection), invalidOffset, blockDirection); -} - -static VisiblePosition rightWordPositionIgnoringEditingBoundary(const VisiblePosition& visiblePosition) -{ - InlineBox* box; - int offset; - visiblePosition.getInlineBoxAndOffset(box, offset); - - if (!box) - return VisiblePosition(); - - TextDirection blockDirection = directionOfEnclosingBlock(visiblePosition.deepEquivalent()); - - if (offset == box->caretLeftmostOffset()) - return rightWordBoundary(box, offset, blockDirection); - if (offset == box->caretRightmostOffset()) - return rightWordBoundary(rightInlineBox(box, blockDirection), invalidOffset, blockDirection); - - VisiblePosition wordBreak; - if (blockDirection == RTL) { - if (box->direction() == blockDirection) - wordBreak = previousBoundary(visiblePosition, previousWordPositionBoundary); - else - wordBreak = nextBoundary(visiblePosition, nextWordPositionBoundary); - } - if (wordBreak.isNotNull() && positionIsInBoxButNotOnBoundary(wordBreak, box)) - return wordBreak; - - WordBoundaryVector orderedWordBoundaries; - collectWordBreaksInBox(box, orderedWordBoundaries, blockDirection); - - int index = box->isLeftToRightDirection() ? smallestOffsetAbove(offset, blockDirection == LTR, orderedWordBoundaries) - : greatestOffsetUnder(offset, blockDirection == RTL, orderedWordBoundaries); - if (index >= 0) - return orderedWordBoundaries[index].visiblePosition; - - return rightWordBoundary(rightInlineBox(box, blockDirection), invalidOffset, blockDirection); -} - -VisiblePosition leftWordPosition(const VisiblePosition& visiblePosition) -{ - if (visiblePosition.isNull()) - return VisiblePosition(); - - VisiblePosition leftWordBreak = leftWordPositionIgnoringEditingBoundary(visiblePosition); - leftWordBreak = visiblePosition.honorEditingBoundaryAtOrBefore(leftWordBreak); - - // FIXME: How should we handle a non-editable position? - if (leftWordBreak.isNull() && isEditablePosition(visiblePosition.deepEquivalent())) { - TextDirection blockDirection = directionOfEnclosingBlock(visiblePosition.deepEquivalent()); - leftWordBreak = blockDirection == LTR ? startOfEditableContent(visiblePosition) : endOfEditableContent(visiblePosition); - } - return leftWordBreak; -} - -VisiblePosition rightWordPosition(const VisiblePosition& visiblePosition) -{ - if (visiblePosition.isNull()) - return VisiblePosition(); - - VisiblePosition rightWordBreak = rightWordPositionIgnoringEditingBoundary(visiblePosition); - rightWordBreak = visiblePosition.honorEditingBoundaryAtOrBefore(rightWordBreak); - - // FIXME: How should we handle a non-editable position? - if (rightWordBreak.isNull() && isEditablePosition(visiblePosition.deepEquivalent())) { - TextDirection blockDirection = directionOfEnclosingBlock(visiblePosition.deepEquivalent()); - rightWordBreak = blockDirection == LTR ? endOfEditableContent(visiblePosition) : startOfEditableContent(visiblePosition); - } - return rightWordBreak; -} - } diff --git a/Source/WebCore/editing/visible_units.h b/Source/WebCore/editing/visible_units.h index 3ca4cdaac..c5c1eb653 100644 --- a/Source/WebCore/editing/visible_units.h +++ b/Source/WebCore/editing/visible_units.h @@ -26,6 +26,7 @@ #ifndef visible_units_h #define visible_units_h +#include "EditingBehaviorTypes.h" #include "EditingBoundary.h" #include "TextDirection.h" @@ -41,8 +42,8 @@ VisiblePosition startOfWord(const VisiblePosition &, EWordSide = RightWordIfOnBo VisiblePosition endOfWord(const VisiblePosition &, EWordSide = RightWordIfOnBoundary); VisiblePosition previousWordPosition(const VisiblePosition &); VisiblePosition nextWordPosition(const VisiblePosition &); -VisiblePosition rightWordPosition(const VisiblePosition&); -VisiblePosition leftWordPosition(const VisiblePosition&); +VisiblePosition rightWordPosition(const VisiblePosition&, bool skipsSpaceWhenMovingRight); +VisiblePosition leftWordPosition(const VisiblePosition&, bool skipsSpaceWhenMovingRight); bool isStartOfWord(const VisiblePosition&); // sentences |
